使用 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 开头,defdefine(定义)的缩写。

在你的 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 文档中的 joystick 模块,看看你是否可以使你的精灵以那种方式移动。或者,看看你是否可以让鼠标与你的精灵互动。

最重要的是,玩得开心!

本教程中使用的所有代码

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

#!/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 (未验证)

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

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.