python 构建
Python已作为一种出色的初学者编程语言而享有盛誉。 但是,从哪里开始呢?
我最喜欢的使人们对编程感兴趣的方法之一就是编写游戏。
PursuedPyBear (ppb)是为教学而优化的游戏编程库,我最近使用它来向孩子们传授更多有关我最喜欢的编程语言的知识 。
Jupyter项目是一个基于浏览器的Python控制台,最初是为数据科学家设计的,用于处理数据。
我有一个Jupyter笔记本,旨在教您如何制作简单的互动游戏,您可以从此处下载。 为了打开文件,您将需要安装最新的Jupyter项目JupyterLab。
先决条件:
- 运行最新版本的Python( Linux , Mac和Windows的说明 )
- 运行最新版本的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 .
最后一条命令应在默认浏览器中打开http:// localhost:8888 / lab的 JupyterLab。 在左侧栏中选择dynamic_penguin.ipynb文件,我们就可以开始!
将运行游戏的事件循环
asyncio和PursuedPyBear运行自己的事件循环。我们可以使用另一个库Twisted集成这两个库,例如粘胶。 这听起来很复杂,但值得庆幸的是,复杂性隐藏在库中,这将为我们完成所有艰苦的工作。
Jupyter中的以下单元格负责上半部分-将Twisted与asyncio事件循环集成。
__file__ = None
不需要将PursuedPyBear与Jupyter集成。
from twisted.
internet
import asyncioreactor
asyncioreactor.
install
(
)
__file__
=
None
接下来,我们需要一个“设置”功能。 设置功能是关键游戏元素配置的常用术语。 但是,我们的功能只会将游戏“场景”放在全局变量中。 就像我们定义要玩游戏的桌子一样。
Jupyter Notebook中的以下单元将完成此操作。
def setup
( scene
) :
global SCENE
SCENE
= scene
现在,我们需要将PursuedPyBear的事件循环与Twisted集成。 我们txppb
使用txppb
模块:
import txppb
d
= txppb.
run
( setup
)
d.
addBoth
(
print
)
如果游戏由于错误而崩溃,最后的print
将为我们提供帮助-它将打印出对Jupyter输出的追溯。
这将显示一个空窗口,准备好游戏元素。
这就是我们开始利用Jupyter的地方-传统上,在开始玩游戏之前,需要先编写整个游戏。 但是,我们违反约定,立即开始玩游戏!
通过互动使游戏变得有趣
但是,这不是一个非常有趣的游戏。 它什么都没有,只坐在那里。 如果我们想要一些东西,我们最好添加它。
在视频游戏编程中,屏幕上移动的东西称为“精灵”。 在PursuedPyBear中,子画面由类表示。 精灵会自动使用与该类相同名称的图像。 我从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
的代码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
)
有用!
迭代游戏设计对企鹅和其他人来说都很有趣!
这具有游戏的所有功能:由玩家控制的企鹅将球踢入目标,获得鱼,吃鱼并踢出新球。 这可以作为游戏的“磨削级别”部分,或者我们可以添加障碍来使企鹅的生活更加艰难。
无论您是经验丰富的程序员,还是刚开始使用,对视频游戏进行编程都是很有趣的。 具有Jupyter的PursuedPyBear通过经典环境(如Logo和Smalltalk)的交互编程功能,带来了经典2D游戏的所有乐趣。 是时候享受复古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'
)
)
python 构建