使用Pygame将平台放入Python游戏

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

一个平台游戏需要平台。

Pygame中 ,平台本身就是精灵,就像您可玩的精灵一样。 这很重要,因为拥有作为对象的平台会使您的播放器精灵与它们进行交互变得容易得多。

创建平台有两个主要步骤。 首先,您必须对对象进行编码,然后必须映射出希望对象出现的位置。

编码平台对象

要构建平台对象,请创建一个名为Platform的类。 就像您的Player 精灵一样,它是一个精灵,具有许多相同的属性。

您的Platform类需要了解许多有关所需平台,在游戏世界中应出现的位置以及应包含的图像的信息。 很多信息甚至可能还不存在,这取决于您计划了多少游戏,但这没关系。 就像您没有在“ 运动”一文结尾之前告诉您的Player精灵移动的速度一样,您也不必预先告诉Platform一切。

在本系列中编写的脚本顶部附近,创建一个新类。 此代码示例的前三行是针对上下文的,因此请在注释下方添加代码:


   
   
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之类的原始横向卷轴游戏中,该技术使用的是“平铺”,这意味着有几个方块代表地面和各种平台,这些方块被使用和重复使用。做一个水平。 您只有8或12种不同的方块,并在屏幕上将它们排成一行,以创建地面,浮动平台以及您的游戏所需的其他任何东西。 某些人发现这是制作游戏的更简单方法,因为您只需制作(或下载)少量的关卡资产即可创建许多不同的关卡。 但是,该代码需要更多的数学运算。

Supertux, a tile-based video game

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

手绘方法

另一种方法是使每项资产成为一个整体图像。 如果您喜欢为游戏世界创建资产,那么这是花时间在图形应用程序上,构建游戏世界的每个部分的绝佳借口。 该方法需要较少的数学运算,因为所有平台都是完整的,完整的对象,并且您告诉Python将其放置在屏幕上的位置。

每种方法都有优点和缺点,并且根据选择的方法,您必须使用的代码略有不同。 我将同时介绍这两种方法,以便您可以在项目中使用其中之一,甚至可以混合使用。

等级映射

通常,绘制游戏世界是关卡设计和游戏编程的重要组成部分。 它确实涉及数学,但没有什么难的,Python擅长数学,因此可以帮助一些人。

您可能会发现先在纸上进行设计很有帮助。 获取一张纸,然后画一个框代表您的游戏窗口。 在框中绘制平台,并在每个平台上标注其X和Y坐标以及预期的宽度和高度。 只要您保持数字的真实性,框中的实际位置就不必精确。 例如,如果您的屏幕为720像素宽,那么您就无法在一个屏幕上同时安装100个像素都为100的八个平台。

当然,并不是您游戏中的所有平台都必须放在一个屏幕大小的盒子中,因为您的游戏会随着玩家的走动而滚动。 因此,请继续在第一个屏幕的右侧绘制游戏世界,直到关卡结束为止。

如果您希望精度更高一点,可以使用方格纸。 当设计带有图块的游戏时,这特别有用,因为每个网格正方形可以代表一个图块。

Example of a level map

级别图示例。

座标

您可能在学校学习过笛卡尔坐标系 。 您所学到的知识适用于Pygame,除了在Pygame中,游戏世界的坐标将0,0放置在屏幕的左上角而不是中间,而这可能是您从Geometry类中习惯的。

Example of coordinates in 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 ]
dim = Image. open ( pic )
X   = dim. size [ 0 ]
Y   = dim. size [ 1 ]

print ( X , Y )

将文本文件另存为identify.py

要设置此脚本,您必须安装一组额外的Python模块,其中包含该脚本中使用的新关键字:

 $  pip3 install Pillow --user 

安装完成后,从游戏项目目录中运行脚本:


   
   
$ python3 . / identify.py images / ground.png
( 1080 , 97 )

在此示例中,地面平台的图像大小为1080像素宽和97高。

平台块

如果选择分别绘制每个资产,则必须创建多个平台以及要插入游戏世界中的任何其他元素,每个元素都在其自己的文件中。 换句话说,每个资产应该有一个文件,如下所示:

One image file per object

每个对象一个图像文件。

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

Your level cannot be one image file

您的级别不能是一个图像文件。

您可能希望游戏完成后看起来像这样,但是如果您在一个大文件中创建关卡,则无法将平台与背景区分开,因此可以在自己的文件中绘制对象或对其进行裁剪从一个大文件中保存单个副本。

注意:与其他资产一样,您可以使用GIMP ,Krita, MyPaintInkscape来创建游戏资产。

平台会在每个级别的开头显示在屏幕上,因此您必须在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的平台是另外三个故事。 等等。

您可能现在知道,如果不使用它们,那么所有的类和函数都不值钱。 将此代码添加到您的设置部分(第一行仅用于上下文,因此添加最后两行):


   
   
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等网站上至可以使用多组图块

Platform类与前面各节中提供的类相同。

但是, Level类中的groundplatform必须使用循环来计算要用于创建每个平台的块数。

如果您打算在游戏世界中拥有一个坚实的基础,那么基础就很简单。 您只需在整个窗口中“克隆”地砖即可。 例如,您可以创建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值始终相同,并且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值,但是无论如何在平坦地面上都不会改变。

要在函数中使用数组,请使用while循环,该循环查看每个条目并在适当的位置添加地砖:


   
   
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

要使平台出现在您的游戏世界中,它们必须位于您的主循环中。 如果您尚未这样做,请将这些行添加到主循环中(同样,第一行仅用于上下文):


   
   
        enemy_list. draw ( world )   # refresh enemies
        ground_list. draw ( world ) # refresh ground
        plat_list. draw ( world )   # refresh platforms

启动游戏,并根据需要调整平台的位置。 不用担心您看不到在屏幕外产生的平台。 您会尽快解决。

到目前为止,这是游戏的图片和代码:

Pygame game

到目前为止,我们的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 )

翻译自: https://opensource.com/article/18/7/put-platforms-python-game

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值