在本系列的前几篇文章中(参见第 1 部分、第 2 部分、第 3 部分和第 4 部分),你学习了如何使用 Pygame 和 Python 在一个尚未开发的视频游戏世界中生成一个可玩的主角角色。但是,没有反派,英雄算什么呢?
如果你没有敌人,那将会是一个非常无聊的游戏,所以在本文中,你将为你的游戏添加一个敌人,并构建一个用于构建关卡的框架。
当玩家精灵还有很多功能尚未完全实现时,就跳到敌人似乎很奇怪,但是你已经学到了很多东西,并且创建反派与创建玩家精灵非常相似。所以放松,运用你已经掌握的知识,看看制造一些麻烦需要什么。
对于本练习,你需要一个敌人精灵。如果你还没有下载,可以在 OpenGameArt.org 上找到 Creative Commons 资产。
创建敌人精灵
无论你是否意识到,你已经知道如何实现敌人。该过程类似于创建玩家精灵
- 创建一个类,以便可以生成敌人。
- 为敌人创建一个
update
函数,并在主循环中更新敌人。 - 创建一个
move
函数,以便你的敌人可以四处游荡。
从类开始。从概念上讲,它与你的 Player 类基本相同。你设置一个或一系列图像,并设置精灵的起始位置。
在继续之前,请确保你已将敌人图形放置在游戏项目的 images
目录中(与放置玩家图像的目录相同)。在本文的示例代码中,敌人图形名为 enemy.png
。
当所有活着的东西都是动画时,游戏看起来会好很多。动画敌人精灵的方式与动画玩家精灵的方式相同。但是,现在,保持简单,并使用非动画精灵。
在代码的 objects
部分的顶部,创建一个名为 Enemy 的类,代码如下
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
如果你想为你的敌人制作动画,请按照相同的方式为你的人物角色制作动画。
生成敌人
你可以使该类不仅可以生成一个敌人,而且可以通过允许你告诉该类要使用哪个图像作为精灵以及你希望精灵在世界中的哪个位置出现,从而使该类变得有用。这意味着你可以使用相同的敌人类在游戏世界的任何位置生成任意数量的敌人精灵。你所要做的就是调用该类,并告诉它要使用哪个图像,以及所需生成点的 X 和 Y 坐标。
就像生成玩家精灵时一样,添加代码以在脚本的 setup
部分中指定生成点
enemy = Enemy(300,0,'enemy.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
在该示例代码中,你通过在 X 轴上 300 像素和 Y 轴上 0 像素处创建一个新对象(称为 enemy
)来生成敌人。在 Y 轴上 0 处生成敌人意味着它的左上角位于 0 处,图形本身从该点向下延伸。你可能需要调整这些数字,或者你的英雄精灵的数字,具体取决于你的精灵有多大,但尝试使其生成在你可以用玩家精灵到达的位置(考虑到你当前的游戏缺少垂直移动)。最后,我将我的敌人放置在 Y 轴上的 0 像素处,将我的英雄放置在 30 像素处,以使它们都出现在同一平面上。自己尝试生成点,请记住,Y 轴数字越大,屏幕上的位置越低。
你的英雄图形在其类中“硬编码”了一个图像,因为只有一个英雄,但是你可能希望为每个敌人使用不同的图形,因此图像文件是可以在精灵创建时定义的内容。用于此敌人精灵的图像是 enemy.png
。
在屏幕上绘制精灵
如果你现在启动游戏,它会运行,但你不会看到敌人。你可能还记得创建玩家精灵时也遇到了同样的问题。你还记得如何解决它吗?
要使精灵出现在屏幕上,你必须将它们添加到主循环中。如果某些东西不在你的主循环中,那么它只会发生一次,并且只持续一毫秒。如果你希望某些东西在你的游戏中持续存在,则必须在主循环中发生。
你必须添加代码以在屏幕上绘制敌人群组(称为 enemy_list
)中的所有敌人,该群组是在你的设置部分中建立的。此示例代码中的中间行是你需要添加的新行
player_list.draw(world)
enemy_list.draw(world) # refresh enemies
pygame.display.flip()
现在,你只有一个敌人,但是如果需要,稍后可以添加更多。只要你将敌人添加到敌人群组,它就会在主循环期间绘制到屏幕上。
启动你的游戏。你的敌人会出现在游戏世界的任何 X 和 Y 坐标处。
第一关
你的游戏还处于起步阶段,但你可能最终会想要添加一系列关卡。在编程时提前计划很重要,这样你的游戏才能在你学习更多关于编程的知识时不断发展。即使你甚至还没有一个完整的关卡,你也应该像计划拥有多个关卡一样进行编码。
想想“关卡”是什么。你如何知道你在游戏中处于某个关卡?
你可以将关卡视为项目集合。在平台游戏中,例如你在此处构建的平台游戏,关卡由平台的特定排列、敌人和战利品的放置等等组成。你可以构建一个类,围绕你的玩家构建一个关卡。最终,当你创建多个关卡时,你可以使用此类在玩家达到特定目标时生成下一个关卡。
将你编写的用于创建敌人及其群组的代码移动到一个新函数中,该函数与每个新关卡一起调用。它需要进行一些修改,以便每次创建新关卡时,都可以创建和放置多个敌人
class Level():
def bad(lvl,eloc):
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'enemy.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
return
语句确保当你使用 Level.bad
函数时,你会得到一个包含你定义的每个敌人的 enemy_list
。
由于你现在将敌人创建为每个关卡的一部分,因此你的 setup
部分也需要更改。你必须定义敌人将生成的位置以及它属于哪个级别,而不是创建敌人。
eloc = []
eloc = [300,0]
enemy_list = Level.bad( 1, eloc )
再次运行游戏以确认你的关卡正在正确生成。你应该像往常一样看到你的玩家,以及你在本章中添加的敌人。
击中敌人
如果敌人对玩家没有影响,那它就不像敌人。当玩家与敌人碰撞时造成伤害是很常见的。
由于你可能想要跟踪玩家的生命值,因此碰撞检查发生在 Player 类中,而不是在 Enemy 类中。如果你愿意,也可以跟踪敌人的生命值。逻辑和代码几乎相同,但是,现在,只需跟踪玩家的生命值即可。
要跟踪玩家生命值,你必须首先为玩家生命值建立一个变量。此代码示例中的第一行用于上下文,因此将第二行添加到你的 Player 类
self.frame = 0
self.health = 10
在你的 Player 类的 update
函数中,添加此代码块
hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
for enemy in hit_list:
self.health -= 1
print(self.health)
此代码使用 Pygame 函数 sprite.spritecollide
建立碰撞检测器,称为 enemy_hit
。每当其父精灵(玩家精灵,创建此检测器的地方)的碰撞箱接触 enemy_list
中任何精灵的碰撞箱时,此碰撞检测器都会发送信号。当收到此类信号时,将触发 for
循环,并从玩家的生命值中扣除一点。
由于此代码出现在你的 player 类的 update
函数中,并且 update
在你的主循环中调用,因此 Pygame 每时钟周期检查一次此碰撞。
移动敌人
如果你想要例如尖刺或陷阱之类的会伤害玩家的敌人,那么静止不动的敌人很有用,但是如果敌人稍微移动一下,游戏会更具挑战性。
与玩家精灵不同,敌人精灵不受用户控制。它的移动必须自动化。
最终,你的游戏世界将滚动,那么当游戏世界本身正在移动时,你如何让敌人游戏角色在游戏世界中来回移动?
你告诉你的敌人精灵例如向右走 10 步,然后向左走 10 步。敌人精灵无法计数,因此你必须创建一个变量来跟踪你的敌人走了多少步 并对你的敌人进行编程,使其根据你的计数变量的值向右或向左移动。
首先,在你的 Enemy 类中创建计数器变量。在此代码示例中添加最后一行
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.counter = 0 # counter variable
接下来,在你的 Enemy 类中创建一个 move
函数。使用 if-else 循环创建所谓的无限循环
- 如果计数器上的数字在 0 到 100 之间的任何数字,则向右移动。
- 如果计数器上的数字在 100 到 200 之间的任何数字,则向左移动。
- 如果计数器大于 200,则将计数器重置回 0。
无限循环没有结尾;它永远循环,因为循环中没有任何内容是不真实的。在这种情况下,计数器始终在 0 到 100 或 100 到 200 之间,因此敌人精灵永远从右向左和从左向右行走。
你用于敌人将在任一方向上移动多远的实际数字取决于你的屏幕尺寸,并且最终可能取决于你的敌人行走的平台的尺寸。从小处开始,随着你习惯结果而逐步增加。首先尝试这个
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
输入此代码后,PyCharm 将提供简化“链式比较”。你可以接受其建议来优化你的代码,并学习一些高级 Python 语法。你也可以安全地忽略 PyCharm。无论哪种方式,代码都可以工作。
你可以根据需要调整距离和速度。
问题是:如果你现在启动游戏,此代码是否有效?
当然无效!你知道为什么:你必须在主循环中调用 move
函数。
此示例代码中的第一行用于上下文,因此添加最后两行
enemy_list.draw(world) #refresh enemy
for e in enemy_list:
e.move()
启动你的游戏,看看当你击中敌人时会发生什么。你可能必须调整精灵的生成位置,以便你的玩家和你的敌人精灵可以碰撞。当它们碰撞时,查看 IDLE 或 PyCharm 的控制台,以查看正在扣除的生命值点数。

你可能会注意到,每当你的玩家和敌人接触时,都会扣除生命值。这是一个问题,但这是一个你稍后会解决的问题,在你对 Python 有更多实践之后。
现在,尝试添加更多敌人。记住将每个敌人添加到 enemy_list
。作为练习,看看你是否可以考虑如何更改不同敌人精灵的移动距离。
到目前为止的代码
供你参考,这是到目前为止的代码
#!/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
'''
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 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
'''
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 )
'''
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)
for e in enemy_list:
e.move()
pygame.display.flip()
clock.tick(fps)
4 条评论