双杆式射手#
这个例子展示了如何在游戏手柄上同时使用两个操纵杆。由于游戏手柄布局变化很大,您可能需要使用调整贴图 SHOOTING_AXIS_SELECTION
。

dual_stick_shooter.py#
1"""
2Dual-stick Shooter Example
3
4A dual-analog stick controller is the preferred method of input. If a controller is
5not present, the game will fail back to use keyboard controls (WASD to move, arrows to shoot)
6
7If Python and Arcade are installed, this example can be run from the command line with:
8python -m arcade.examples.dual_stick_shooter
9"""
10import math
11import pprint
12import random
13from typing import cast
14
15import arcade
16
17SCREEN_WIDTH = 1024
18SCREEN_HEIGHT = 768
19SCREEN_TITLE = "Dual-stick Shooter Example"
20MOVEMENT_SPEED = 4
21BULLET_SPEED = 10
22BULLET_COOLDOWN_TICKS = 10
23ENEMY_SPAWN_INTERVAL = 1
24ENEMY_SPEED = 1
25STICK_DEADZONE = 0.05
26# An angle of "0" means "right", but the player's texture is oriented in the "up" direction.
27# So an offset is needed.
28ROTATE_OFFSET = -90
29
30
31def dump_obj(obj):
32 for key in sorted(vars(obj)):
33 val = getattr(obj, key)
34 print("{:30} = {} ({})".format(key, val, type(val).__name__))
35
36
37def dump_controller(controller):
38 print("========== {}".format(controller))
39 print("Left X {}".format(controller.leftx))
40 print("Left Y {}".format(controller.lefty))
41 print("Left Trigger {}".format(controller.lefttrigger))
42 print("Right X {}".format(controller.rightx))
43 print("Right Y {}".format(controller.righty))
44 print("Right Trigger {}".format(controller.righttrigger))
45 print("========== Extra controller")
46 dump_obj(controller)
47 print("========== Extra controller.device")
48 dump_obj(controller.device)
49 print("========== pprint controller")
50 pprint.pprint(controller)
51 print("========== pprint controller.device")
52 pprint.pprint(controller.device)
53
54
55def dump_controller_state(ticks, controller):
56 # print("{:5.2f} {:5.2f} {:>20} {:5}_".format(1.234567, -8.2757272903, "hello", str(True)))
57 fmt_str = "{:6d} "
58 num_fmts = ["{:5.2f}"] * 6
59 fmt_str += " ".join(num_fmts)
60 print(fmt_str.format(ticks,
61 controller.leftx,
62 controller.lefty,
63 controller.lefttrigger,
64 controller.rightx,
65 controller.righty,
66 controller.righttrigger,
67 ))
68
69
70def get_stick_position(x, y):
71 """Given position of stick axes, return (x, y, angle_in_degrees).
72 If movement is not outside of deadzone, return (None, None, None)"""
73 if x > STICK_DEADZONE or x < -STICK_DEADZONE or y > STICK_DEADZONE or y < -STICK_DEADZONE:
74 rad = math.atan2(y, x)
75 angle = math.degrees(rad)
76 return x, y, angle
77 return None, None, None
78
79
80class Player(arcade.sprite.Sprite):
81 def __init__(self, filename):
82 super().__init__(filename, scale=0.4, center_x=SCREEN_WIDTH / 2, center_y=SCREEN_HEIGHT / 2)
83 self.shoot_up_pressed = False
84 self.shoot_down_pressed = False
85 self.shoot_left_pressed = False
86 self.shoot_right_pressed = False
87 self.start_pressed = False
88
89
90class Enemy(arcade.sprite.Sprite):
91 def __init__(self, x, y):
92 super().__init__(':resources:images/pinball/bumper.png', scale=0.5, center_x=x, center_y=y)
93
94 def follow_sprite(self, player_sprite):
95 """
96 This function will move the current sprite towards whatever
97 other sprite is specified as a parameter.
98
99 We use the 'min' function here to get the sprite to line up with
100 the target sprite, and not jump around if the sprite is not off
101 an exact multiple of ENEMY_SPEED.
102 """
103
104 if self.center_y < player_sprite.center_y:
105 self.center_y += min(ENEMY_SPEED, player_sprite.center_y - self.center_y)
106 elif self.center_y > player_sprite.center_y:
107 self.center_y -= min(ENEMY_SPEED, self.center_y - player_sprite.center_y)
108
109 if self.center_x < player_sprite.center_x:
110 self.center_x += min(ENEMY_SPEED, player_sprite.center_x - self.center_x)
111 elif self.center_x > player_sprite.center_x:
112 self.center_x -= min(ENEMY_SPEED, self.center_x - player_sprite.center_x)
113
114
115class MyGame(arcade.Window):
116 def __init__(self, width, height, title):
117 super().__init__(width, height, title)
118 self.game_over = False
119 self.score = 0
120 self.tick = 0
121 self.bullet_cooldown = 0
122 self.player = Player(":resources:images/space_shooter/playerShip2_orange.png")
123 self.bullet_list = arcade.SpriteList()
124 self.enemy_list = arcade.SpriteList()
125 self.controller_manager = arcade.ControllerManager()
126 self.controller = None
127
128 self.background_color = arcade.color.DARK_MIDNIGHT_BLUE
129 controllers = self.controller_manager.get_controllers()
130 for controller in controllers:
131 dump_controller(controller)
132 if controllers:
133 self.connect_controller(controllers[0])
134 arcade.window_commands.schedule(self.debug_controller_state, 0.1)
135
136 arcade.window_commands.schedule(self.spawn_enemy, ENEMY_SPAWN_INTERVAL)
137
138 @self.controller_manager.event
139 def on_connect(controller):
140 if not self.controller:
141 self.connect_controller(controller)
142
143 @self.controller_manager.event
144 def on_disconnect(controller):
145 if self.controller == controller:
146 controller.close()
147 self.controller = None
148
149 def setup(self):
150 self.game_over = False
151 self.score = 0
152 self.tick = 0
153 self.bullet_cooldown = 0
154 self.bullet_list = arcade.SpriteList()
155 self.enemy_list = arcade.SpriteList()
156 self.player.center_x = SCREEN_WIDTH / 2
157 self.player.center_y = SCREEN_HEIGHT / 2
158
159 def connect_controller(self, controller):
160 self.controller = controller
161 self.controller.open()
162
163 def debug_controller_state(self, _delta_time):
164 if self.controller:
165 dump_controller_state(self.tick, self.controller)
166
167 def spawn_enemy(self, _elapsed):
168 if self.game_over:
169 return
170 x = random.randint(0, SCREEN_WIDTH)
171 y = random.randint(0, SCREEN_HEIGHT)
172 self.enemy_list.append(Enemy(x, y))
173
174 def on_update(self, delta_time):
175 self.tick += 1
176 if self.game_over:
177 if self.controller:
178 if self.controller.start:
179 self.setup()
180 return
181
182 if self.player.start_pressed:
183 self.setup()
184 return
185
186 return
187
188 self.bullet_cooldown += 1
189
190 for enemy in self.enemy_list:
191 cast(Enemy, enemy).follow_sprite(self.player)
192
193 if self.controller:
194 # Controller input - movement
195 move_x, move_y, move_angle = get_stick_position(self.controller.leftx, self.controller.lefty)
196 if move_angle:
197 self.player.change_x = move_x * MOVEMENT_SPEED
198 self.player.change_y = move_y * MOVEMENT_SPEED
199 self.player.angle = move_angle + ROTATE_OFFSET
200 else:
201 self.player.change_x = 0
202 self.player.change_y = 0
203
204 # Controller input - shooting
205 shoot_x, shoot_y, shoot_angle = get_stick_position(self.controller.rightx, self.controller.righty)
206 if shoot_angle:
207 self.spawn_bullet(shoot_angle)
208 else:
209 # Keyboard input - shooting
210 if self.player.shoot_right_pressed and self.player.shoot_up_pressed:
211 self.spawn_bullet(0 + 45)
212 elif self.player.shoot_up_pressed and self.player.shoot_left_pressed:
213 self.spawn_bullet(90 + 45)
214 elif self.player.shoot_left_pressed and self.player.shoot_down_pressed:
215 self.spawn_bullet(180 + 45)
216 elif self.player.shoot_down_pressed and self.player.shoot_right_pressed:
217 self.spawn_bullet(270 + 45)
218 elif self.player.shoot_right_pressed:
219 self.spawn_bullet(0)
220 elif self.player.shoot_up_pressed:
221 self.spawn_bullet(90)
222 elif self.player.shoot_left_pressed:
223 self.spawn_bullet(180)
224 elif self.player.shoot_down_pressed:
225 self.spawn_bullet(270)
226
227 self.enemy_list.update()
228 self.player.update()
229 self.bullet_list.update()
230 ship_death_hit_list = arcade.check_for_collision_with_list(self.player,
231 self.enemy_list)
232 if len(ship_death_hit_list) > 0:
233 self.game_over = True
234 for bullet in self.bullet_list:
235 bullet_killed = False
236 enemy_shot_list = arcade.check_for_collision_with_list(bullet,
237 self.enemy_list)
238 # Loop through each colliding sprite, remove it, and add to the score.
239 for enemy in enemy_shot_list:
240 enemy.remove_from_sprite_lists()
241 bullet.remove_from_sprite_lists()
242 bullet_killed = True
243 self.score += 1
244 if bullet_killed:
245 continue
246
247 def on_key_press(self, key, modifiers):
248 if key == arcade.key.W:
249 self.player.change_y = MOVEMENT_SPEED
250 elif key == arcade.key.A:
251 self.player.change_x = -MOVEMENT_SPEED
252 elif key == arcade.key.S:
253 self.player.change_y = -MOVEMENT_SPEED
254 elif key == arcade.key.D:
255 self.player.change_x = MOVEMENT_SPEED
256 elif key == arcade.key.RIGHT:
257 self.player.shoot_right_pressed = True
258 elif key == arcade.key.UP:
259 self.player.shoot_up_pressed = True
260 elif key == arcade.key.LEFT:
261 self.player.shoot_left_pressed = True
262 elif key == arcade.key.DOWN:
263 self.player.shoot_down_pressed = True
264 elif key == arcade.key.ESCAPE:
265 self.player.start_pressed = True
266
267 rad = math.atan2(self.player.change_y, self.player.change_x)
268 self.player.angle = math.degrees(rad) + ROTATE_OFFSET
269
270 def on_key_release(self, key, modifiers):
271 if key == arcade.key.W:
272 self.player.change_y = 0
273 elif key == arcade.key.A:
274 self.player.change_x = 0
275 elif key == arcade.key.S:
276 self.player.change_y = 0
277 elif key == arcade.key.D:
278 self.player.change_x = 0
279 elif key == arcade.key.RIGHT:
280 self.player.shoot_right_pressed = False
281 elif key == arcade.key.UP:
282 self.player.shoot_up_pressed = False
283 elif key == arcade.key.LEFT:
284 self.player.shoot_left_pressed = False
285 elif key == arcade.key.DOWN:
286 self.player.shoot_down_pressed = False
287
288 rad = math.atan2(self.player.change_y, self.player.change_x)
289 self.player.angle = math.degrees(rad) + ROTATE_OFFSET
290
291 def spawn_bullet(self, angle_in_deg):
292 # only allow bullet to spawn on an interval
293 if self.bullet_cooldown < BULLET_COOLDOWN_TICKS:
294 return
295 self.bullet_cooldown = 0
296
297 bullet = arcade.Sprite(":resources:images/space_shooter/laserBlue01.png", scale=0.75)
298
299 # Position the bullet at the player's current location
300 start_x = self.player.center_x
301 start_y = self.player.center_y
302 bullet.center_x = start_x
303 bullet.center_y = start_y
304
305 # angle the bullet visually
306 bullet.angle = angle_in_deg
307 angle_in_rad = math.radians(angle_in_deg)
308
309 # set bullet's movement direction
310 bullet.change_x = math.cos(angle_in_rad) * BULLET_SPEED
311 bullet.change_y = math.sin(angle_in_rad) * BULLET_SPEED
312
313 # Add the bullet to the appropriate lists
314 self.bullet_list.append(bullet)
315
316 def on_draw(self):
317 # clear screen and start render process
318 self.clear()
319
320 # draw game items
321 self.bullet_list.draw()
322 self.enemy_list.draw()
323 self.player.draw()
324
325 # Put the score on the screen.
326 output = f"Score: {self.score}"
327 arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
328
329 # Game over message
330 if self.game_over:
331 arcade.draw_text("Game Over",
332 SCREEN_WIDTH / 2,
333 SCREEN_HEIGHT / 2,
334 arcade.color.WHITE, 100,
335 width=SCREEN_WIDTH,
336 align="center",
337 anchor_x="center",
338 anchor_y="center")
339
340
341
342def main():
343 game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
344 game.run()
345
346
347if __name__ == "__main__":
348 main()