使用 Pygame 移动你的游戏角色

在本系列的第四部分中,学习如何编写控制代码以移动游戏角色。
370 位读者喜欢这个。
Python game screenshot

OpenGameArt.org

在本系列的第一篇文章中,我解释了如何使用 Python 创建一个简单的基于文本的骰子游戏。在第二部分中,你开始从头构建一个游戏,从创建游戏环境开始。在第三部分中,你创建了一个玩家精灵并使其在你的(相当空旷的)游戏世界中生成。你可能已经注意到,当你的角色无法移动时,游戏就没什么乐趣了。在本文中,你将使用 Pygame 添加键盘控制,以便你可以控制角色的移动。

Pygame 中有一些函数可以添加其他类型的控制(例如鼠标或游戏控制器),但是由于如果你正在键入 Python 代码,你肯定有键盘,所以本文涵盖了键盘控制。一旦你理解了键盘控制,你就可以自行探索其他选项。

你在本系列的第二篇文章中创建了一个退出游戏的按键,其原理与移动相同。但是,让你的角色移动稍微复杂一些。

从简单的部分开始:设置控制器按键。

设置用于控制玩家精灵的按键

在 IDLE、PyCharm 或文本编辑器中打开你的 Python 游戏脚本。

由于游戏必须不断“监听”键盘事件,因此你将编写需要持续运行的代码。你能想出将需要持续运行的代码放在游戏持续时间内的什么位置吗?

如果你的答案是“在主循环中”,那么你是正确的!记住,除非代码在循环中,否则它最多只运行一次——如果它隐藏在永远不会使用的类或函数中,则可能根本不运行。

为了让 Python 监视传入的按键,请将此代码添加到主循环中。目前还没有代码让任何事情发生,因此请使用 print 语句来表示成功。这是一种常见的调试技术。

while main:
    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'):
                print('left')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right')
            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'):
                print('left stop')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right stop')
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False    

有些人喜欢使用键盘字符 W、A、S 和 D 来控制玩家角色,而另一些人则喜欢使用方向键。请务必包含两种选项。

注意: 在编程时,考虑所有用户至关重要。如果你编写的代码仅适用于你,则很可能你将是唯一使用你的应用程序的人。更重要的是,如果你寻求一份编写代码赚钱的工作,则你应被期望编写适用于所有人的代码。为你的用户提供选择,例如使用方向键或 WASD 的选项(这称为可访问性),是优秀程序员的标志。

使用 Python 启动你的游戏,并在按下向右、向左和向上箭头或 A、D 和 W 键时,观察控制台窗口的输出。

$ python ./your-name_game.py
  left
  left stop
  right
  right stop
  jump

这证实 Pygame 正确检测到你的按键。现在是时候开始制作精灵移动的艰苦工作了。

编写玩家移动函数

为了使你的精灵移动,你必须为你的精灵创建一个表示移动的属性。当你的精灵不移动时,此变量设置为 0

如果你正在为你的精灵制作动画,或者如果你将来决定制作动画,你也必须跟踪帧,以便行走循环保持在轨道上。

在 Player 类中创建这些变量。前两行用于上下文(如果你一直在跟进,你已经拥有它们),因此仅添加最后三行

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0 # move along X
        self.movey = 0 # move along Y
        self.frame = 0 # count frames

设置好这些变量后,就该编写精灵的移动代码了。

玩家精灵不需要一直响应控制,因为有时它没有被告知要移动。因此,控制精灵的代码只是玩家精灵可以做的所有事情中的一小部分。当你想让 Python 中的对象执行独立于其代码其余部分的操作时,请将你的新代码放在函数中。Python 函数以关键字 def 开头,def 代表 define

在你的 Player 类中创建一个函数,以将某个数量的像素添加到精灵在屏幕上的位置。暂时不用担心要添加多少像素;这将在以后的代码中决定。

    def control(self,x,y):
        """
        control player movement
        """
        self.movex += x
        self.movey += y

要在 Pygame 中移动精灵,你必须告诉 Python 在其新位置重绘精灵——以及新位置在哪里。

由于 Player 精灵并非始终在移动,因此请在 Player 类中创建专门的函数来完成这些更新。在你之前创建的 control 函数之后添加此函数。

为了使精灵看起来像在行走(或飞行,或你的精灵应该做的任何事情),你需要在其位置更改时,即在按下相应的键时,更改其在屏幕上的位置。为了使其在屏幕上移动,请重新定义其位置,由 self.rect.xself.rect.y 属性指定,即其当前位置加上应用的 movexmovey 的量。(移动所需的像素数稍后设置。)

    def update(self):
        """
        Update sprite position
        """
        self.rect.x = self.rect.x + self.movex        

对 Y 位置执行相同的操作

        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 = self.images[self.frame//ani]

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

通过设置变量来告知代码要将多少像素添加到你的精灵位置,然后在触发 Player 精灵的函数时使用该变量。

首先,在你的设置部分中创建变量。在此代码中,前两行用于上下文,因此只需将第三行添加到你的脚本中

player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10  # how many pixels to move

现在你有了适当的函数和变量,请使用你的按键来触发该函数并将变量发送到你的精灵。

通过将主循环中的 print 语句替换为 Player 精灵的名称 (player)、函数 (.control) 以及你希望玩家精灵在每次循环中沿 X 轴和 Y 轴移动的步数来完成此操作。

        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

记住,steps 是一个变量,表示你的精灵在按下按键时移动的像素数。如果你在按下 D 或向右箭头时向玩家精灵的位置添加 10 个像素,那么当你停止按下该键时,你必须减去 10 (-steps) 以将精灵的动量恢复为 0。

现在尝试你的游戏。警告:它不会如你所愿。

更新精灵图形

为什么你的精灵还不能移动?因为主循环没有调用 update 函数。

将代码添加到你的主循环中,以告知 Python 更新你的玩家精灵的位置。添加带有注释的行

    player.update()  # update player position
    player_list.draw(world)
    pygame.display.flip()
    clock.tick(fps)

再次启动你的游戏,见证你的玩家精灵按照你的意愿在屏幕上移动。目前还没有垂直移动,因为这些函数将由重力控制,但那是另一篇文章的课程了。

移动有效,但仍然有一个小问题:你的英雄图形不会转向它行走的方向。换句话说,如果你将你的英雄设计为面向右侧,那么当你按下向左箭头键时,它看起来像在向后走。通常,你会期望你的英雄在向左走时向左转,并在向右走时再次向右转。

翻转你的精灵

你可以使用 Pygame 的 transform 函数翻转图形。与你一直用于此游戏的所有其他函数一样,这很多复杂的代码和数学都被提炼成一个易于使用的 Python 关键字。这是框架帮助你编写代码的一个很好的例子。你无需学习在屏幕上绘制像素的基本原理,而可以让 Pygame 完成所有工作,只需调用一个已经存在的函数即可。

你只需要在图形默认面向方向的相反方向行走时才需要对实例进行变换。我的图形面向右侧,因此我将变换应用于左侧代码块。根据Pygame 文档pygame.transform.flip 函数接受三个参数:要翻转的内容、是否水平翻转以及是否垂直翻转。在这种情况下,它们是图形(你已经在现有代码中定义了),水平为 True,垂直为 False。

更新你的代码

        # 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)

请注意,变换函数已插入到你的现有代码中。变量 self.image 仍然被定义为来自你的英雄图像列表的图像,但它被“包装”在变换函数中。

现在尝试你的代码,并观看你的英雄每次在你指向不同方向时都进行 180 度转弯。

今天的课程就到这里。在下一篇文章之前,你可以尝试探索控制英雄的其他方法。例如,如果你可以使用操纵杆,请尝试阅读 Pygame 文档中的操纵杆模块,看看你是否可以让你的精灵以这种方式移动。或者,看看你是否可以让鼠标与你的精灵交互。

最重要的是,玩得开心!

本教程中使用的所有代码

为了方便你参考,以下是到目前为止本系列文章中使用的所有代码。

#!/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/>.
from typing import Tuple

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.images = []
        for i in range(1, 5):
            img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert()
            img.convert_alpha()  # optimise alpha
            img.set_colorkey(ALPHA)  # set 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]


'''
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 = 0  # go to y
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10

'''
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)
    pygame.display.flip()
    clock.tick(fps)

你已经走了很远,学到了很多,但还有很多事情要做。在接下来的几篇文章中,你将添加敌方精灵、模拟重力以及更多内容。同时,练习 Python!

标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算机行业工作,通常同时进行。
User profile image.
Jess Weichler 是一位数字艺术家,使用开源软件和硬件在 CyanideCupcake.com 上创作数字和物理世界的作品。

5 条评论

我不确定我是否做错了什么,但是以下行不起作用

self.image = self.images[self.frame//ani+4]

除非我将其更改为

self.image = self.images[self.frame//ani]

感谢你的错误报告,Jay。你使用的是动画精灵还是静态图像?如果你没有使用动画精灵(带有行走循环的精灵),则无需循环浏览图像。如果你正在使用动画图像,那么是的,这需要修复。

如果你愿意,你可以尝试将你的代码粘贴到此处:https://paste.fedoraproject.org/

将链接发回给我,我会看一下。

回复 作者 jlacroix

请不要写

while main == True

这是多余的。main 是一个布尔变量,所以

while main

就足够了。

当我读到它时,我的眼睛都流血了。

很高兴能深入了解该语言的细节。谢谢你。

但是,我关于如何构建这些课程的论点是,“while Main:” 之类的东西比 “while Main is True:” 或 “== True” 或其他任何东西“更正确”,但它更难预先教授。教授编程语言意味着用大量信息冲击读者和学生,信息越一致越好。

根据我的经验(从大约 5 年的教学经验中获得,学生年龄从 11 岁到 64 岁不等),教授某人如何比较值,然后告诉他们哦,顺便说一句,不进行比较与布尔比较相同,这令人困惑。

这不是任何学生都无法理解的概念,但在培训的早期,以我非常谦虚的意见来看,这不是坚持超级优化或风格标准的时候。

有一个有效的反驳论点,即从一开始就教授最符合标准的方法是最好的。但是,在我的现实世界经验中,它分散了课程的注意力,实际上混淆了你试图教授的内容。

我对教育有很多有争议的想法。总有一天我应该写一系列文章。

说了这么多,抱歉让你的眼睛流血。 :-) 我真的感同身受,真的。

回复 作者 David Aponte (未验证)

嗨,我只想说谢谢!
我刚为我的马戏团学到了一些新技巧。^^

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.