这是使用 Pygame 模块在 Python 3 中创建视频游戏的系列文章的第 6 部分。之前的文章是
- 学习如何通过构建一个简单的骰子游戏来使用 Python 编程
- 使用 Pygame 模块通过 Python 构建游戏框架
- 如何向您的 Python 游戏中添加玩家
- 使用 Pygame 移动您的游戏角色
- 没有反派的英雄算什么?如何向您的 Python 游戏中添加一个反派
平台游戏需要平台。
在 Pygame 中,平台本身就是精灵,就像您的可玩精灵一样。这很重要,因为拥有作为对象的平台使您的玩家精灵更容易与它们互动。
创建平台有两个主要步骤。首先,您必须编写对象代码,然后您必须规划出您希望对象出现的位置。
编写平台对象代码
要构建平台对象,您需要创建一个名为 Platform
的类。它是一个精灵,就像您的 Player
精灵一样,具有许多相同的属性。
您的 Platform
类需要了解很多关于您想要的平台类型、它应该在游戏世界中出现的位置以及它应该包含的图像的信息。很多信息甚至可能还不存在,这取决于您对游戏的规划程度,但这没关系。正如您在 移动文章 的结尾才告诉您的 Player 精灵移动速度有多快一样,您也不必预先告诉 Platform
所有信息。
在脚本的对象部分,创建一个新类
# x location, y location, img width, img height, img file
class Platform(pygame.sprite.Sprite):
def __init__(self, xloc, yloc, imgw, imgh, img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images', img)).convert()
self.image.convert_alpha()
self.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.y = yloc
self.rect.x = xloc
调用时,此类会在屏幕上的某个 X 和 Y 位置创建一个对象,具有某个宽度和高度,并使用某个图像文件作为纹理。这与玩家或敌人如何在屏幕上绘制非常相似。您可能从 Player 和 Enemy 类中识别出相同的代码结构。
平台类型
下一步是规划出所有平台需要出现的位置。
瓦片方法
有几种不同的方法来实现平台游戏世界。在最初的横向卷轴游戏中,例如《超级马里奥兄弟》和《索尼克》,该技术是使用“瓦片”,这意味着有一些方块来代表地面和各种平台,并且这些方块被使用和重用来制作关卡。您只有八到十二种不同的方块,您可以将它们排列在屏幕上以创建地面、浮动平台以及您的游戏需要的任何其他东西。有些人发现这是制作游戏的更简单方法,因为您只需要制作(或下载)一小组关卡素材即可创建许多不同的关卡。但是,代码需要更多的数学知识。

超级企鹅,一款基于瓦片的视频游戏。
手绘方法
另一种方法是将每个素材都制作成一个完整的图像。如果您喜欢为您的游戏世界创建素材,这是一个在图形应用程序中花费时间、构建游戏世界每个部分的绝佳借口。这种方法需要的数学知识较少,因为所有平台都是完整的、完整的对象,您可以告诉 Python 将它们放置在屏幕上的位置。
每种方法都有优点和缺点,并且您必须使用的代码根据您选择的方法略有不同。我将介绍这两种方法,以便您可以在您的项目中使用其中一种或另一种,甚至混合使用。
关卡映射
规划您的游戏世界是关卡设计和一般游戏编程的重要组成部分。它确实涉及数学,但并不太难,而且 Python 擅长数学,因此它可以提供一些帮助。
您可能会发现先在纸上设计很有帮助。拿一张纸,画一个框来代表您的游戏窗口。在框中绘制平台,用其 X 和 Y 坐标以及预期的宽度和高度标记每个平台。框中的实际位置不必完全精确,只要您保持数字的真实性即可。例如,如果您的屏幕宽度为 720 像素,那么您无法在一个屏幕上容纳八个每个 100 像素的平台。
当然,您的游戏中的并非所有平台都必须适合一个屏幕大小的框,因为当您的玩家在其中行走时,您的游戏会滚动。因此,继续在第一个屏幕的右侧绘制您的游戏世界,直到关卡结束。
如果您喜欢更精确一点,可以使用方格纸。当设计使用瓦片的游戏时,这尤其有帮助,因为每个网格正方形可以代表一个瓦片。

坐标
您可能在学校学过 笛卡尔坐标系。您学到的知识适用于 Pygame,只是在 Pygame 中,您的游戏世界的坐标将 0,0
放置在屏幕的左上角,而不是中间,这可能与您在几何课上习惯的不同。

Pygame 中坐标的示例。
X 轴从最左侧的 0 开始,无限向右增加。Y 轴从屏幕顶部的 0 开始,向下延伸。
图像尺寸
如果您不知道您的玩家、敌人和平台有多大,那么规划游戏世界是没有意义的。您可以在图形程序中找到平台或瓦片的尺寸。例如,在 Krita 中,单击图像菜单并选择属性。您可以在属性窗口的顶部找到尺寸。
或者,您可以创建一个简单的 Python 脚本来告诉您图像的尺寸。为此,您必须安装一个名为 Pillow 的 Python 模块,该模块提供 Python 图像库 (PIL)。将 Pillow 添加到您的项目的 requirements.txt
文件中
pygame~=1.9.6
Pillow
在 PyCharm 中创建一个新的 Python 文件,并将其命名为 identify
。将此代码键入其中
#!/usr/bin/env python3
# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
from PIL import Image
import os.path
import sys
if len(sys.argv) > 1:
print(sys.argv[1])
else:
sys.exit('Syntax: identify.py [filename]')
pic = sys.argv[1]
img = Image.open(pic)
X = img.size[0]
Y = img.size[1]
print(X, Y)
单击 PyCharm 窗口底部的 Terminal 选项卡,以在您的虚拟环境中打开终端。现在您可以将 Pillow 模块安装到您的环境中
(venv) pip install -r requirements.txt
Requirement already satisfied: pygame~=1.9.6 [...]
Installed Pillow [...]
安装完成后,从您的游戏项目目录中运行您的脚本
(venv) python ./identify.py images/ground.png
(1080, 97)
此示例中地面平台的图像大小为 1080 像素宽和 97 像素高。
平台块
如果您选择单独绘制每个素材,则必须创建几个平台以及您想要插入到游戏世界中的任何其他元素,每个元素都在自己的文件中。换句话说,您应该每个素材有一个文件,如下所示

每个对象一个图像文件。
您可以根据需要多次重复使用每个平台,只需确保每个文件仅包含一个平台即可。您不能使用包含所有内容的文件,如下所示

您的关卡不能是一个图像文件。
您可能希望您的游戏在完成后看起来像那样,但是如果您在一个大文件中创建您的关卡,则无法区分平台和背景,因此请在它们自己的文件中绘制您的对象,或者从大文件中裁剪它们并保存单个副本。
注意: 与您的其他素材一样,您可以使用 GIMP、Krita、MyPaint 或 Inkscape 来创建您的游戏素材。
平台在每个关卡开始时出现在屏幕上,因此您必须在 Level
类中添加一个 platform
函数。这里的特殊情况是地面平台,它非常重要,可以被视为自己的平台组。通过将地面视为其自身的特殊平台类型,您可以选择它是滚动还是静止不动,而其他平台则漂浮在其上方。这取决于您。
将这两个函数添加到您的 Level
类中
def ground(lvl,x,y,w,h):
ground_list = pygame.sprite.Group()
if lvl == 1:
ground = Platform(x,y,w,h,'block-ground.png')
ground_list.add(ground)
if lvl == 2:
print("Level " + str(lvl) )
return ground_list
def platform( lvl ):
plat_list = pygame.sprite.Group()
if lvl == 1:
plat = Platform(200, worldy-97-128, 285,67,'block-big.png')
plat_list.add(plat)
plat = Platform(500, worldy-97-320, 197,54,'block-small.png')
plat_list.add(plat)
if lvl == 2:
print("Level " + str(lvl) )
return plat_list
ground
函数需要 X 和 Y 位置,以便 Pygame 知道将地面平台放置在哪里。它还需要平台的宽度和高度,以便 Pygame 知道地面在每个方向延伸多远。该函数使用您的 Platform
类在屏幕上生成一个对象,然后将该对象添加到 ground_list
组。
platform
函数本质上是相同的,只是有更多平台需要列出。在本例中,只有两个,但您可以根据需要拥有任意多个。输入一个平台后,您必须将其添加到 plat_list
中,然后再列出另一个平台。如果您不将平台添加到组中,则它不会出现在您的游戏中。
提示: 考虑到您的游戏世界中 0 在顶部可能很困难,因为现实世界中发生的情况恰恰相反;当计算您有多高时,您不会从天空向下测量自己,而是从脚到头顶测量自己。
如果从“地面”向上构建您的游戏世界对您来说更容易,那么将 Y 轴值表示为负数可能会有所帮助。例如,您知道您的游戏世界的底部是
worldy
的值。因此,worldy
减去地面的高度(在本例中为 97)就是您的玩家通常站立的位置。如果您的角色高 64 像素,那么地面减去 128 正好是您的玩家高度的两倍。实际上,放置在 128 像素处的平台大约是相对于您的玩家的两层楼高。位于 -320 的平台是另外三层楼。等等。
正如您现在可能知道的那样,如果您不使用它们,您的所有类和函数都毫无价值。将此代码添加到您的设置部分
ground_list = Level.ground(1, 0, worldy-97, 1080, 97)
plat_list = Level.platform(1)
并将这些行添加到您的主循环中(同样,第一行仅用于上下文)
enemy_list.draw(world) # refresh enemies
ground_list.draw(world) # refresh ground
plat_list.draw(world) # refresh platforms
平铺平台
平铺游戏世界被认为更容易制作,因为您只需预先绘制几个方块,就可以一遍又一遍地使用它们来创建游戏中的每个平台。在 kenney.nl 和 OpenGameArt.org 等网站上,有许多带有 知识共享许可 的瓦片集供您使用。来自 kenney.nl 的 simplified-platformer-pack 是 64 像素的正方形,因此这是本文使用的瓦片尺寸。如果您下载或创建尺寸不同的瓦片,请根据需要调整代码。
Platform
类与前面部分提供的类相同。
但是,Level
类中的 ground
和 platform
必须使用循环来计算创建每个平台需要使用多少个方块。
如果您打算在您的游戏世界中拥有一个坚实的地面,那么地面很简单。您只需在整个窗口中“克隆”您的地面瓦片。例如,您可以创建一个 X 和 Y 值列表来指示每个瓦片应放置的位置,然后使用循环来获取每个值并绘制一个瓦片。这只是一个示例,因此不要将其添加到您的代码中
# Do not add this to your code
gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656]
但是,如果您仔细观察,您会看到所有 Y 值始终相同(具体来说是 656),并且 X 值以 64 的增量稳步增加,这是瓦片的大小。这种重复性正是计算机擅长的,因此您可以使用一点数学逻辑让计算机为您完成所有计算
将其添加到脚本的设置部分
gloc = []
tx = 64
ty = 64
i=0
while i <= (worldx/tx)+tx:
gloc.append(i*tx)
i=i+1
ground_list = Level.ground( 1,gloc,tx,ty )
使用此代码,无论您的窗口大小如何,Python 都会将游戏世界的宽度除以瓦片的宽度,并创建一个列出每个 X 值的数组。这不会计算 Y 值,但无论如何,在平坦地面上,Y 值永远不会改变。
要在函数中使用数组,请使用一个 while
循环,该循环查看每个条目并在适当的位置添加一个地面瓦片。将此函数添加到您的 Level
类中
def ground(lvl,gloc,tx,ty):
ground_list = pygame.sprite.Group()
i=0
if lvl == 1:
while i < len(gloc):
ground = Platform(gloc[i],worldy-ty,tx,ty,'tile-ground.png')
ground_list.add(ground)
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return ground_list
除了 while
循环外,这几乎与上一节中提供的块状平台游戏的 ground
函数的代码相同。
对于移动平台,原理类似,但您可以使用一些技巧让您的生活更轻松。
您可以根据平台的起始像素(其 X 值)、距地面的高度(其 Y 值)以及要绘制的瓦片数量来定义平台,而不是按像素映射每个平台。这样,您不必担心每个平台的宽度和高度。
此技巧的逻辑稍微复杂一些,因此请仔细复制此代码。在一个 while
循环内部有一个 while
循环,因为此函数必须查看每个数组条目中的所有三个值才能成功构建一个完整的平台。在本例中,只有三个平台定义为 ploc.append
语句,但您的游戏可能需要更多平台,因此请根据需要定义任意多个平台。当然,有些平台还不会出现,因为它们远离屏幕,但一旦您实现滚动,它们就会进入视野。
def platform(lvl,tx,ty):
plat_list = pygame.sprite.Group()
ploc = []
i=0
if lvl == 1:
ploc.append((200,worldy-ty-128,3))
ploc.append((300,worldy-ty-256,3))
ploc.append((500,worldy-ty-128,4))
while i < len(ploc):
j=0
while j <= ploc[i][2]:
plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'tile.png')
plat_list.add(plat)
j=j+1
print('run' + str(i) + str(ploc[i]))
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return plat_list
当然,这只是创建了一个函数来计算每个关卡的平台。您的代码尚未调用该函数。
在程序的设置部分,添加此行
plat_list = Level.platform(1, tx, ty)
要使平台出现在您的游戏世界中,它们必须在您的主循环中。如果您尚未这样做,请将这些行添加到您的主循环中(同样,第一行仅用于上下文)
enemy_list.draw(world) # refresh enemies
ground_list.draw(world) # refresh ground
plat_list.draw(world) # refresh platforms
启动您的游戏,并根据需要调整平台的放置位置。不要担心您看不到在屏幕外生成的平台;您很快就会解决这个问题。

应用您所学的知识
我还没有演示如何在您的游戏世界中放置您的敌人,但请应用您到目前为止所学的知识,将敌人精灵放置在平台上或地面上。
暂时不要定位您的英雄精灵。这必须由重力(或至少是它的模拟)来管理,您将在接下来的两篇文章中学习到。
现在,这是到目前为止的代码
#!/usr/bin/env python3
# by Seth Kenlon
# GPLv3
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://gnu.ac.cn/licenses/>.
import pygame
import sys
import os
'''
Variables
'''
worldx = 960
worldy = 720
fps = 40
ani = 4
world = pygame.display.set_mode([worldx, worldy])
BLUE = (25, 25, 200)
BLACK = (23, 23, 23)
WHITE = (254, 254, 254)
ALPHA = (0, 255, 0)
'''
Objects
'''
# x location, y location, img width, img height, img file
class Platform(pygame.sprite.Sprite):
def __init__(self, xloc, yloc, imgw, imgh, img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images', img)).convert()
self.image.convert_alpha()
self.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.y = yloc
self.rect.x = xloc
class Player(pygame.sprite.Sprite):
"""
Spawn a player
"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.movex = 0
self.movey = 0
self.frame = 0
self.health = 10
self.images = []
for i in range(1, 5):
img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert()
img.convert_alpha()
img.set_colorkey(ALPHA)
self.images.append(img)
self.image = self.images[0]
self.rect = self.image.get_rect()
def control(self, x, y):
"""
control player movement
"""
self.movex += x
self.movey += y
def update(self):
"""
Update sprite position
"""
self.rect.x = self.rect.x + self.movex
self.rect.y = self.rect.y + self.movey
# moving left
if self.movex < 0:
self.frame += 1
if self.frame > 3 * ani:
self.frame = 0
self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > 3 * ani:
self.frame = 0
self.image = self.images[self.frame // ani]
hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
for enemy in hit_list:
self.health -= 1
print(self.health)
class Enemy(pygame.sprite.Sprite):
"""
Spawn an enemy
"""
def __init__(self, x, y, img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images', img))
self.image.convert_alpha()
self.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.counter = 0
def move(self):
"""
enemy movement
"""
distance = 80
speed = 8
if self.counter >= 0 and self.counter <= distance:
self.rect.x += speed
elif self.counter >= distance and self.counter <= distance * 2:
self.rect.x -= speed
else:
self.counter = 0
self.counter += 1
class Level:
def ground(lvl, gloc, tx, ty):
ground_list = pygame.sprite.Group()
i = 0
if lvl == 1:
while i < len(gloc):
ground = Platform(gloc[i], worldy - ty, tx, ty, 'tile-ground.png')
ground_list.add(ground)
i = i + 1
if lvl == 2:
print("Level " + str(lvl))
return ground_list
def bad(lvl, eloc):
if lvl == 1:
enemy = Enemy(eloc[0], eloc[1], 'enemy.png')
enemy_list = pygame.sprite.Group()
enemy_list.add(enemy)
if lvl == 2:
print("Level " + str(lvl))
return enemy_list
# x location, y location, img width, img height, img file
def platform(lvl, tx, ty):
plat_list = pygame.sprite.Group()
ploc = []
i = 0
if lvl == 1:
ploc.append((200, worldy - ty - 128, 3))
ploc.append((300, worldy - ty - 256, 3))
ploc.append((500, worldy - ty - 128, 4))
while i < len(ploc):
j = 0
while j <= ploc[i][2]:
plat = Platform((ploc[i][0] + (j * tx)), ploc[i][1], tx, ty, 'tile.png')
plat_list.add(plat)
j = j + 1
print('run' + str(i) + str(ploc[i]))
i = i + 1
if lvl == 2:
print("Level " + str(lvl))
return plat_list
'''
Setup
'''
backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
clock = pygame.time.Clock()
pygame.init()
backdropbox = world.get_rect()
main = True
player = Player() # spawn player
player.rect.x = 0 # go to x
player.rect.y = 30 # go to y
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10
eloc = []
eloc = [300, 0]
enemy_list = Level.bad(1, eloc)
gloc = []
tx = 64
ty = 64
i = 0
while i <= (worldx / tx) + tx:
gloc.append(i * tx)
i = i + 1
ground_list = Level.ground(1, gloc, tx, ty)
plat_list = Level.platform(1, tx, ty)
'''
Main Loop
'''
while main:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
try:
sys.exit()
finally:
main = False
if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
pygame.quit()
try:
sys.exit()
finally:
main = False
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(-steps, 0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(steps, 0)
if event.key == pygame.K_UP or event.key == ord('w'):
print('jump')
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(steps, 0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps, 0)
world.blit(backdrop, backdropbox)
player.update()
player_list.draw(world)
enemy_list.draw(world)
ground_list.draw(world)
plat_list.draw(world)
for e in enemy_list:
e.move()
pygame.display.flip()
clock.tick(fps)
6 条评论