mirror of
https://git.davidovski.xyz/tkinter-space-game.git
synced 2024-08-15 00:43:41 +00:00
310 lines
8.8 KiB
Python
310 lines
8.8 KiB
Python
from random import randint, random
|
|
from tkinter import Canvas, PhotoImage, Tk
|
|
from typing import List
|
|
|
|
from config import Config
|
|
from font import Font
|
|
from frame_counter import FrameCounter
|
|
from inputs import InputController
|
|
from sprite import Sprite
|
|
from textures import TextureFactory
|
|
|
|
|
|
class Game:
|
|
"""A generic game object"""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialise the game
|
|
"""
|
|
self.win = Tk()
|
|
game_width, game_height = (
|
|
Config.WIDTH*Config.SCALE, Config.HEIGHT*Config.SCALE
|
|
)
|
|
self.w, self.h = Config.WIDTH, Config.HEIGHT
|
|
|
|
self.win.geometry(f"{game_width}x{game_height}")
|
|
self.canvas = Canvas(self.win, width=game_width,
|
|
height=game_height, bg="#000")
|
|
self.canvas.pack()
|
|
|
|
self.texture_factory = TextureFactory(scale=Config.SCALE)
|
|
self.effect_player = EffectPlayer(self)
|
|
self.frame_counter = FrameCounter(self.canvas, Config.FPS)
|
|
|
|
self.inputs = InputController(self)
|
|
self.sprites = []
|
|
|
|
self.score = 0
|
|
|
|
self.alpha = 0
|
|
|
|
def start(self):
|
|
"""Start the game"""
|
|
self.loop()
|
|
self.win.mainloop()
|
|
|
|
def tick(self):
|
|
"""Update the game's sprites"""
|
|
for sprite in self.sprites:
|
|
sprite.tick()
|
|
self.effect_player.tick()
|
|
|
|
def loop(self):
|
|
"""Loop the game at a set framerate"""
|
|
self.alpha += 1
|
|
self.tick()
|
|
self.frame_counter.next_frame(self.loop)
|
|
|
|
def clear_all(self):
|
|
"""Remove all game sprites"""
|
|
for sprite in self.sprites:
|
|
sprite.destroy()
|
|
self.sprites = []
|
|
|
|
|
|
class GameSprite(Sprite):
|
|
"""A sprite which belongs to a game"""
|
|
|
|
def __init__(self, game: Game, image: PhotoImage):
|
|
"""Initialise the sprite
|
|
|
|
:param game: The game which this belongs to
|
|
:type game: Game
|
|
:param image: The image to use for the sprite
|
|
:type image: PhotoImage
|
|
"""
|
|
self.game = game
|
|
super().__init__(game.canvas, image, (0, 0))
|
|
|
|
def move(self, x, y):
|
|
"""Move the sprite by a certain amount
|
|
|
|
:param x: Amount of pixels to move right
|
|
:param y: Amount of pixels to move down
|
|
"""
|
|
# if the object needs to move less than a pixel
|
|
# only move it every few frames to create this effect
|
|
if abs(x) >= 1:
|
|
self.x += x
|
|
elif x != 0:
|
|
if self.game.alpha % (1/x) == 0:
|
|
self.x += 1
|
|
|
|
if abs(y) >= 1:
|
|
self.y += y
|
|
elif y != 0:
|
|
if self.game.alpha % (1/y) == 0:
|
|
self.y += 1
|
|
|
|
self.update_position()
|
|
|
|
|
|
class GameEffect(GameSprite):
|
|
"""An effect that can be played within game"""
|
|
|
|
def __init__(self, game: Game, image: PhotoImage,
|
|
duration=10, momentum=(0, 0)):
|
|
"""Initialise the game effect
|
|
|
|
:param game: The game which this belongs to
|
|
:type game: Game
|
|
:param image: The image to use for this
|
|
:type image: PhotoImage
|
|
:param duration: How long this effect should last for
|
|
:param momentum: Which direction to move this effect
|
|
"""
|
|
self.start_time = game.alpha
|
|
self.duration = duration
|
|
self.velocity_x, self.velocity_y = momentum
|
|
super().__init__(game, image)
|
|
|
|
def tick(self):
|
|
"""Move the effect by its momentum and remove it if its over"""
|
|
super().tick()
|
|
self.move(self.velocity_x, self.velocity_y)
|
|
|
|
alpha = self.game.alpha - self.start_time
|
|
if self.duration != -1 and alpha > self.duration:
|
|
self.destroy()
|
|
|
|
|
|
class AnimatedEffect(GameEffect):
|
|
"""An effect which involves animating an image"""
|
|
|
|
def __init__(self, game: Game, images: List[PhotoImage],
|
|
frame_time=1, momentum=(0, 0)):
|
|
"""Initialise the effect
|
|
|
|
:param game: The game which this belongs to
|
|
:type game: Game
|
|
:param images: The images to use for this animation
|
|
:type images: List[PhotoImage]
|
|
:param frame_time: Length of each frame of the animation
|
|
:param momentum: Direction to move this effect
|
|
"""
|
|
self.start_time = game.alpha
|
|
self.frame_time = frame_time
|
|
self.images = images
|
|
self.frame_start = game.alpha
|
|
super().__init__(game, images[0], duration=len(
|
|
images)*frame_time, momentum=momentum)
|
|
|
|
def tick(self):
|
|
"""Animate the effect"""
|
|
super().tick()
|
|
|
|
alpha = self.game.alpha - self.start_time
|
|
|
|
i = int(alpha // self.frame_time)
|
|
if i < len(self.images):
|
|
self.set_image(self.images[i])
|
|
else:
|
|
self.destroy()
|
|
|
|
|
|
class EffectPlayer:
|
|
"""An object which concerns itself with managing the effects"""
|
|
|
|
def __init__(self, game: Game) -> None:
|
|
"""Initialise the
|
|
|
|
:param game: The game which this belongs to
|
|
:type game: Game
|
|
"""
|
|
self.sprites = []
|
|
self.game = game
|
|
self.explosion_frames = []
|
|
self.star_image: PhotoImage
|
|
|
|
def load_textures(self):
|
|
"""Load effect textures"""
|
|
|
|
self.explosion_frames = [
|
|
self.game.texture_factory.get_image(f"explosion{i+1}")
|
|
for i in range(3)
|
|
]
|
|
self.star_image = self.game.texture_factory.get_image("star")
|
|
|
|
def tick(self):
|
|
"""Update all effects"""
|
|
for sprite in self.sprites:
|
|
sprite.tick()
|
|
|
|
self.sprites = Sprite.remove_destroyed(self.sprites)
|
|
|
|
def create_stars(self):
|
|
"""Initialise the stars in the background"""
|
|
for _ in range(100):
|
|
self.create_star(True)
|
|
|
|
def create_star(self, new=False):
|
|
"""Add a star to the background
|
|
|
|
:param new: Whether this star should be added at
|
|
the top of the screen or anywhere
|
|
"""
|
|
x = randint(0, self.game.w)
|
|
if new:
|
|
y = randint(0, self.game.h)
|
|
else:
|
|
y = -1
|
|
|
|
speed = randint(1, 4) * 0.1
|
|
duration = 2*self.game.h / speed
|
|
|
|
star = GameEffect(
|
|
self.game,
|
|
self.star_image,
|
|
duration=int(duration),
|
|
momentum=(0, speed)
|
|
)
|
|
star.set_pos((x, y))
|
|
|
|
star.send_to_back()
|
|
star.show()
|
|
|
|
self.sprites.append(star)
|
|
|
|
def create_explosion(self, position=(0, 0)):
|
|
"""Create an explosion effect
|
|
|
|
:param position: location of the explosion
|
|
"""
|
|
for _ in range(randint(1, 3)):
|
|
m = ((random()*2)-1, (random()*2)-1)
|
|
explosion_sprite = AnimatedEffect(
|
|
self.game, self.explosion_frames, frame_time=5, momentum=m)
|
|
explosion_sprite.set_pos(position)
|
|
explosion_sprite.show()
|
|
|
|
self.sprites.append(explosion_sprite)
|
|
|
|
def splash_text(self, text, duration=50):
|
|
"""splash_text.
|
|
|
|
:param text:
|
|
:param duration:
|
|
"""
|
|
text_img = Font.load_text(self.game.texture_factory, text)
|
|
position = (
|
|
(self.game.w-Font.FONT_WIDTH*len(text)) // 2,
|
|
(self.game.h-Font.FONT_SIZE) // 3
|
|
)
|
|
|
|
text_sprite = GameEffect(
|
|
self.game, text_img, duration=duration)
|
|
text_sprite.set_pos(position)
|
|
text_sprite.show()
|
|
self.sprites.append(text_sprite)
|
|
|
|
|
|
class DamageableSprite(GameSprite):
|
|
"""Sprite with health points """
|
|
|
|
def __init__(self, game: Game, image_name: str, hp=3):
|
|
"""Initialise the sprite
|
|
|
|
:param game: The game which this belongs to
|
|
:type game: Game
|
|
:param image_name: The name of the image to use for this sprite
|
|
:type image_name: str
|
|
:param hp: The number of hit points this sprite spawns with
|
|
"""
|
|
self.image = game.texture_factory.get_image(image_name)
|
|
self.white_image = game.texture_factory.get_image(
|
|
f"{image_name}:white")
|
|
|
|
self.hp = hp
|
|
self.animation_frame = 0
|
|
|
|
super().__init__(game, self.image)
|
|
|
|
def damage(self, amount=1):
|
|
"""Decrease number of hit points by an amount
|
|
|
|
:param amount:
|
|
"""
|
|
if not self.destroyed:
|
|
self.hp -= amount
|
|
self.animation_frame = 5
|
|
if self.hp <= 0:
|
|
self.hp = 0
|
|
self.destroy()
|
|
self.game.effect_player.create_explosion(self.get_pos())
|
|
|
|
def tick(self):
|
|
"""Update the sprite"""
|
|
super().tick()
|
|
if self.animation_frame > 0:
|
|
self.animation_frame -= 1
|
|
if self.animation_frame % 2 == 0:
|
|
self.set_image(self.image)
|
|
else:
|
|
self.set_image(self.white_image)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("!!!")
|
|
print("This is not the main file!")
|
|
print("Pleae run\n\tpython main.py\ninstead!")
|
|
print("!!!")
|