在本系列关于第6部分中从头开始构建Python游戏的,创建一些供角色旅行的平台。
图片作者:Opensource.com
这是正在进行的关于使用Pygame模块在Python 3创建视频游戏的系列文章的第6部分。以前的文章有:
Platformer游戏需要平台。
在Pygame中,平台本身就是精灵,就像你的可以用来玩的精灵一样。这一点很重要,因为有了作为对象的平台,玩家精灵就可以更容易地与它们进行交互。
创建平台有两个主要步骤。首先,必须对对象进行编码,然后必须映射出要显示的对象的位置。
要构建平台对象,可以创建一个名为的类。这是一个精灵,就像你的玩家精灵一样,有很多相同的属性。
你的平台等级需要知道很多关于你想要的平台,它应该出现在游戏世界的什么地方,以及它应该包含什么图像的信息。很多信息可能还不存在,这取决于你计划了多少游戏,但没关系。就像你没有告诉你的玩家的精灵移动有多快,直到运动文章的结尾,你不必提前告诉一切。
在本系列中编写的脚本的顶部,创建一个新类。此代码示例中的前三行用于上下文,因此请在注释下面添加代码:
import pygame
import sys
import os
## new code below:
class Platform(pygame.sprite.Sprite):
# x location, y location, img width, img height, img file
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位置创建一个具有一定宽度和高度的对象,使用一些图像文件作为纹理。这与玩家或敌人在屏幕上的表现非常相似。
下一步是找出所有平台需要出现的位置。
实现平台游戏世界有几种不同的方法。在最初的侧滚游戏中,如Mario Super Bros和Sonic the Hedgehog,技术是使用“tiles”,意思是有几个块来代表地面和各种平台,这些块被使用和重复使用以使其水平。你只有8到12种不同的积木,你在屏幕上把它们排成一行来创造地面,漂浮平台,以及你的游戏所需要的任何东西。有些人认为这是制作游戏的更简单的方法,因为你只需要制作(或下载)一小部分水平的资源就可以创建许多不同的水平。然而,这个代码需要更多的数学知识。
另一种方法是使每一个资产都成为一个整体图像。如果你喜欢为你的游戏世界创建资产,这是一个很好的借口花时间在一个图形应用程序上,建立你的游戏世界的每一个部分。这个方法不需要太多的数学运算,因为所有的平台都是完整的对象,您可以告诉Python将它们放在屏幕上的哪个位置。
每种方法都有优点和缺点,而且必须使用的代码根据您选择的方法略有不同。我将涵盖两者,以便您可以在您的项目中使用其中的一个或另一个,甚至两者的混合。
绘制游戏世界是级别设计和游戏编程的重要组成部分。它确实涉及到数学,但没有什么太难的,而且Python擅长数学,所以它可以帮助一些人。
你可能会发现先在纸上设计是有帮助的。找一张纸,画一个盒子来代表你的游戏窗口。在框中绘制平台,用其X和Y坐标以及预期的宽度和高度标记每个平台。只要保持数字的真实性,框中的实际位置就不必精确。例如,如果你的屏幕是720像素宽,那么你就不能在一个屏幕上安装8个100像素的平台。
当然,并不是所有的平台都必须放在一个屏幕大小的框中,因为你的游戏会随着玩家的浏览而滚动。所以继续把你的游戏世界画在第一个屏幕的右边直到关卡结束。
如果你想更精确一点,你可以用相纸。这在使用平铺设计游戏时特别有用,因为每个网格正方形可以表示一个平铺。
你可能在学校学过笛卡尔坐标系。你所学到的适用于Pygame,除了在Pygame中,游戏世界的坐标位于屏幕的左上角而不是中间,这可能是你在几何类中习惯的。
x轴在最左边的0处开始,无限地向右边扩展。Y轴在屏幕顶部以0开始,并向下延伸。
如果你不知道你的玩家、敌人和平台有多大,那么规划一个游戏世界就毫无意义。可以在图形程序中找到平台或分幅的尺寸。例如,在Krita中,单击图像菜单并选择属性。您可以在“属性”窗口的顶部找到维度。
或者,可以创建一个简单的Python脚本来告诉您图像的尺寸。打开新的文本文件并在其中键入以下代码:
#!/usr/bin/env python3
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]
将文件保存为identify.py
.
要设置此脚本,必须安装一组额外的Python模块,其中包含脚本中使用的新关键字:
$ pip3 install Pillow --user
安装后,从游戏项目目录中运行脚本:
$ python3 ./identify.py images/ground.png
(1080, 97)
本例中地面平台的图像大小为1080像素宽,97像素高。
如果选择单独绘制每个资源,则必须创建多个平台和要插入游戏世界的任何其他元素,每个平台和元素都在其自己的文件中。换句话说,每个资产应该有一个文件,如下所示:
可以根据需要多次重用每个平台,只要确保每个文件只包含一个平台。不能使用包含所有内容的文件,例如:
你可能希望你的游戏在完成后看起来像那样,但是如果你在一个大文件中创建你的级别,就无法区分平台和背景,所以要么在自己的文件中绘制你的对象,要么从一个大文件中裁剪它们并保存单个副本。
注:与其他资源一样,您可以使用GIMP、Krita、MyPaint或Inkscape创建游戏资源。
平台出现在每个级别的开始处的屏幕上,因此必须在类中添加一个函数。这里的特例是地面平台,其重要性足以被视为自己的平台组。通过将地面视为自己的特殊平台,您可以选择它是滚动的还是静止的,而其他平台则漂浮在它的顶部。这取决于你。平台层
将这两个函数添加到水平等级中:
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
该函数需要一个X和Y位置,因此Pygame知道在哪里放置地面平台。它还需要平台的宽度和高度,因此Pygame知道地面在每个方向延伸的距离。函数使用类在屏幕上生成一个对象,然后将该对象添加到平台地面清单组中。
这个函数本质上是相同的,只是有更多的平台可以列出。在这个例子中,只有两个,但是您可以拥有任意多个。进入一个平台后,必须将其添加到,然后再列出另一个平台。如果你不在组中添加一个平台,那么它就不会出现在你的游戏中。
提示:很难想象你的游戏世界中0在顶部,因为现实世界的情况正好相反;当你计算出自己有多高时,你不是从天上往下测量自己,而是从脚到头顶测量自己。
如果你更容易从“地面”上来构建你的游戏世界,它可能有助于将Y轴值表示为负值。例如,你知道你游戏世界的底部是的价值。所以减去地面的高度(在这个例子中是97)就是你的玩家通常站的地方。如果你的角色是64像素高,那么地面减去128的高度正好是你的玩家的两倍。实际上,一个放置在128像素的平台相对于玩家来说大约有两层楼高。在-320的平台还有三层楼等等。
正如您现在可能知道的,如果不使用它们,您的类和函数都没有多大价值。将此代码添加到设置部分(第一行仅用于上下文,因此添加最后两行):
enemy_list = Level.bad( 1, eloc )
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
平铺的游戏世界被认为是比较容易制作的,因为你只需要在前面画几个方块,就可以一遍又一遍地使用它们来创建游戏中的每个平台。在OpenGameArt.org这样的网站上,你甚至可以使用一些平铺块。
这个平台等级与前面几节中提供的等级相同。
但是,Level类中的ground和platform必须使用循环来计算要用于创建每个平台的块数。
如果你想在你的游戏世界里有一个坚实的基础,那么基础很简单。你只是在整个窗户上“克隆”你的平块。例如,您可以创建一个X和Y值列表来指定每个平铺应放置在何处,然后使用循环获取每个值并绘制一个平铺。这只是一个例子,所以不要将其添加到代码中:
# Do not add this to your code
gloc = [0,656,64,656,128,656,192,656,256,656
但是,如果仔细观察,可以看到所有的Y值总是相同的,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值,但在平地上也不会改变。
要在函数中使用数组,请使用一个循环来查看每个条目,并在适当的位置添加平块:虽然
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循环外,这几乎与上面一节中提供的块式平台的基本函数相同。对于移动平台,原理是相似的,但是有一些技巧可以让你的生活更轻松。
与按像素映射每个平台不同,您可以通过平台的起始像素(其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
为了让平台出现在你的游戏世界中,它们必须在你的主循环中。如果您还没有这样做,请将这些行添加到主循环(同样,第一行仅用于上下文):
enemy_list.draw(world) # refresh enemies
ground_list.draw(world) # refresh ground
plat_list.draw(world) # refresh platforms
启动游戏,并根据需要调整平台的位置。别担心你看不到屏幕外衍生出来的平台;你很快就会修复的。
以下是迄今为止的游戏图片和代码:
#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement
# add enemy and basic collision
# add platform
# 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.
import pygame
import sys
import os
'''
Objects
'''
class Platform(pygame.sprite.Sprite):
# x location, y location, img width, img height, img file
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.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.score = 1
self.images = []
for i in range(1,9):
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 > ani*3:
self.frame = 0
self.image = self.images[self.frame//ani]
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > ani*3:
self.frame = 0
self.image = self.images[(self.frame//ani)+4]
# collisions
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
for enemy in enemy_hit_list:
self.health -= 1
print(self.health)
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
for g in ground_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 bad(lvl,eloc):
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
if lvl == 2:
print("Level " + str(lvl) )
return enemy_list
def loot(lvl,lloc):
print(lvl)
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,'ground.png')
ground_list.add(ground)
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return ground_list
def platform(lvl,tx,ty):
plat_list = pygame.sprite.Group()
ploc = []
i=0
if lvl == 1:
ploc.append((0,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,'ground.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
'''
worldx = 960
worldy = 720
fps = 40 # frame rate
ani = 4 # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True
BLUE = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)
world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10 # how fast to move
eloc = []
eloc = [200,20]
gloc = []
#gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630]
tx = 64 #tile size
ty = 64 #tile size
i=0
while i <= (worldx/tx)+tx:
gloc.append(i*tx)
i=i+1
enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )
plat_list = Level.platform( 1,tx,ty )
'''
Main loop
'''
while main == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
main = False
if event.type == pygame.KEYDOWN:
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)
if event.key == ord('q'):
pygame.quit()
sys.exit()
main = False
# world.fill(BLACK)
world.blit(backdrop, backdropbox)
player.update()
player_list.draw(world) #refresh player position
enemy_list.draw(world) # refresh enemies
ground_list.draw(world) # refresh enemies
plat_list.draw(world) # refresh platforms
for e in enemy_list:
e.move()
pygame.display.flip()
clock.tick(fps)
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。