首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Pygame在Python游戏中放置平台【Gaming】

使用Pygame在Python游戏中放置平台【Gaming】

作者头像
五月Rambo
修改2019-11-18 12:05:39
2.6K0
修改2019-11-18 12:05:39
举报

在本系列关于第6部分中从头开始构建Python游戏的,创建一些供角色旅行的平台。

图片作者:Opensource.com

这是正在进行的关于使用Pygame模块在Python 3创建视频游戏的系列文章的第6部分。以前的文章有:

  • 学习如何用Python编写一个简单的骰子游戏
  • 使用Pygame模块用Python构建游戏框架
  • 如何将玩家添加到Python游戏中
  • 使用Pygame移动游戏角色
  • 没有坏人,英雄是什么?如何在Python游戏中添加一个坏人

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种不同的积木,你在屏幕上把它们排成一行来创造地面,漂浮平台,以及你的游戏所需要的任何东西。有些人认为这是制作游戏的更简单的方法,因为你只需要制作(或下载)一小部分水平的资源就可以创建许多不同的水平。然而,这个代码需要更多的数学知识。

SuperTux,一款基于tiles的视频游戏。
SuperTux,一款基于tiles的视频游戏。

手绘法

另一种方法是使每一个资产都成为一个整体图像。如果你喜欢为你的游戏世界创建资产,这是一个很好的借口花时间在一个图形应用程序上,建立你的游戏世界的每一个部分。这个方法不需要太多的数学运算,因为所有的平台都是完整的对象,您可以告诉Python将它们放在屏幕上的哪个位置。

每种方法都有优点和缺点,而且必须使用的代码根据您选择的方法略有不同。我将涵盖两者,以便您可以在您的项目中使用其中的一个或另一个,甚至两者的混合。

水平地图

绘制游戏世界是级别设计和游戏编程的重要组成部分。它确实涉及到数学,但没有什么太难的,而且Python擅长数学,所以它可以帮助一些人。

你可能会发现先在纸上设计是有帮助的。找一张纸,画一个盒子来代表你的游戏窗口。在框中绘制平台,用其X和Y坐标以及预期的宽度和高度标记每个平台。只要保持数字的真实性,框中的实际位置就不必精确。例如,如果你的屏幕是720像素宽,那么你就不能在一个屏幕上安装8个100像素的平台。

当然,并不是所有的平台都必须放在一个屏幕大小的框中,因为你的游戏会随着玩家的浏览而滚动。所以继续把你的游戏世界画在第一个屏幕的右边直到关卡结束。

如果你想更精确一点,你可以用相纸。这在使用平铺设计游戏时特别有用,因为每个网格正方形可以表示一个平铺。

平面图的示例。
平面图的示例。

坐标

你可能在学校学过笛卡尔坐标系。你所学到的适用于Pygame,除了在Pygame中,游戏世界的坐标位于屏幕的左上角而不是中间,这可能是你在几何类中习惯的。

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 

启动游戏,并根据需要调整平台的位置。别担心你看不到屏幕外衍生出来的平台;你很快就会修复的。

以下是迄今为止的游戏图片和代码:

到目前为止我们的Pygame平台
到目前为止我们的Pygame平台
#!/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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 编码平台上的对象
  • 平台类型
    • 平铺法
      • 手绘法
      • 水平地图
        • 坐标
          • 图像大小
          • 平台块
          • 平铺平台
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档