生命点数和生命值栏#

此示例演示了在角色上方绘制健康条的一种相当有效的方法。
屏幕中央的敌人向玩家发射子弹,而玩家试图通过移动鼠标来躲避子弹。每一颗击中球员的子弹都会降低球员的健康,这一点由球员头顶上的横条显示。当玩家的生命条为空(零)时,游戏结束。
sprite_health.py#
1"""
2Sprite Health Bars
3
4Artwork from https://kenney.nl
5
6If Python and Arcade are installed, this example can be run from the command line with:
7python -m arcade.examples.sprite_health
8"""
9import math
10from typing import Tuple
11
12import arcade
13from arcade.resources import (
14 image_female_person_idle,
15 image_laser_blue01,
16 image_zombie_idle,
17)
18from arcade.types import Color
19
20SPRITE_SCALING_PLAYER = 0.5
21SPRITE_SCALING_ENEMY = 0.5
22SPRITE_SCALING_BULLET = 1
23INDICATOR_BAR_OFFSET = 32
24ENEMY_ATTACK_COOLDOWN = 1
25BULLET_SPEED = 150
26BULLET_DAMAGE = 1
27PLAYER_HEALTH = 5
28
29SCREEN_WIDTH = 800
30SCREEN_HEIGHT = 600
31SCREEN_TITLE = "Sprite Health Bars"
32
33
34def sprite_off_screen(
35 sprite: arcade.Sprite,
36 screen_height: int = SCREEN_HEIGHT,
37 screen_width: int = SCREEN_WIDTH,
38) -> bool:
39 """Checks if a sprite is off-screen or not."""
40 return (
41 sprite.top < 0
42 or sprite.bottom > screen_height
43 or sprite.right < 0
44 or sprite.left > screen_width
45 )
46
47
48class Player(arcade.Sprite):
49 def __init__(self, bar_list: arcade.SpriteList) -> None:
50 super().__init__(
51 image_female_person_idle,
52 scale=SPRITE_SCALING_PLAYER,
53 )
54 self.indicator_bar: IndicatorBar = IndicatorBar(
55 self, bar_list, (self.center_x, self.center_y), scale=1.5,
56 )
57 self.health: int = PLAYER_HEALTH
58
59
60class Bullet(arcade.Sprite):
61 def __init__(self) -> None:
62 super().__init__(
63 image_laser_blue01,
64 scale=SPRITE_SCALING_BULLET,
65 )
66
67 def on_update(self, delta_time: float = 1 / 60) -> None:
68 """Updates the bullet's position."""
69 self.position = (
70 self.center_x + self.change_x * delta_time,
71 self.center_y + self.change_y * delta_time,
72 )
73
74
75class IndicatorBar:
76 """
77 Represents a bar which can display information about a sprite.
78
79 :param owner: The owner of this indicator bar.
80 :param sprite_list: The sprite list used to draw the indicator
81 bar components.
82 :param Tuple[float, float] position: The initial position of the bar.
83 :param full_color: The color of the bar.
84 :param background_color: The background color of the bar.
85 :param width: The width of the bar.
86 :param height: The height of the bar.
87 :param border_size: The size of the bar's border.
88 :param scale: The scale of the indicator bar.
89 """
90
91 def __init__(
92 self,
93 owner: Player,
94 sprite_list: arcade.SpriteList,
95 position: Tuple[float, float] = (0, 0),
96 full_color: Color = arcade.color.GREEN,
97 background_color: Color = arcade.color.BLACK,
98 width: int = 100,
99 height: int = 4,
100 border_size: int = 4,
101 scale: float = 1.0,
102 ) -> None:
103 # Store the reference to the owner and the sprite list
104 self.owner: Player = owner
105 self.sprite_list: arcade.SpriteList = sprite_list
106
107 # Set the needed size variables
108 self._bar_width: int = width
109 self._bar_height: int = height
110 self._center_x: float = 0.0
111 self._center_y: float = 0.0
112 self._fullness: float = 0.0
113 self._scale: float = 1.0
114
115 # Create the boxes needed to represent the indicator bar
116 self._background_box: arcade.SpriteSolidColor = arcade.SpriteSolidColor(
117 self._bar_width + border_size,
118 self._bar_height + border_size,
119 color=background_color,
120 )
121 self._full_box: arcade.SpriteSolidColor = arcade.SpriteSolidColor(
122 self._bar_width,
123 self._bar_height,
124 color=full_color,
125 )
126 self.sprite_list.append(self._background_box)
127 self.sprite_list.append(self._full_box)
128
129 # Set the fullness, position and scale of the bar
130 self.fullness = 1.0
131 self.position = position
132 self.scale = scale
133
134 def __repr__(self) -> str:
135 return f"<IndicatorBar (Owner={self.owner})>"
136
137 @property
138 def background_box(self) -> arcade.SpriteSolidColor:
139 """Returns the background box of the indicator bar."""
140 return self._background_box
141
142 @property
143 def full_box(self) -> arcade.SpriteSolidColor:
144 """Returns the full box of the indicator bar."""
145 return self._full_box
146
147 @property
148 def bar_width(self) -> int:
149 """Gets the width of the bar."""
150 return self._bar_width
151
152 @property
153 def bar_height(self) -> int:
154 """Gets the height of the bar."""
155 return self._bar_height
156
157 @property
158 def center_x(self) -> float:
159 """Gets the x position of the bar."""
160 return self._center_x
161
162 @property
163 def center_y(self) -> float:
164 """Gets the y position of the bar."""
165 return self._center_y
166
167 @property
168 def top(self) -> float:
169 """Gets the y coordinate of the top of the bar."""
170 return self.background_box.top
171
172 @property
173 def bottom(self) -> float:
174 """Gets the y coordinate of the bottom of the bar."""
175 return self.background_box.bottom
176
177 @property
178 def left(self) -> float:
179 """Gets the x coordinate of the left of the bar."""
180 return self.background_box.left
181
182 @property
183 def right(self) -> float:
184 """Gets the x coordinate of the right of the bar."""
185 return self.background_box.right
186
187 @property
188 def fullness(self) -> float:
189 """Returns the fullness of the bar."""
190 return self._fullness
191
192 @fullness.setter
193 def fullness(self, new_fullness: float) -> None:
194 """Sets the fullness of the bar."""
195 # Check if new_fullness if valid
196 if not (0.0 <= new_fullness <= 1.0):
197 raise ValueError(
198 f"Got {new_fullness}, but fullness must be between 0.0 and 1.0."
199 )
200
201 # Set the size of the bar
202 self._fullness = new_fullness
203 if new_fullness == 0.0:
204 # Set the full_box to not be visible since it is not full anymore
205 self.full_box.visible = False
206 else:
207 # Set the full_box to be visible incase it wasn't then update the bar
208 self.full_box.visible = True
209 self.full_box.width = self._bar_width * new_fullness * self.scale
210 self.full_box.left = self._center_x - (self._bar_width / 2) * self.scale
211
212 @property
213 def position(self) -> Tuple[float, float]:
214 """Returns the current position of the bar."""
215 return self._center_x, self._center_y
216
217 @position.setter
218 def position(self, new_position: Tuple[float, float]) -> None:
219 """Sets the new position of the bar."""
220 # Check if the position has changed. If so, change the bar's position
221 if new_position != self.position:
222 self._center_x, self._center_y = new_position
223 self.background_box.position = new_position
224 self.full_box.position = new_position
225
226 # Make sure full_box is to the left of the bar instead of the middle
227 self.full_box.left = self._center_x - (self._bar_width / 2) * self.scale
228
229 @property
230 def scale(self) -> float:
231 """Returns the scale of the bar."""
232 return self._scale
233
234 @scale.setter
235 def scale(self, value: float) -> None:
236 """Sets the new scale of the bar."""
237 # Check if the scale has changed. If so, change the bar's scale
238 if value != self.scale:
239 self._scale = value
240 self.background_box.scale = value
241 self.full_box.scale = value
242
243
244class MyGame(arcade.Window):
245 def __init__(self) -> None:
246 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
247
248 # Create sprite lists
249 self.bullet_list: arcade.SpriteList = arcade.SpriteList()
250 self.bar_list: arcade.SpriteList = arcade.SpriteList()
251 self.player_sprite_list: arcade.SpriteList = arcade.SpriteList()
252 self.enemy_sprite_list: arcade.SpriteList = arcade.SpriteList()
253
254 # Create player sprite
255 self.player_sprite = Player(self.bar_list)
256 self.player_sprite_list.append(self.player_sprite)
257
258 # Create enemy Sprite
259 self.enemy_sprite = arcade.Sprite(image_zombie_idle, scale=SPRITE_SCALING_ENEMY)
260 self.enemy_sprite_list.append(self.enemy_sprite)
261
262 # Create text objects
263 self.top_text: arcade.Text = arcade.Text(
264 "Dodge the bullets by moving the mouse!",
265 self.width // 2,
266 self.height - 50,
267 anchor_x="center",
268 )
269 self.bottom_text: arcade.Text = arcade.Text(
270 "When your health bar reaches zero, you lose!",
271 self.width // 2,
272 50,
273 anchor_x="center",
274 )
275 self.enemy_timer = 0
276
277 def setup(self) -> None:
278 """Set up the game and initialize the variables."""
279 # Setup player and enemy positions
280 self.player_sprite.position = self.width // 2, self.height // 4
281 self.enemy_sprite.position = self.width // 2, self.height // 2
282
283 # Set the background color
284 self.background_color = arcade.color.AMAZON
285
286 def on_draw(self) -> None:
287 """Render the screen."""
288 # Clear the screen. This command has to happen before we start drawing
289 self.clear()
290
291 # Draw all the sprites
292 self.player_sprite_list.draw()
293 self.enemy_sprite_list.draw()
294 self.bullet_list.draw()
295 self.bar_list.draw()
296
297 # Draw the text objects
298 self.top_text.draw()
299 self.bottom_text.draw()
300
301 def on_mouse_motion(self, x: float, y: float, dx: float, dy: float) -> None:
302 """Called whenever the mouse moves."""
303 self.player_sprite.position = x, y
304
305 def on_update(self, delta_time) -> None:
306 """Movement and game logic."""
307 # Check if the player is dead. If so, exit the game
308 if self.player_sprite.health <= 0:
309 arcade.exit()
310
311 # Increase the enemy's timer
312 self.enemy_timer += delta_time
313
314 # Update the player's indicator bar position
315 self.player_sprite.indicator_bar.position = (
316 self.player_sprite.center_x,
317 self.player_sprite.center_y + INDICATOR_BAR_OFFSET,
318 )
319
320 # Call updates on bullet sprites
321 self.bullet_list.on_update(delta_time)
322
323 # Check if the enemy can attack. If so, shoot a bullet from the
324 # enemy towards the player
325 if self.enemy_timer >= ENEMY_ATTACK_COOLDOWN:
326 self.enemy_timer = 0
327
328 # Create the bullet
329 bullet = Bullet()
330
331 # Set the bullet's position
332 bullet.position = self.enemy_sprite.position
333
334 # Calculate the trajectory.
335 # Zero degrees is up, 90 to the right.
336 # atan returns 0 degrees to the right instead of up, so shift by 90 degrees.
337 diff_x = self.player_sprite.center_x - self.enemy_sprite.center_x
338 diff_y = self.player_sprite.center_y - self.enemy_sprite.center_y
339 angle = -math.atan2(diff_y, diff_x) + 3.14 / 2
340 angle_deg = math.degrees(angle)
341
342 if angle_deg < 0:
343 angle_deg += 360
344
345 # Set the bullet's angle to face the player.
346 # Bullet graphic isn't pointed up, so rotate 90
347 bullet.angle = angle_deg - 90
348
349 # Give the bullet a velocity towards the player
350 bullet.change_x = math.sin(angle) * BULLET_SPEED
351 bullet.change_y = math.cos(angle) * BULLET_SPEED
352
353 # Add the bullet to the bullet list
354 self.bullet_list.append(bullet)
355
356 # Loop through each bullet
357 for existing_bullet in self.bullet_list:
358 # Check if the bullet has gone off-screen. If so, delete the bullet
359 if sprite_off_screen(existing_bullet):
360 existing_bullet.remove_from_sprite_lists()
361 continue
362
363 # Check if the bullet has hit the player
364 if arcade.check_for_collision(existing_bullet, self.player_sprite):
365 # Damage the player and remove the bullet
366 self.player_sprite.health -= BULLET_DAMAGE
367 existing_bullet.remove_from_sprite_lists()
368
369 # Set the player's indicator bar fullness
370 self.player_sprite.indicator_bar.fullness = (
371 self.player_sprite.health / PLAYER_HEALTH
372 )
373
374
375def main() -> None:
376 """Main Program."""
377 window = MyGame()
378 window.setup()
379 arcade.run()
380
381
382if __name__ == "__main__":
383 main()