Sprite Rotation Around a Tank#
This example uses a player-controlled tank to demonstrate the difference between the right and wrong way of rotating a turret around an attachment point. In both modes, the tank’s barrel follows the mouse. The turret sprite is ommitted to keep the rotation visible.
See the docstring, comments, and on screen instructions for further info.
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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | """ Sprite Rotation Around a Point, With A Tank Games often include elements that rotate toward targets. Common examples include gun turrets on vehicles and towers. In 2D games, these rotating parts are usually implemented as sprites that move relative to whatever they're attached to. There's a catch to this: you have to rotate these parts around their attachment points rather than the centers of their sprites. Otherwise, the rotation will look wrong! To illustrate the difference, this example uses a player-controllable tank with a barrel that follows the mouse. You can press P to switch between two ways of rotating the barrel: 1. Correctly, with the barrel's rear against the tank's center 2. Incorrectly, around the barrel's center pinned to the tank's Artwork from https://kenney.nl If Python and Arcade are installed, this example can be run from the command line with: python -m arcade.examples.sprite_rotate_around_tank """ import arcade import math TANK_SPEED_PIXELS = 64 # How many pixels per second the tank travels TANK_TURN_SPEED_DEGREES = 70 # How fast the tank's body can turn # This is half the length of the barrel sprite. # We use it to ensure the barrel's rear sits in the middle of the tank TANK_BARREL_LENGTH_HALF = 15 SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_MIDDLE = (SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2) SCREEN_TITLE = "Rotating Tank Example" # These paths are built-in resources included with arcade TANK_BODY = ":resources:images/topdown_tanks/tankBody_dark_outline.png" TANK_BARREL = ":resources:images/topdown_tanks/tankDark_barrel3_outline.png" class RotatingSprite(arcade.Sprite): """ Sprite subclass which can be rotated around a point. This version of the class always changes the angle of the sprite. Other games might not rotate the sprite. For example, moving platforms in a platformer wouldn't rotate. """ def rotate_around_point(self, point: arcade.Point, degrees: float): """ Rotate the sprite around a point by the set amount of degrees :param point: The point that the sprite will rotate about :param degrees: How many degrees to rotate the sprite """ # Make the sprite turn as its position is moved self.angle += degrees # Move the sprite along a circle centered around the passed point self.position = arcade.rotate_point( self.center_x, self.center_y, point[0], point[1], degrees) class ExampleWindow(arcade.Window): def __init__(self): super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) # Set Background to be green self.background_color = arcade.csscolor.SEA_GREEN # The tank and barrel sprite self.tank = arcade.Sprite(TANK_BODY) self.tank.position = SCREEN_MIDDLE self.barrel = RotatingSprite(TANK_BARREL) self.barrel.position =\ SCREEN_MIDDLE[0], SCREEN_MIDDLE[1] - TANK_BARREL_LENGTH_HALF self.tank_direction = 0.0 # Forward & backward throttle self.tank_turning = 0.0 # Turning strength to the left or right self.mouse_pos = 0, 0 self.tank_sprite_list = arcade.SpriteList() self.tank_sprite_list.extend([self.tank, self.barrel]) self._correct = True self.correct_text = arcade.Text( "Turret Rotation is Correct, Press P to Switch", SCREEN_MIDDLE[0], SCREEN_HEIGHT - 25, anchor_x='center') self.control_text = arcade.Text( "WASD to move tank, Mouse to aim", SCREEN_MIDDLE[0], 15, anchor_x='center') def on_draw(self): self.clear() self.tank_sprite_list.draw() self.control_text.draw() self.correct_text.draw() def on_update(self, delta_time: float): self.move_tank(delta_time) def move_tank(self, delta_time): """ Perform all calculations for moving the tank's body and barrel """ # Rotate the tank's body in place without changing position # We'll rotate the barrel after updating the entire tank's x & y self.tank.angle += TANK_TURN_SPEED_DEGREES\ * self.tank_turning * delta_time # Calculate how much the tank should move forward or back move_magnitude = self.tank_direction * TANK_SPEED_PIXELS * delta_time x_dir = math.cos(self.tank.radians - math.pi / 2) * move_magnitude y_dir = math.sin(self.tank.radians - math.pi / 2) * move_magnitude # Move the tank's body self.tank.position =\ self.tank.center_x + x_dir,\ self.tank.center_y + y_dir # Move the barrel with the body self.barrel.position =\ self.barrel.center_x + x_dir,\ self.barrel.center_y + y_dir # Begin rotating the barrel by finding the angle to the mouse mouse_angle = arcade.get_angle_degrees( self.tank.center_y, self.tank.center_x, self.mouse_pos[1], self.mouse_pos[0]) # Compensate for the vertical orientation of the barrel texture # This could be skipped if the texture faced right instead mouse_angle += 90 if self.correct: # Rotate the barrel sprite with one end at the tank's center # Subtract the old angle to get the change in angle angle_change = mouse_angle - self.barrel.angle self.barrel.rotate_around_point(self.tank.position, angle_change) else: # Swivel the barrel with its center aligned with the body's self.barrel.angle = mouse_angle def on_key_press(self, symbol: int, modifiers: int): if symbol == arcade.key.W: self.tank_direction += 1 elif symbol == arcade.key.S: self.tank_direction -= 1 elif symbol == arcade.key.A: self.tank_turning += 1 elif symbol == arcade.key.D: self.tank_turning -= 1 elif symbol == arcade.key.P: self.correct = not self.correct self.correct_text.text =\ f"Turret Rotation is "\ f"{'Correct' if self.correct else 'Incorrect'},"\ f" Press P to Switch" def on_key_release(self, symbol: int, modifiers: int): if symbol == arcade.key.W: self.tank_direction -= 1 elif symbol == arcade.key.S: self.tank_direction += 1 elif symbol == arcade.key.A: self.tank_turning -= 1 elif symbol == arcade.key.D: self.tank_turning += 1 def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): self.mouse_pos = x, y @property def correct(self): return self._correct @correct.setter def correct(self, correct: bool): """ Move the tank's barrel between correct rotation and incorrect positions """ self._correct = correct if correct: angle = arcade.get_angle_radians( self.tank.center_y, self.tank.center_x, self.mouse_pos[1], self.mouse_pos[0]) self.barrel.position =\ self.barrel.center_x + math.cos(angle) * TANK_BARREL_LENGTH_HALF,\ self.barrel.center_y + math.sin(angle) * TANK_BARREL_LENGTH_HALF else: self.barrel.position = self.tank.position def main(): window = ExampleWindow() window.run() if __name__ == '__main__': main() |