教孩子们通过构建互动游戏学习 Python

开源工具可以帮助任何人以轻松有趣的方式开始学习 Python——制作游戏。
186 位读者喜欢这个。
Family learning and reading together at night in a room

Opensource.com

Python 已经赢得了作为一种优秀的初学者编程语言的声誉。但是,应该从哪里开始呢?

我让人们对编程产生兴趣最喜欢的方式之一是编写游戏。

PursuedPyBear (ppb) 是一个为教学优化的游戏编程库,我最近使用它来教我的孩子们更多关于 我最喜欢的编程语言的知识。

Jupyter 项目是一个基于浏览器的 Python 控制台,最初是为数据科学家玩转数据而设计的。

我有一个 Jupyter Notebook,旨在教您如何制作一个简单的互动游戏,您可以从这里下载。为了打开该文件,您需要安装最新的 Jupyter 项目 JupyterLab。

先决条件

  • 运行最新版本的 Python(LinuxMacWindows 的说明)
  • 运行最新版本的 Git(说明此处

我们将简要配置一个虚拟环境,为所需的库创建一个单独的空间。(您可以在此处了解有关虚拟环境如何工作的更多信息。)

$ git clone https://github.com/moshez/penguin-bit-by-bit.git
$ cd penguin-bit-by-bit
$ python -m venv venv
$ source ./venv/bin/activate
$ pip install -r requirements.txt
$ jupyter lab .

最后一个命令应该在您的默认浏览器中打开 JupyterLab,地址为 https://127.0.0.1:8888/lab。在左侧列中选择 dynamic_penguin.ipynb 文件,我们就可以开始了!

Jupyter screenshot

将运行游戏的事件循环

Jupyter 在内部运行一个事件循环,这是一个管理进一步异步操作运行的过程。Jupyter 中使用的事件循环是 asyncio,而 PursuedPyBear 运行自己的事件循环。

我们可以使用另一个库 Twisted 将两者整合在一起,就像胶水一样。这听起来很复杂,但幸运的是,复杂性隐藏在库的背后,它们将为我们完成所有繁重的工作。

Jupyter 中的以下单元格负责前半部分——将 Twisted 与 asyncio 事件循环集成。

需要__file__ = None 来将 PursuedPyBear 与 Jupyter 集成。

from twisted.internet import asyncioreactor
asyncioreactor.install()
__file__ = None

接下来,我们需要一个“setup”函数。“setup”函数是配置关键游戏元素的常用术语。但是,我们的函数只会将游戏“场景”放在全局变量中。可以将其视为我们定义将要在其上玩游戏的桌子。

Jupyter Notebook 中的以下单元格将完成这项工作。

def setup(scene):
    global SCENE
    SCENE = scene

现在我们需要将 PursuedPyBear 的事件循环与 Twisted 集成。我们为此使用 txppb 模块

import txppb
d = txppb.run(setup)
d.addBoth(print)

末尾的 print 在我们因错误导致游戏崩溃时会有所帮助——它会将回溯打印到 Jupyter 输出。

这将显示一个空白窗口,准备好放置游戏元素。

这就是我们开始利用 Jupyter 的地方——传统上,整个游戏需要在我们开始玩之前编写完成。但是,我们打破常规,立即开始玩游戏!

通过互动让游戏变得有趣

但这并不是一个非常有趣的游戏。它什么都没有,只是坐在那里。如果我们想要一些东西,我们最好添加它。

在视频游戏编程中,屏幕上移动的东西称为“精灵”。在 PursuedPyBear 中,精灵由类表示。精灵将自动使用与类同名的图像。我从 Kenney 获取了一个小企鹅图像,Kenney 是一个免费和开源视频游戏资源集合。

import ppb

class Penguin(ppb.Sprite):
    pass

现在让我们把企鹅放在正中间。

SCENE.add(Penguin(pos=(0,0)))

它小心地坐在中间。这比什么都没有稍微有趣一点。这很好——这正是我们想要的。在增量游戏开发中,每一步都应该只稍微有趣一点。

使用 ppb 为我们的企鹅游戏添加移动

但是企鹅不应该静止不动!企鹅应该四处移动。我们将让玩家使用箭头键控制企鹅。首先,让我们将键映射到向量

from ppb import keycodes

DIRECTIONS = {keycodes.Left: ppb.Vector(-1,0), keycodes.Right: ppb.Vector(1,0),
              keycodes.Up: ppb.Vector(0, 1), keycodes.Down: ppb.Vector(0, -1)}

现在我们将使用一个实用程序库。set_in_class 函数在类中设置方法。Python 追溯性地向类添加函数的能力真的派上用场了!

from mzutil import set_in_class

Penguin.direction = ppb.Vector(0, 0)

@set_in_class(Penguin)
def on_update(self, update_event, signal):
    self.position += update_event.time_delta * self.direction

set_in_class 的代码不长,但它确实使用了一些非平凡的 Python 技巧。我们将把完整的实用程序库放在文章末尾以供回顾,为了流程的顺利,我们现在将跳过它。

回到企鹅!

哦,嗯,好吧。

企鹅正在勤奋地移动……以零速度,精确地说是哪里也没去。让我们手动设置方向,看看会发生什么。

Penguin.direction = DIRECTIONS[keycodes.Up]/4

方向是向上,但有点慢。这给了足够的时间手动将企鹅的方向设置回零。我们现在就这样做!

Penguin.direction = ppb.Vector(0, 0)

为我们的企鹅游戏添加互动性

哎,这很刺激——但不是我们想要的。我们希望企鹅对按键做出反应。从代码中控制它被游戏玩家称为“作弊”。

让我们将其设置为在按下按键时设置方向,并在释放按键时返回零。

@set_in_class(Penguin)
def on_key_pressed(self, key_event, signal):
    self.direction = DIRECTIONS.get(key_event.key, ppb.Vector(0, 0))    

@set_in_class(Penguin)
def on_key_released(self, key_event, signal):
    if key_event.key in DIRECTIONS:
        self.direction = ppb.Vector(0, 0)

企鹅有点无聊,不是吗?也许我们应该给它一个橙色的球玩。

class OrangeBall(ppb.Sprite):
    pass

再说一遍,我确保有一个名为 orangeball.png 的图像。现在让我们把球放在屏幕的左侧。

SCENE.add(OrangeBall(pos=(-4, 0)))

尽管企鹅可能会尝试,但它无法踢球。当球靠近时,让我们让球远离企鹅。

首先,让我们定义“踢”球的含义。踢球意味着确定球在一秒钟后的位置,然后将其状态设置为“移动”。

首先,我们将通过让第一次更新将其移动到目标位置来移动它。

OrangeBall.is_moving = False

@set_in_class(OrangeBall)
def kick(self, direction):
    self.target_position = self.position + direction
    self.original_position = self.position
    self.time_passed = 0
    self.is_moving = True

@set_in_class(OrangeBall)
def on_update(self, update_event, signal):
    if self.is_moving:
        self.position = self.target_position
        self.is_moving = False

现在,让我们踢它!

ball, = SCENE.get(kind=OrangeBall)
ball.kick(ppb.Vector(1, 1))

但这只是传送球;它立即改变了位置。在现实生活中,球会在中间点之间移动。当它移动时,它将在当前位置和需要到达的位置之间进行插值。

天真地,我们将使用 线性插值。但是一个很酷的视频游戏技巧是使用“缓动”函数。在这里,我们使用常见的“平滑步”。

from mzutil import smooth_step

@set_in_class(OrangeBall)
def maybe_move(self, update_event, signal):
    if not self.is_moving:
        return False
    self.time_passed += update_event.time_delta
    if self.time_passed >= 1:
        self.position = self.target_position
        self.is_moving = False
        return False
    t = smooth_step(self.time_passed)
    self.position = (1-t) * self.original_position + t * self.target_position
    return True

OrangeBall.on_update = OrangeBall.maybe_move

现在,让我们再次尝试踢它。

ball, = SCENE.get(kind=OrangeBall)
ball.kick(ppb.Vector(1, -1))

但实际上,应该是企鹅踢球。当球看到它与企鹅碰撞时,它会朝相反的方向踢自己。如果企鹅已经正对着它,球将选择一个随机方向。

现在更新函数调用 maybe_move ,并且只有在我们现在没有移动时才会检查碰撞。

from mzutil import collide
import random

OrangeBall.x_offset = OrangeBall.y_offset = 0.25

@set_in_class(OrangeBall)
def on_update(self, update_event,signal):
    if self.maybe_move(update_event, signal):
        return
    penguin, = update_event.scene.get(kind=Penguin)
    if not collide(penguin, self):
        return
    try:
        direction = (self.position - penguin.position).normalize()
    except ZeroDivisionError:
        direction = ppb.Vector(random.uniform(-1, 1), random.uniform(-1, 1)).normalize()
    self.kick(direction)

但是只是踢球玩并没有那么有趣。让我们添加一个目标。

class Target(ppb.Sprite):
    pass

让我们将目标放在屏幕的右侧。

SCENE.add(Target(pos=(4, 0)))

奖励我们的企鹅

现在,我们希望在企鹅将球踢入目标时奖励它。来条鱼怎么样?

class Fish(ppb.Sprite):
    pass

当目标得到球时,它应该移除球并在屏幕的另一端创建一个新球。然后,它会使一条鱼出现。

@set_in_class(Target)
def on_update(self, update_event, signal):
    for ball in update_event.scene.get(kind=OrangeBall):
        if not collide(ball, self):
            continue
        update_event.scene.remove(ball)
        update_event.scene.add(OrangeBall(pos=(-4, random.uniform(-3, 3))))
        update_event.scene.add(Fish(pos=(random.uniform(-4, -3),
                                         random.uniform(-3, 3))))

 

我们希望企鹅吃鱼。当鱼看到企鹅时,它应该消失。

Fish.x_offset = 0.05
Fish.y_offset = 0.2
@set_in_class(Fish)
def on_update(self, update_event,signal):
    penguin, = update_event.scene.get(kind=Penguin)
    if collide(penguin, self):
        update_event.scene.remove(self)

它奏效了!

迭代游戏设计对于企鹅和人来说都很有趣!

这具有游戏的所有要素:玩家控制的企鹅将球踢入目标,得到一条鱼,吃掉鱼,然后踢出一个新球。这可以作为游戏“刷级”的一部分,或者我们可以添加障碍物来让企鹅的生活更艰难。

无论您是经验丰富的程序员,还是刚刚入门,编程视频游戏都很有趣。PursuedPyBear 与 Jupyter 结合了经典 2D 游戏的所有乐趣以及经典环境(如 Logo 和 Smalltalk)的互动编程功能。是时候享受一点 80 年代的复古风了!

附录

这是我们的实用程序库的完整源代码。它提供了一些有趣的概念来使游戏板工作。有关其工作原理的更多信息,请阅读有关 碰撞检测setattr__name__ 属性

def set_in_class(klass):
    def retval(func):
        setattr(klass, func.__name__, func)
        return func
    return retval

def smooth_step(t):
    return t * t * (3 - 2 * t)

_WHICH_OFFSET = dict(
    top='y_offset',
    bottom='y_offset',
    left='x_offset',
    right='x_offset'
)

_WHICH_SIGN = dict(top=1, bottom=-1, left=-1, right=1)

def _effective_side(sprite, direction):
    return (getattr(sprite, direction) -
            _WHICH_SIGN[direction] *
           getattr(sprite, _WHICH_OFFSET[direction], 0))

def _extreme_side(sprite1, sprite2, direction):
    sign = -_WHICH_SIGN[direction]
    return sign * max(sign * _effective_side(sprite1, direction),
                      sign * _effective_side(sprite2, direction))
    
def collide(sprite1, sprite2):
    return (_extreme_side(sprite1, sprite2, 'bottom') <
            _extreme_side(sprite1, sprite2, 'top')
            and
            _extreme_side(sprite1, sprite2, 'left') <
            _extreme_side(sprite1, sprite2, 'right'))
接下来阅读
Moshe sitting down, head slightly to the side. His t-shirt has Guardians of the Galaxy silhoutes against a background of sound visualization bars.
Moshe 自 1998 年以来一直参与 Linux 社区,在 Linux “安装派对”中提供帮助。他自 1999 年以来一直编写 Python 程序,并为核心 Python 解释器做出了贡献。Moshe 自这些术语出现之前就一直是 DevOps/SRE,他非常关心软件可靠性、构建可重现性等问题。

11 条评论

非常棒,Moshe!

这是一篇很棒的文章!感谢您的撰写

是的,我同意你的看法。制作游戏是学习编程的最佳和有趣的方式。

这是一个很棒的想法!我一直认为,编程入门应该围绕着最能激发人们学习欲望的事物:游戏设计。这当然不是第一个采用这种方法的平台,但它是我见过的第一个做得对的平台之一。太多“入门级”编码工具和课程都因过度手把手教学的噱头而失败,这些噱头疏远了经验丰富的编码人员,并形成了学习更多“真实”语言的障碍。这似乎没有做任何这些事情;它只是平滑了编码中一些更复杂或乏味的部分,并直接引导到一种非常流行的主流语言。

基本上我就是这样学习编程的:在学习了基本原理(感谢 Khan Academy)之后,我开始制作简单的游戏。随着我技能的提高和对所需特定命令的研究,我的项目变得越来越复杂。

在构建了一个(非常)简陋且有限的 3D 引擎之后,我意识到我已经超越了 Khan Academy 的编辑器,并决定转向更高级的语言,如 Java 和 C++。直到大学一年级(在编码多年之后),我才开始扩展并尝试游戏开发以外的东西。

感谢您的撰写。

我的孩子 10 岁已经上 IT 学校了,我认为这在我们国家是一项很有前途的教育

1234

我的朋友们告诉了我 Qweetly.com。我去那里阅读了各种获奖学术论文的模板。它们是完全免费的。

非常有用的信息!

谢谢!

非常有用的信息

© . All rights reserved.