Program Arcade Games
With Python And Pygame

Chapter 10: 控制器和图形

我们如何通过使用键盘,鼠标或者游戏手柄来操控物体移动?

10.1 入门

fig.controller

截止目前,我们已经演示国入伙让物体在屏幕上动画化,但是还不知道如何同它们交互。我们如何使用鼠标,键盘或者游戏手柄来可能哦告知屏幕上的行动? 其实很简单。

首先,我们必须让一个物体能够在屏幕上四处移动。 最哈的办法是拥有一个函数function能够接受x,y坐标,然后在那个位置画出物体。 所以寄回到了第9章! 我们来看一下如何写个函数来画物体。

视频:用函数画图用

所有的Pygame的画图函数需要一个screen参数来让Pygame知道画在哪个窗口上。我们需要把这个参数传递到任何要在屏幕上砸图的函数。

这个函数也需要知道在屏幕的哪里画出对象。函数需要一个x和一个y。我们以参数的形式把位置传递给函数。 以下的代码定义了一个画雪人的函数:

画雪人的函数
1
2
3
4
5
6
7
def draw_snowman(screen, x, y):
# 画一个圆形作头部
pygame.draw.ellipse(screen, WHITE, [35+x, 0+y, 25, 25])
# 画一个圆形作中部
pygame.draw.ellipse(screen, WHITE, [23+x, 20+y, 50, 50])
# 画一个圆形作底部
pygame.draw.ellipse(screen, WHITE, [0+x, 65+y, 100, 100])

然后,在主程序的循环里,多个雪人画了出来,如图所示10.1.

fig.snowmen
Figure 10.1: 函数画的雪人
调用draw_snowman
1
2
3
4
5
6
7
8
# 左上角的雪人
draw_snowman(screen, 10, 10)
# 右上角的雪人
draw_snowman(screen, 300, 10)
# 左下角的雪人
draw_snowman(screen, 10, 300)
视频:把已有的代码转到函数

一个完整的例子在这里:
ProgramArcadeGames.com/python_examples/f.php?file=functions_and_graphics.py

可能你在之前的实验里已经有画出很酷的图的代码。你应该如何将它转成一个函数呢 让我们来看一个画火柴人的代码:

画火柴人的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
# 头
pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)
# 腿
pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)
# 身体
pygame.draw.line(screen, RED, [100,100], [100,90], 2)
# 手臂
pygame.draw.line(screen, RED, [100,90], [104,100], 2)
pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.stick_figure
Figure 10.2: 火柴人

这段代码可以通过添加一个def和缩进来变成一个函数。我们需要引入画出图形所需的所有数据。 我们需要screen变量来告诉函数画在哪个窗口上,还需要一对x,y坐标来告知画的位置。

但是我们不能在我们程序的循环之中定义函数! 代码应该从程序的主要部分里移除。函数的声明应该在程序的开头。 我们需要把代码移到顶端。 参看图例10.3来帮助视觉化地理解。

在同一位置画火柴人的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def draw_stick_figure(screen,x,y):
# 头
pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)
# 腿
pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)
# 身体
pygame.draw.line(screen, RED, [100,100], [100,90], 2)
# 手臂
pygame.draw.line(screen, RED, [100,90], [104,100], 2)
pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.making_a_function
Figure 10.3: 写一个函数并放在正确的位置

现在,代码取了一个x,y的坐标。不幸地是函数并没有对它做什么。你可以声明任何你想要的坐标,但是火柴人总是被画在 同一个位置。并不是很有用。下面的代码会真正使用到x,y坐标在我们之前的代码基础之上。

读火柴人函数的第二次尝试对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def draw_stick_figure(screen, x, y):
# 头
pygame.draw.ellipse(screen, BLACK,[96+x,83+y,10,10],0)
# 腿
pygame.draw.line(screen, BLACK, [100+x,100+y], [105+x,110+y], 2)
pygame.draw.line(screen, BLACK, [100+x,100+y], [95+x,110+y], 2)
# 身体
pygame.draw.line(screen, RED, [100+x,100+y], [100+x,90+y], 2)
# 手臂
pygame.draw.line(screen, RED, [100+x,90+y], [104+x,100+y], 2)
pygame.draw.line(screen, RED, [100+x,90+y], [96+x,100+y], 2)

但是问题是现在的火柴人的位置已经和原点有一些距离了。 它假设了原点是(0, 0),然后在向下相距100像素的地方画出了图形。参考图例10.4,它演示了图形并没有在传入的(0, 0)处画出来。

fig.stick_figure_2
Figure 10.4: 火柴人图形

通过在函数里添加x和y,我们把原点挪动了那段距离。例如,如果我们调用:

draw_stick_figure(screen, 50, 50)

代码并没有在(50, 50)出画图。它把原点向右和向下各挪了50像素。我们原来的火柴人就画在(100, 100)处, 现在原点被挪动了,图形现在出现在(150, 150)出。我们该如何改动能把图画在函数调用的位置呢??

fig.code_example
Figure 10.5: 找到X和Y的最小值

找到x的最小值和y的最小值,如图例所示。10.5。然后在函数里用x和y分别减去这个值。 不要同高度与宽度搞混淆。 这是一个例子:

对火柴人的第三次尝试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def draw_stick_figure(screen, x, y):
# 头
pygame.draw.ellipse(screen, BLACK,[96-95+x,83-83+y,10,10],0)
# 腿
pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [105-95+x,110-83+y], 2)
pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [95-95+x,110-83+y], 2)
# 身体
pygame.draw.line(screen, RED, [100-95+x,100-83+y], [100-95+x,90-83+y], 2)
# 手臂
pygame.draw.line(screen, RED, [100-95+x,90-83+y], [104-95+x,100-83+y], 2)
pygame.draw.line(screen, RED, [100-95+x,90-83+y], [96-95+x,100-83+y], 2)

或者,让程序更加简单,你自己来完成减法:

Final stickfigure function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def draw_stick_figure(screen, x, y):
# 头
pygame.draw.ellipse(screen, BLACK, [1+x,y,10,10], 0)
# 腿
pygame.draw.line(screen, BLACK ,[5+x,17+y], [10+x,27+y], 2)
pygame.draw.line(screen, BLACK, [5+x,17+y], [x,27+y], 2)
# 身体
pygame.draw.line(screen, RED, [5+x,17+y], [5+x,7+y], 2)
# 手臂
pygame.draw.line(screen, RED, [5+x,7+y], [9+x,17+y], 2)
pygame.draw.line(screen, RED, [5+x,7+y], [1+x,17+y], 2)

10.2 鼠标

视频:用鼠标移动用

现在,我们知道如何写一个函数来在指定位置画东西。 我们如何获取那些坐标呢?最简单的办法是用鼠标。取得坐标只需要一行代码:

pos = pygame.mouse.get_pos()

技巧是返回的坐标是一个列表,或更具体地说是一个不可修改的元组。x和y的值存在同一个变量里。 所以如果我们做一个print(pos),我们会得到如图所示10.6.

fig.coordinates
Figure 10.6: 坐标

变量pos是一个两个数组成的元组。x坐标在数组的位置0,而y坐标在位置1。它们都可以简单地提取并传递到所需的函数里:

使用鼠标控制物体
# 游戏逻辑
pos = pygame.mouse.get_pos()
x = pos[0]
y = pos[1]
# 绘图
draw_stick_figure(screen, x, y)

获得鼠标位置的代码,应噶放在主循环中的“游戏逻辑”这一块。 要调用它的函数应该放在“绘图”这一块。

这里唯一的问题是鼠标会停留在图像正上方,难于观察,如图例所示10.7.

fig.mouse_and_figure
Figure 10.7: 鼠标挡在图像之上

鼠标可以通过下面的代码隐藏起来:

隐藏鼠标光标
# 隐藏鼠标光标
pygame.mouse.set_visible(False)

一个湾真的例子可以在这儿找到完整
ProgramArcadeGames.com/python_examples/f.php?file=move_mouse.py

10.3 键盘

视频:用键盘移动

用键盘控制要复杂一些。 我们不能像从鼠标那儿抓取x和y。键盘没法提供x和y。我们需要做的是:

看起来好像很复杂,其实这和我们之前做的反弹矩形很相近,只不过速度是由键盘看哦的。

要开始,我们先在主循环开始之前设置好位置和速度:

# 每一帧的速度
x_speed = 0
y_speed = 0
# 闲杂的位置现在x_coord = 10
y_coord = 10

在程序的主while循环之内,我们需要处理一些事件的操作。 另外,在等待pygame.QUIT事件的时候,程序也需要寻找键盘事件的发生。 每一次按下一个键都会生成一个事件。

一个pygame.KEYDOWN事件是在按键被按下时生澈的。一个pygame.KEYUP事件是当用户离开一个按键的时候生成的。 当用户按下键的时候,速度矢量设置为每帧3或这-3像素。 当用户释放健的时候,速度矢量又重新设为0。 最终,物体的坐标由速度调整,然后被画了出来。 请查看下面的代码:

用键盘控制一个物体
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# 用户按下了一个键
if event.type == pygame.KEYDOWN:
# 判断出是一个方向键,从而调整速度。
if event.key == pygame.K_LEFT:
x_speed = -3
if event.key == pygame.K_RIGHT:
x_speed = 3
if event.key == pygame.K_UP:
y_speed = -3
if event.key == pygame.K_DOWN:
y_speed = 3
# 用户释放了一个键
if event.type == pygame.KEYUP:
# 判断出是一个方向键,从而重设速度为0。
if event.key == pygame.K_LEFT:
x_speed = 0
if event.key == pygame.K_RIGHT:
x_speed = 0
if event.key == pygame.K_UP:
y_speed = 0
if event.key == pygame.K_DOWN:
y_speed = 0
# 根据速度矢量来移动物体
x_coord += x_speed
y_coord += y_speed
# 画出图像
draw_stick_figure(screen, x_coord, y_coord)

一个完整的例子请看:
ProgramArcadeGames.com/python_examples/f.php?file=move_keyboard.py

请注意这段代码不能阻止物体滑出屏幕边缘。要防止这样,我们需要在游戏逻辑的部分,添加一组if语句来检查x_coordy_coord的值。 如果它们在屏幕边界外,就重设密码。具体的代码就作为练习留给读者了。

下面的表格展示了Python中所有可以用的键盘对应的代码:

Pygame码ASCII常用名称
K_BACKSPACE\bbackspace
K_RETURN\rreturn
K_TAB\ttab
K_ESCAPE^[escape
K_SPACE space
K_COMMA,comma sign
K_MINUS-minus
K_PERIOD.period slash
K_SLASH/forward
K_000
K_111
K_222
K_333
K_444
K_555
K_666
K_777
K_888
K_999
K_SEMICOLON;semicolon sign
K_EQUALS=equals sign
K_LEFTBRACKET[left
K_RIGHTBRACKET]right
K_BACKSLASH\backslash bracket
K_BACKQUOTE`grave
K_aaa
K_bbb
K_ccc
K_ddd
K_eee
K_fff
K_ggg
K_hhh
K_iii
K_jjj
K_kkk
K_lll
K_mmm
K_nnn
K_ooo
K_ppp
K_qqq
K_rrr
K_sss
K_ttt
K_uuu
K_vvv
K_www
K_xxx
K_yyy
K_zzz
K_DELETEdelete
K_KP0keypad0
K_KP1keypad1
K_KP2keypad2
K_KP3keypad3
K_KP4keypad4
K_KP5keypad5
K_KP6keypad6
K_KP7keypad7
K_KP8keypad8
K_KP9keypad9 period
K_KP_PERIOD.keypad divide
K_KP_DIVIDE/keypad multiply
K_KP_MULTIPLY*keypad minus
K_KP_MINUS-keypad plus
K_KP_PLUS+keypad enter
K_KP_ENTER\rkeypad equals
K_KP_EQUALS=keypad
K_UPuparrow
K_DOWNdownarrow
K_RIGHTrightarrow
K_LEFTleftarrow
K_INSERTinsert
K_HOMEhome
K_ENDend
K_PAGEUPpageup
K_PAGEDOWNpagedown
K_F1F1
K_F2F2
K_F3F3
K_F4F4
K_F5F5
K_F6F6
K_F7F7
K_F8F8
K_F9F9
K_F10F10
K_F11F11
K_F12F12
K_NUMLOCKnumlock
K_CAPSLOCKcapslock
K_RSHIFTrightshift
K_LSHIFTleftshift
K_RCTRLrightctrl
K_LCTRLleftctrl
K_RALTrightalt
K_LALTleftalt

10.4 游戏控制器

游戏鼠标需要一组不同的代码,但是概念仍然是简单的。

首席,要检查计算机是否装有手柄,先启动手柄。这只需要在主循环之前做一次。:

初始化游戏手柄
# 现在位置
x_coord = 10
y_coord = 10
# 检查游戏手柄
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
# 没检查到游戏手柄!
print("Error, I didn't find any joysticks.")
else:
# 使用手柄0并初始化
my_joystick = pygame.joystick.Joystick(0)
my_joystick.init()

一个游戏手柄会返回两个浮点数。如果手柄位于正中央,返回的值是(0, 0)。 如果手柄在左上角,会返回(-1, -1)。如果手表在右下角,返回(1, 1)。如果在这之间的某个位置,其值会按比例缩放。请参考图例对其工作原理有个大概印象10.8

fig.c
Figure 10.8: Center (0,0)
fig.ul
Figure 10.9: Up Left (-1,-1)
fig.u
Figure 10.10: Up (0,-1)
fig.ur
Figure 10.11: Up Right (1,-1)
fig.r
Figure 10.12: Right (1,0)
fig.dr
Figure 10.13: Down Right (1,1)
fig.d
Figure 10.14: Down (0,1)
fig.dl
Figure 10.15: Down Left (-1,1)
fig.controller_left
Figure 10.16: Left (-1,0)

在主循环之内,手柄返回的值可能会根据物理移动的距离来做乘法。在下段例子中,手柄的值被乘以了10,所以会以每帧10像素的速度移动手柄。

通过手柄控制物体
# 这需在主循环之中
#检查手柄的存在
if joystick_count != 0:
# 获得手柄的位置
# 返回一个-1.0 and +1.0之间的值
horiz_axis_pos = my_joystick.get_axis(0)
vert_axis_pos = my_joystick.get_axis(1)
#沿着轴移动x。乘以10来加速。
# 转化成整数,因为我们不能在像素3.5处画图,要么像素3或是像素4。
x_coord = x_coord + int(horiz_axis_pos * 10)
y_coord = y_coord + int(vert_axis_pos * 10)
# 清理屏幕
screen.fill(WHITE)
# 在适当的位置绘图
draw_stick_figure(screen, x_coord, y_coord)

完整的例子,请看
ProgramArcadeGames.com/python_examples/f.php?file=move_game_controller.py.

游戏控制器有手柄,按键甚至“帽子”开关。下面的例子和截图,打印出了每个游戏控制器在做什么。请注意游戏控制器必须在程序开始前就插入,否则无法检测到它们。

fig.joystick_calls
Figure 10.17: 手柄调用程序
joystick_calls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""
Sample Python/Pygame Programs
Simpson College Computer Science
Show everything we can pull off the joystick
"""
import pygame
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
class TextPrint(object):
"""
This is a simple class that will help us print to the screen
It has nothing to do with the joysticks, just outputting the
information.
"""
def __init__(self):
""" Constructor """
self.reset()
self.x_pos = 10
self.y_pos = 10
self.font = pygame.font.Font(None, 20)
def print(self, my_screen, text_string):
""" Draw text onto the screen. """
text_bitmap = self.font.render(text_string, True, BLACK)
my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])
self.y_pos += self.line_height
def reset(self):
""" Reset text to the top of the screen. """
self.x_pos = 10
self.y_pos = 10
self.line_height = 15
def indent(self):
""" Indent the next line of text """
self.x_pos += 10
def unindent(self):
""" Unindent the next line of text """
self.x_pos -= 10
pygame.init()
# Set the width and height of the screen [width,height]
size = [500, 700]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Initialize the joysticks
pygame.joystick.init()
# Get ready to print
textPrint = TextPrint()
# -------- Main Program Loop -----------
while not done:
# EVENT PROCESSING STEP
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN
# JOYBUTTONUP JOYHATMOTION
if event.type == pygame.JOYBUTTONDOWN:
print("Joystick button pressed.")
if event.type == pygame.JOYBUTTONUP:
print("Joystick button released.")
# DRAWING STEP
# First, clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
screen.fill(WHITE)
textPrint.reset()
# Get count of joysticks
joystick_count = pygame.joystick.get_count()
textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))
textPrint.indent()
# For each joystick:
for i in range(joystick_count):
joystick = pygame.joystick.Joystick(i)
joystick.init()
textPrint.print(screen, "Joystick {}".format(i))
textPrint.indent()
# Get the name from the OS for the controller/joystick
name = joystick.get_name()
textPrint.print(screen, "Joystick name: {}".format(name))
# Usually axis run in pairs, up/down for one, and left/right for
# the other.
axes = joystick.get_numaxes()
textPrint.print(screen, "Number of axes: {}".format(axes))
textPrint.indent()
for i in range(axes):
axis = joystick.get_axis(i)
textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))
textPrint.unindent()
buttons = joystick.get_numbuttons()
textPrint.print(screen, "Number of buttons: {}".format(buttons))
textPrint.indent()
for i in range(buttons):
button = joystick.get_button(i)
textPrint.print(screen, "Button {:>2} value: {}".format(i, button))
textPrint.unindent()
# Hat switch. All or nothing for direction, not like joysticks.
# Value comes back in an array.
hats = joystick.get_numhats()
textPrint.print(screen, "Number of hats: {}".format(hats))
textPrint.indent()
for i in range(hats):
hat = joystick.get_hat(i)
textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))
textPrint.unindent()
textPrint.unindent()
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 60 frames per second
clock.tick(60)
# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()

10.5 复习

10.5.1 选择题小测验

Click here for a multiple-choice quiz.

10.5.2 练习表

Click here for the chapter worksheet.

10.5.3 实验

Click here for the chapter lab.


You are not logged in. Log in here and track your progress.