按键盘移动,向鼠标射击#

这个例子使用玩家控制的坦克来演示围绕附着点旋转炮塔的正确和错误方式之间的区别。在这两种模式下,坦克的炮管都跟随鼠标移动。为了保持旋转可见,将省略转塔精灵。
有关详细信息,请参阅文档字符串、备注和屏幕说明。
sprite_rotation_around_tank.py#
1"""
2Sprite Rotation Around a Point, With A Tank
3
4Games often include elements that rotate toward targets. Common
5examples include gun turrets on vehicles and towers. In 2D games,
6these rotating parts are usually implemented as sprites that move
7relative to whatever they're attached to.
8
9There's a catch to this: you have to rotate these parts around their
10attachment points rather than the centers of their sprites. Otherwise,
11the rotation will look wrong!
12
13To illustrate the difference, this example uses a player-controllable
14tank with a barrel that follows the mouse. You can press P to switch
15between two ways of rotating the barrel:
161. Correctly, with the barrel's rear against the tank's center
172. Incorrectly, around the barrel's center pinned to the tank's
18
19Artwork from https://kenney.nl
20
21If Python and Arcade are installed, this example can be run from the command line with:
22python -m arcade.examples.sprite_rotate_around_tank
23"""
24import math
25import arcade
26from arcade.types import Point
27from arcade.math import (
28 get_angle_radians,
29 rotate_point,
30 get_angle_degrees,
31)
32
33TANK_SPEED_PIXELS = 64 # How many pixels per second the tank travels
34TANK_TURN_SPEED_DEGREES = 70 # How fast the tank's body can turn
35
36
37# This is half the length of the barrel sprite.
38# We use it to ensure the barrel's rear sits in the middle of the tank
39TANK_BARREL_LENGTH_HALF = 15
40
41
42SCREEN_WIDTH = 800
43SCREEN_HEIGHT = 600
44SCREEN_MIDDLE = (SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
45
46
47SCREEN_TITLE = "Rotating Tank Example"
48
49
50# These paths are built-in resources included with arcade
51TANK_BODY = ":resources:images/topdown_tanks/tankBody_dark_outline.png"
52TANK_BARREL = ":resources:images/topdown_tanks/tankDark_barrel3_outline.png"
53
54
55class RotatingSprite(arcade.Sprite):
56 """
57 Sprite subclass which can be rotated around a point.
58
59 This version of the class always changes the angle of the sprite.
60 Other games might not rotate the sprite. For example, moving
61 platforms in a platformer wouldn't rotate.
62 """
63 def rotate_around_point(self, point: Point, degrees: float):
64 """
65 Rotate the sprite around a point by the set amount of degrees
66
67 :param point: The point that the sprite will rotate about
68 :param degrees: How many degrees to rotate the sprite
69 """
70
71 # Make the sprite turn as its position is moved
72 self.angle += degrees
73
74 # Move the sprite along a circle centered around the passed point
75 self.position = rotate_point(
76 self.center_x, self.center_y,
77 point[0], point[1], degrees)
78
79
80class ExampleWindow(arcade.Window):
81
82 def __init__(self):
83 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
84
85 # Set Background to be green
86 self.background_color = arcade.csscolor.SEA_GREEN
87
88 # The tank and barrel sprite
89 self.tank = arcade.Sprite(TANK_BODY)
90 self.tank.position = SCREEN_MIDDLE
91
92 self.barrel = RotatingSprite(TANK_BARREL)
93 self.barrel.position =\
94 SCREEN_MIDDLE[0], SCREEN_MIDDLE[1] - TANK_BARREL_LENGTH_HALF
95
96 self.tank_direction = 0.0 # Forward & backward throttle
97 self.tank_turning = 0.0 # Turning strength to the left or right
98
99 self.mouse_pos = 0, 0
100
101 self.tank_sprite_list = arcade.SpriteList()
102 self.tank_sprite_list.extend([self.tank, self.barrel])
103
104 self._correct = True
105 self.correct_text = arcade.Text(
106 "Turret Rotation is Correct, Press P to Switch",
107 SCREEN_MIDDLE[0], SCREEN_HEIGHT - 25,
108 anchor_x='center')
109
110 self.control_text = arcade.Text(
111 "WASD to move tank, Mouse to aim",
112 SCREEN_MIDDLE[0], 15,
113 anchor_x='center')
114
115 def on_draw(self):
116 self.clear()
117 self.tank_sprite_list.draw()
118
119 self.control_text.draw()
120 self.correct_text.draw()
121
122 def on_update(self, delta_time: float):
123 self.move_tank(delta_time)
124
125 def move_tank(self, delta_time):
126 """
127 Perform all calculations for moving the tank's body and barrel
128 """
129
130 # Rotate the tank's body in place without changing position
131 # We'll rotate the barrel after updating the entire tank's x & y
132 self.tank.angle += TANK_TURN_SPEED_DEGREES\
133 * self.tank_turning * delta_time
134
135 # Calculate how much the tank should move forward or back
136 move_magnitude = self.tank_direction * TANK_SPEED_PIXELS * delta_time
137 x_dir = math.sin(self.tank.radians) * move_magnitude
138 y_dir = math.cos(self.tank.radians) * move_magnitude
139
140 # Move the tank's body
141 self.tank.position =\
142 self.tank.center_x + x_dir,\
143 self.tank.center_y + y_dir
144
145 # Move the barrel with the body
146 self.barrel.position =\
147 self.barrel.center_x + x_dir,\
148 self.barrel.center_y + y_dir
149
150 # Begin rotating the barrel by finding the angle to the mouse
151 mouse_angle = get_angle_degrees(
152 self.tank.center_x, self.tank.center_y,
153 self.mouse_pos[0], self.mouse_pos[1])
154
155 # Compensate for the flipped orientation of the barrel texture
156 # This could be skipped if the texture faced up instead
157 mouse_angle += 180
158
159 if self.correct:
160 # Rotate the barrel sprite with one end at the tank's center
161
162 # Subtract the old angle to get the change in angle
163 angle_change = mouse_angle - self.barrel.angle
164
165 self.barrel.rotate_around_point(self.tank.position, angle_change)
166 else:
167 # Swivel the barrel with its center aligned with the body's
168 self.barrel.angle = mouse_angle
169
170 def on_key_press(self, symbol: int, modifiers: int):
171 if symbol == arcade.key.W:
172 self.tank_direction += 1
173 elif symbol == arcade.key.S:
174 self.tank_direction -= 1
175 elif symbol == arcade.key.A:
176 self.tank_turning -= 1
177 elif symbol == arcade.key.D:
178 self.tank_turning += 1
179 elif symbol == arcade.key.P:
180 self.correct = not self.correct
181
182 self.correct_text.text =\
183 f"Turret Rotation is "\
184 f"{'Correct' if self.correct else 'Incorrect'},"\
185 f" Press P to Switch"
186
187 def on_key_release(self, symbol: int, modifiers: int):
188 if symbol == arcade.key.W:
189 self.tank_direction -= 1
190 elif symbol == arcade.key.S:
191 self.tank_direction += 1
192 elif symbol == arcade.key.A:
193 self.tank_turning += 1
194 elif symbol == arcade.key.D:
195 self.tank_turning -= 1
196
197 def on_mouse_motion(self, x: int, y: int, dx: int, dy: int):
198 self.mouse_pos = x, y
199
200 @property
201 def correct(self):
202 return self._correct
203
204 @correct.setter
205 def correct(self, correct: bool):
206 """
207 Move the tank's barrel between correct rotation and incorrect positions
208 """
209 self._correct = correct
210 if correct:
211 angle = get_angle_radians(
212 self.tank.center_y, self.tank.center_x,
213 self.mouse_pos[1], self.mouse_pos[0])
214
215 self.barrel.position = (
216 self.barrel.center_x + math.sin(angle) * TANK_BARREL_LENGTH_HALF,
217 self.barrel.center_y + math.cos(angle) * TANK_BARREL_LENGTH_HALF,
218 )
219
220 else:
221 self.barrel.position = self.tank.position
222
223
224def main():
225 window = ExampleWindow()
226 window.run()
227
228
229if __name__ == '__main__':
230 main()