这是有关使用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种不同的方块,并在屏幕上将它们排成一行,以创建地面,浮动平台以及您的游戏所需的其他任何东西。 某些人发现这是制作游戏的更简单方法,因为您只需制作(或下载)少量的关卡资产即可创建许多不同的关卡。 但是,该代码需要更多的数学运算。
另一种方法是使每项资产成为一个整体图像。 如果您喜欢为游戏世界创建资产,那么这是花时间在图形应用程序上,构建游戏世界的每个部分的绝佳借口。 该方法需要较少的数学运算,因为所有平台都是完整的,完整的对象,并且您告诉Python将其放置在屏幕上的位置。
每种方法都有优点和缺点,并且根据选择的方法,您必须使用的代码略有不同。 我将同时介绍这两种方法,以便您可以在项目中使用其中之一,甚至可以混合使用。
通常,绘制游戏世界是关卡设计和游戏编程的重要组成部分。 它确实涉及数学,但没有什么难的,Python擅长数学,因此可以帮助一些人。
您可能会发现先在纸上进行设计很有帮助。 获取一张纸,然后画一个框代表您的游戏窗口。 在框中绘制平台,并在每个平台上标注其X和Y坐标以及预期的宽度和高度。 只要您保持数字的真实性,框中的实际位置就不必精确。 例如,如果您的屏幕为720像素宽,那么您就无法在一个屏幕上同时安装100个像素都为100的八个平台。
当然,并不是您游戏中的所有平台都必须放在一个屏幕大小的盒子中,因为您的游戏会随着玩家的走动而滚动。 因此,请继续在第一个屏幕的右侧绘制游戏世界,直到关卡结束为止。
如果您希望精度更高一点,可以使用方格纸。 当设计带有图块的游戏时,这特别有用,因为每个网格正方形可以代表一个图块。
您可能在学校学习过笛卡尔坐标系 。 您所学到的知识适用于Pygame,除了在Pygame中,游戏世界的坐标将0,0
放置在屏幕的左上角而不是中间,而这可能是您从Geometry类中习惯的。
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高。
如果选择分别绘制每个资产,则必须创建多个平台以及要插入游戏世界中的任何其他元素,每个元素都在其自己的文件中。 换句话说,每个资产应该有一个文件,如下所示:
您可以根据需要多次重复使用每个平台,只需确保每个文件仅包含一个平台即可。 您不能使用包含所有内容的文件,如下所示:
您可能希望游戏完成后看起来像这样,但是如果您在一个大文件中创建关卡,则无法将平台与背景区分开,因此可以在自己的文件中绘制对象或对其进行裁剪从一个大文件中保存单个副本。
注意:与其他资产一样,您可以使用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的平台是另外三个故事。 等等。
您可能现在知道,如果不使用它们,那么所有的类和函数都不值钱。 将此代码添加到您的设置部分(第一行仅用于上下文,因此添加最后两行):
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
类中的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值始终相同,并且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
启动游戏,并根据需要调整平台的位置。 不用担心您看不到在屏幕外产生的平台。 您会尽快解决。
到目前为止,这是游戏的图片和代码:
#!/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