Spaces:
Sleeping
Sleeping
""" | |
Asteroid Smasher | |
Shoot space rocks in this demo program created with | |
Python and the Arcade library. | |
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.asteroid_smasher | |
""" | |
import random | |
import math | |
import arcade | |
from typing import cast | |
STARTING_ASTEROID_COUNT = 3 | |
SCALE = 0.5 | |
OFFSCREEN_SPACE = 300 | |
SCREEN_WIDTH = 800 | |
SCREEN_HEIGHT = 600 | |
SCREEN_TITLE = "Asteroid Smasher" | |
LEFT_LIMIT = -OFFSCREEN_SPACE | |
RIGHT_LIMIT = SCREEN_WIDTH + OFFSCREEN_SPACE | |
BOTTOM_LIMIT = -OFFSCREEN_SPACE | |
TOP_LIMIT = SCREEN_HEIGHT + OFFSCREEN_SPACE | |
class TurningSprite(arcade.Sprite): | |
""" Sprite that sets its angle to the direction it is traveling in. """ | |
def update(self): | |
""" Move the sprite """ | |
super().update() | |
self.angle = math.degrees(math.atan2(self.change_y, self.change_x)) | |
class ShipSprite(arcade.Sprite): | |
""" | |
Sprite that represents our space ship. | |
Derives from arcade.Sprite. | |
""" | |
def __init__(self, filename, scale): | |
""" Set up the space ship. """ | |
# Call the parent Sprite constructor | |
super().__init__(filename, scale) | |
# Info on where we are going. | |
# Angle comes in automatically from the parent class. | |
self.thrust = 0 | |
self.speed = 0 | |
self.max_speed = 4 | |
self.drag = 0.05 | |
self.respawning = 0 | |
# Mark that we are respawning. | |
self.respawn() | |
def respawn(self): | |
""" | |
Called when we die and need to make a new ship. | |
'respawning' is an invulnerability timer. | |
""" | |
# If we are in the middle of respawning, this is non-zero. | |
self.respawning = 1 | |
self.center_x = SCREEN_WIDTH / 2 | |
self.center_y = SCREEN_HEIGHT / 2 | |
self.angle = 0 | |
def update(self): | |
""" | |
Update our position and other particulars. | |
""" | |
if self.respawning: | |
self.respawning += 1 | |
self.alpha = self.respawning | |
if self.respawning > 250: | |
self.respawning = 0 | |
self.alpha = 255 | |
if self.speed > 0: | |
self.speed -= self.drag | |
if self.speed < 0: | |
self.speed = 0 | |
if self.speed < 0: | |
self.speed += self.drag | |
if self.speed > 0: | |
self.speed = 0 | |
self.speed += self.thrust | |
if self.speed > self.max_speed: | |
self.speed = self.max_speed | |
if self.speed < -self.max_speed: | |
self.speed = -self.max_speed | |
self.change_x = -math.sin(math.radians(self.angle)) * self.speed | |
self.change_y = math.cos(math.radians(self.angle)) * self.speed | |
self.center_x += self.change_x | |
self.center_y += self.change_y | |
# If the ship goes off-screen, move it to the other side of the window | |
if self.right < 0: | |
self.left = SCREEN_WIDTH | |
if self.left > SCREEN_WIDTH: | |
self.right = 0 | |
if self.bottom < 0: | |
self.top = SCREEN_HEIGHT | |
if self.top > SCREEN_HEIGHT: | |
self.bottom = 0 | |
""" Call the parent class. """ | |
super().update() | |
class AsteroidSprite(arcade.Sprite): | |
""" Sprite that represents an asteroid. """ | |
def __init__(self, image_file_name, scale): | |
super().__init__(image_file_name, scale=scale) | |
self.size = 0 | |
def update(self): | |
""" Move the asteroid around. """ | |
super().update() | |
if self.center_x < LEFT_LIMIT: | |
self.center_x = RIGHT_LIMIT | |
if self.center_x > RIGHT_LIMIT: | |
self.center_x = LEFT_LIMIT | |
if self.center_y > TOP_LIMIT: | |
self.center_y = BOTTOM_LIMIT | |
if self.center_y < BOTTOM_LIMIT: | |
self.center_y = TOP_LIMIT | |
class MyGame(arcade.Window): | |
""" Main application class. """ | |
def __init__(self): | |
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) | |
self.game_over = False | |
# Sprite lists | |
self.player_sprite_list = arcade.SpriteList() | |
self.asteroid_list = arcade.SpriteList() | |
self.bullet_list = arcade.SpriteList() | |
self.ship_life_list = arcade.SpriteList() | |
# Set up the player | |
self.score = 0 | |
self.player_sprite = None | |
self.lives = 3 | |
# Sounds | |
self.laser_sound = arcade.load_sound(":resources:sounds/hurt5.wav") | |
self.hit_sound1 = arcade.load_sound(":resources:sounds/explosion1.wav") | |
self.hit_sound2 = arcade.load_sound(":resources:sounds/explosion2.wav") | |
self.hit_sound3 = arcade.load_sound(":resources:sounds/hit1.wav") | |
self.hit_sound4 = arcade.load_sound(":resources:sounds/hit2.wav") | |
# Text | |
self.text_score = None | |
self.text_asteroid_count = None | |
def start_new_game(self): | |
""" Set up the game and initialize the variables. """ | |
self.game_over = False | |
# Sprite lists | |
self.player_sprite_list = arcade.SpriteList() | |
self.asteroid_list = arcade.SpriteList() | |
self.bullet_list = arcade.SpriteList() | |
self.ship_life_list = arcade.SpriteList() | |
# Set up the player | |
self.score = 0 | |
self.player_sprite = ShipSprite(":resources:images/space_shooter/" | |
"playerShip1_orange.png", | |
SCALE) | |
self.player_sprite_list.append(self.player_sprite) | |
self.lives = 3 | |
# Set up the little icons that represent the player lives. | |
cur_pos = 10 | |
for i in range(self.lives): | |
life = arcade.Sprite(":resources:images/space_shooter/" | |
"playerLife1_orange.png", | |
SCALE) | |
life.center_x = cur_pos + life.width | |
life.center_y = life.height | |
cur_pos += life.width | |
self.ship_life_list.append(life) | |
# Make the asteroids | |
image_list = (":resources:images/space_shooter/meteorGrey_big1.png", | |
":resources:images/space_shooter/meteorGrey_big2.png", | |
":resources:images/space_shooter/meteorGrey_big3.png", | |
":resources:images/space_shooter/meteorGrey_big4.png") | |
for i in range(STARTING_ASTEROID_COUNT): | |
image_no = random.randrange(4) | |
enemy_sprite = AsteroidSprite(image_list[image_no], SCALE) | |
enemy_sprite.guid = "Asteroid" | |
enemy_sprite.center_y = random.randrange(BOTTOM_LIMIT, TOP_LIMIT) | |
enemy_sprite.center_x = random.randrange(LEFT_LIMIT, RIGHT_LIMIT) | |
enemy_sprite.change_x = random.random() * 2 - 1 | |
enemy_sprite.change_y = random.random() * 2 - 1 | |
enemy_sprite.change_angle = (random.random() - 0.5) * 2 | |
enemy_sprite.size = 4 | |
self.asteroid_list.append(enemy_sprite) | |
# Create new text objects with initial values | |
self.text_score = arcade.Text( | |
f"Score: {self.score}", | |
start_x=10, | |
start_y=70, | |
font_size=13, | |
) | |
self.text_asteroid_count = arcade.Text( | |
f"Asteroid Count: {len(self.asteroid_list)}", | |
start_x=10, | |
start_y=50, | |
font_size=13, | |
) | |
def on_draw(self): | |
""" | |
Render the screen. | |
""" | |
# This command has to happen before we start drawing | |
self.clear() | |
# Draw all the sprites. | |
self.asteroid_list.draw() | |
self.ship_life_list.draw() | |
self.bullet_list.draw() | |
self.player_sprite_list.draw() | |
# Draw the text | |
self.text_score.draw() | |
self.text_asteroid_count.draw() | |
def on_key_press(self, symbol, modifiers): | |
""" Called whenever a key is pressed. """ | |
# Shoot if the player hit the space bar and we aren't respawning. | |
if not self.player_sprite.respawning and symbol == arcade.key.SPACE: | |
bullet_sprite = TurningSprite(":resources:images/space_shooter/" | |
"laserBlue01.png", | |
SCALE) | |
bullet_sprite.guid = "Bullet" | |
bullet_speed = 13 | |
bullet_sprite.change_y = \ | |
math.cos(math.radians(self.player_sprite.angle)) * bullet_speed | |
bullet_sprite.change_x = \ | |
-math.sin(math.radians(self.player_sprite.angle)) \ | |
* bullet_speed | |
bullet_sprite.center_x = self.player_sprite.center_x | |
bullet_sprite.center_y = self.player_sprite.center_y | |
bullet_sprite.update() | |
self.bullet_list.append(bullet_sprite) | |
arcade.play_sound(self.laser_sound, speed=random.random() * 3 + 0.5) | |
if symbol == arcade.key.LEFT: | |
self.player_sprite.change_angle = 3 | |
elif symbol == arcade.key.RIGHT: | |
self.player_sprite.change_angle = -3 | |
elif symbol == arcade.key.UP: | |
self.player_sprite.thrust = 0.15 | |
elif symbol == arcade.key.DOWN: | |
self.player_sprite.thrust = -.2 | |
def on_key_release(self, symbol, modifiers): | |
""" Called whenever a key is released. """ | |
if symbol == arcade.key.LEFT: | |
self.player_sprite.change_angle = 0 | |
elif symbol == arcade.key.RIGHT: | |
self.player_sprite.change_angle = 0 | |
elif symbol == arcade.key.UP: | |
self.player_sprite.thrust = 0 | |
elif symbol == arcade.key.DOWN: | |
self.player_sprite.thrust = 0 | |
def split_asteroid(self, asteroid: AsteroidSprite): | |
""" Split an asteroid into chunks. """ | |
x = asteroid.center_x | |
y = asteroid.center_y | |
self.score += 1 | |
if asteroid.size == 4: | |
for i in range(3): | |
image_no = random.randrange(2) | |
image_list = [":resources:images/space_shooter/meteorGrey_med1.png", | |
":resources:images/space_shooter/meteorGrey_med2.png"] | |
enemy_sprite = AsteroidSprite(image_list[image_no], | |
SCALE * 1.5) | |
enemy_sprite.center_y = y | |
enemy_sprite.center_x = x | |
enemy_sprite.change_x = random.random() * 2.5 - 1.25 | |
enemy_sprite.change_y = random.random() * 2.5 - 1.25 | |
enemy_sprite.change_angle = (random.random() - 0.5) * 2 | |
enemy_sprite.size = 3 | |
self.asteroid_list.append(enemy_sprite) | |
self.hit_sound1.play() | |
elif asteroid.size == 3: | |
for i in range(3): | |
image_no = random.randrange(2) | |
image_list = [":resources:images/space_shooter/meteorGrey_small1.png", | |
":resources:images/space_shooter/meteorGrey_small2.png"] | |
enemy_sprite = AsteroidSprite(image_list[image_no], | |
SCALE * 1.5) | |
enemy_sprite.center_y = y | |
enemy_sprite.center_x = x | |
enemy_sprite.change_x = random.random() * 3 - 1.5 | |
enemy_sprite.change_y = random.random() * 3 - 1.5 | |
enemy_sprite.change_angle = (random.random() - 0.5) * 2 | |
enemy_sprite.size = 2 | |
self.asteroid_list.append(enemy_sprite) | |
self.hit_sound2.play() | |
elif asteroid.size == 2: | |
for i in range(3): | |
image_no = random.randrange(2) | |
image_list = [":resources:images/space_shooter/meteorGrey_tiny1.png", | |
":resources:images/space_shooter/meteorGrey_tiny2.png"] | |
enemy_sprite = AsteroidSprite(image_list[image_no], | |
SCALE * 1.5) | |
enemy_sprite.center_y = y | |
enemy_sprite.center_x = x | |
enemy_sprite.change_x = random.random() * 3.5 - 1.75 | |
enemy_sprite.change_y = random.random() * 3.5 - 1.75 | |
enemy_sprite.change_angle = (random.random() - 0.5) * 2 | |
enemy_sprite.size = 1 | |
self.asteroid_list.append(enemy_sprite) | |
self.hit_sound3.play() | |
elif asteroid.size == 1: | |
self.hit_sound4.play() | |
def on_update(self, x): | |
""" Move everything """ | |
if not self.game_over: | |
self.asteroid_list.update() | |
self.bullet_list.update() | |
self.player_sprite_list.update() | |
for bullet in self.bullet_list: | |
asteroids = arcade.check_for_collision_with_list(bullet, | |
self.asteroid_list) | |
for asteroid in asteroids: | |
# expected AsteroidSprite, got Sprite instead | |
self.split_asteroid(cast(AsteroidSprite, asteroid)) | |
asteroid.remove_from_sprite_lists() | |
bullet.remove_from_sprite_lists() | |
# Remove bullet if it goes off-screen | |
size = max(bullet.width, bullet.height) | |
if bullet.center_x < 0 - size: | |
bullet.remove_from_sprite_lists() | |
if bullet.center_x > SCREEN_WIDTH + size: | |
bullet.remove_from_sprite_lists() | |
if bullet.center_y < 0 - size: | |
bullet.remove_from_sprite_lists() | |
if bullet.center_y > SCREEN_HEIGHT + size: | |
bullet.remove_from_sprite_lists() | |
if not self.player_sprite.respawning: | |
asteroids = arcade.check_for_collision_with_list(self.player_sprite, | |
self.asteroid_list) | |
if len(asteroids) > 0: | |
if self.lives > 0: | |
self.lives -= 1 | |
self.player_sprite.respawn() | |
self.split_asteroid(cast(AsteroidSprite, asteroids[0])) | |
asteroids[0].remove_from_sprite_lists() | |
self.ship_life_list.pop().remove_from_sprite_lists() | |
print("Crash") | |
else: | |
self.game_over = True | |
print("Game over") | |
# Update the text objects | |
self.text_score.text = f"Score: {self.score}" | |
self.text_asteroid_count.text = f"Asteroid Count: {len(self.asteroid_list)}" | |
def main(): | |
""" Start the game """ | |
window = MyGame() | |
window.start_new_game() | |
arcade.run() | |
if __name__ == "__main__": | |
main() |