added docstrings to every file and function. (with claude, checked it)

tower and game_play dont meet pylint check yet.
This commit is contained in:
2026-07-15 22:16:18 +02:00
parent 367a839c8a
commit 821aa2e8a4
9 changed files with 428 additions and 109 deletions
+3
View File
@@ -655,3 +655,6 @@ init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[MASTER]
extension-pkg-allow-list=pygame
+15 -11
View File
@@ -6,6 +6,7 @@ import pygame as pg
from game.util import tile_to_pixel
class Enemy(pg.sprite.Sprite):
# pylint: disable=too-many-instance-attributes
"""A single enemy that follows waypoints
across the tilemap.
@@ -13,31 +14,33 @@ class Enemy(pg.sprite.Sprite):
game (Game): Whole Game instances, contains everything that the game class has.
speed (float): Movement speed in pixels per update.
health (float): Current health points.
angle (float): Current facing angle in degrees.
angle (float): Current facing angle in degrees. (initalized on 0 degress)
"""
def __init__(self, game, speed, health):
super().__init__()
self.game = game
# coordinate on where the enemy should move to (in map json)
self.waypoints = self.game.tilemap.waypoints
self.speed = speed
self.angle = 0
self.health = health
#starting waypoint index
self.target_index = 1
self.target = None #iititalise target var
self.angle = 0
self.healthbar = HealthBar(
18,
4,
self.health
)
#initalise objects need to be initalized last (depend on ealier dependencies)
self.image = pg.transform.rotate(self.game.assets['enemy'].copy(), self.angle)
self.rect = self.image.get_rect()
self.pos = pg.Vector2(
tile_to_pixel(self.waypoints[0], self.game.tilemap.tile_size, True)
)
self.target_index = 1
self.rect.center = self.pos
self.healthbar = HealthBar(
18,
5,
self.health
)
def update(self):
"""Advance movement and update rotation each frame."""
@@ -133,6 +136,7 @@ class EnemyGroup(pg.sprite.Group):
enemy.update()
class HealthBar():
# pylint: disable=too-few-public-methods
"""A simple two-rect health bar drawn in world space.
Attributes:
+24 -18
View File
@@ -1,21 +1,28 @@
"""
game class
"""
import pygame
"""Game module - core game loop, screen management, and shared asset loading."""
import pygame_gui
import pygame
from game.util import *
from game.util import load_image, load_images, load_images_alpha, Animation
from game.tilemap import Tilemap
from game.screens.main_menu import MainMenu
from game.screens.game_play import GamePlay
#constants
FPS=60
WINDOW_SIZE=(960,540)
TILE=16
class Game:
"""Central game class managing the loop, screens, and shared resources.
Attributes:
window (pygame.Surface): The main display surface.
clock (pygame.time.Clock): Controls the frame rate.
ui_manager (pygame_gui.UIManager): Manages all pygame_gui UI elements.
tilemap (Tilemap): The active tilemap instance.
assets (dict): Shared images and animations, keyed by name.
screens (dict): All available screens, keyed by name.
current_screen: The screen currently being updated and drawn.
"""
def __init__(self):
pygame.init()
@@ -45,40 +52,39 @@ class Game:
self.current_screen = self.screens["menu"]
def change_screen(self, name):
"""Transition to a different screen by name.
Hides the current screen and shows the requested one.
Args:
name (str): Key of the screen to switch to (e.g. ``"menu"``, ``"game"``).
"""
self.current_screen.hide()
self.current_screen = self.screens[name]
self.current_screen.show()
def run(self):
"""Start the main game loop and block until the window is closed.
Each iteration: processes events, updates state, and draws the frame.
"""
running = True
while running:
dt = self.clock.tick(FPS) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
self.ui_manager.process_events(event)
self.current_screen.process_event(event)
self.current_screen.update(dt)
self.ui_manager.update(dt)
self.window.fill((20, 20, 20))
self.current_screen.draw(self.window)
self.ui_manager.draw_ui(self.window)
pygame.display.update()
pygame.quit()
+29 -4
View File
@@ -1,23 +1,48 @@
"""Base screen module - shared interface for all game screens."""
class BaseScreen:
"""Abstract base class defining the interface for all game screens.
Subclasses override :meth:`process_event`, :meth:`update`, and :meth:`draw`
to implement screen-specific behaviour. UI elements added to ``self.elements``
are shown and hidden automatically when the screen is switched.
Attributes:
game (Game): The central game instance.
elements (list): pygame_gui elements managed by this screen.
"""
def __init__(self, game):
self.game = game
self.elements = []
def process_event(self, event):
pass
"""Handle a single pygame event.
Args:
event (pygame.event.Event): The event to process.
"""
def update(self, dt):
pass
"""Update screen state for the current frame.
Args:
dt (float): Time elapsed since the last frame in seconds.
"""
def draw(self, surface):
pass
"""Draw the screen onto the given surface.
Args:
surface (pygame.Surface): Target surface to draw onto.
"""
def show(self):
"""Make all UI elements on this screen visible."""
for element in self.elements:
element.show()
def hide(self):
"""Hide all UI elements on this screen."""
for element in self.elements:
element.hide()
+89 -13
View File
@@ -1,19 +1,34 @@
"""Gameplay screen - map rendering, camera control, tower placement, and enemy waves."""
import pygame_gui as pgi
import pygame as pg
from game.screens.base_screen import BaseScreen
from game.enemy import Enemy, EnemyGroup
from game.camera import Camera
from game.tower import Tower, TowerGroup
from game.util import *
import pygame as pg
import pygame_gui as pgi
from game.util import pixel_to_tile
class GamePlay(BaseScreen):
# pylint: disable=too-many-instance-attributes
"""The main gameplay screen handling the game loop, input, and rendering.
Attributes:
game_surface (pg.Surface): The subsurface used for the game viewport.
camera (Camera): Controls zoom and pan over the tilemap.
movment (list[bool]): Movement flags for [left, right, up, down] (WASD).
selected (Tower | None): The currently selected tower, or ``None``.
bought (bool): Whether the player is currently placing a tower.
tile_pos (tuple[int, int] | None): Tile under the mouse cursor, or ``None``.
enemy_group (EnemyGroup): All active enemies.
tower_group (TowerGroup): All placed towers.
"""
def __init__(self, game):
super().__init__(game)
# self.tile_multiplicator = 2.0
self.width, self.height = pg.display.get_surface().get_size()
self.game_surface = pg.surface.Surface((int(self.width/4*3),int(self.height/6*5)))
# self.map_surface = pg.surface.Surface((self.game_surface.get_width()/self.tile_multiplicator,self.game_surface.get_height()/self.tile_multiplicator))
self.camera = Camera()
self.center = (self.game_surface.get_width() /2, self.game_surface.get_height() /2)
@@ -65,7 +80,19 @@ class GamePlay(BaseScreen):
self.cancel_button
]
self.mpos = (0, 0)
self.tile_pos = None
self.tile_pos_decimal = (0, 0)
def process_event(self, event):
"""Handle mouse clicks, keyboard input, and UI button presses.
Translates mouse position to tile coordinates, manages tower
placement and selection, camera movement keys, and zoom controls.
Args:
event (pg.event.Event): The pygame event to process.
"""
#calculate tile position from mouse position
self.mpos = pg.mouse.get_pos()
game_rect = self.game_surface.get_rect(topleft=(0, self.height / 6))
@@ -101,7 +128,7 @@ class GamePlay(BaseScreen):
print(f'{self.selected}')
if self.bought:
if self.game.tilemap.placeable(self.tile_pos):
if not any(tower.tile_pos == self.tile_pos for tower in self.tower_group):
if not any(tw.tile_pos == self.tile_pos for tw in self.tower_group):
tower = Tower(self.game, self.mpos, self.tile_pos, 'cannon', 40)
self.tower_group.add(tower)
self.bought = False
@@ -135,12 +162,24 @@ class GamePlay(BaseScreen):
self.movment[3] = False
def update(self, dt):
"""Update enemies, towers, and camera position.
Args:
dt (float): Time elapsed since the last frame in seconds.
"""
self.enemy_group.update()
self.tower_group.update(self.enemy_group)
self.camera.move((self.movment[1] - self.movment[0]) * 2, (self.movment[3] - self.movment[2]) * 2)
self.camera.move(
(self.movment[1] - self.movment[0]) * 2, (self.movment[3] - self.movment[2]) * 2
)
def draw(self, surface):
"""Render the tilemap, enemies, towers, and tower placement preview.
Args:
surface (pg.Surface): The main window surface to draw onto.
"""
self.game_surface.fill((40, 40, 40))
# self.map_surface.fill((40, 40, 40))
@@ -151,22 +190,59 @@ class GamePlay(BaseScreen):
if self.bought:
if self.game.tilemap.placeable(self.tile_pos):
self.game_surface.blit(pg.transform.scale2x(self.game.assets["cannon"]), (self.mpos[0] - (self.game.tilemap.tile_size //2), self.mpos[1] - (self.game.tilemap.tile_size //2 + self.height/6)))
self.game_surface.blit(
pg.transform.scale2x(self.game.assets["cannon"]),
(
self.mpos[0] - (self.game.tilemap.tile_size //2),
self.mpos[1] - (self.game.tilemap.tile_size //2 + self.height/6)
)
)
else:
self.game_surface.blit(pg.transform.scale2x(self.game.assets["red_cannon"]), (self.mpos[0] - (self.game.tilemap.tile_size //2), self.mpos[1] - (self.game.tilemap.tile_size //2 + self.height/6)))
# self.game_surface.blit(pg.transform.scale(self.map_surface, self.game_surface.get_size()))
self.game_surface.blit(
pg.transform.scale2x(self.game.assets["red_cannon"]),
(self.mpos[0] - (self.game.tilemap.tile_size //2),
self.mpos[1] - (self.game.tilemap.tile_size //2 + self.height/6))
)
surface.blit(self.game_surface, (0, self.height/6))
# if self.bought:
# self.game_surface.blit(pg.transform.scale2x(self.game.assets["cannon"]), (self.mpos[0] - (self.game.tilemap.tile_size //2), self.mpos[1] - (self.game.tilemap.tile_size //2)))
def select_turret(self, tile_pos):
"""Return the tower at tile_pos, or ``None`` if the tile is empty.
Args:
tile_pos (tuple[int, int]): Grid position to check.
Returns:
Tower | None: The tower occupying that tile, or ``None``.
"""
for tower in self.tower_group:
if (tile_pos[0], tile_pos[1]) == (tower.tile_pos[0], tower.tile_pos[1]):
return tower
return None
def clear_selection(self):
"""Deselect all towers."""
for tower in self.tower_group:
tower.selected = False
#private helpers jsut within class (planned not sure yet)
def _remove_tower_at(self, tile_pos):
"""Remove the tower at tile_pos on right click, if one exists.
Args:
tile_pos (tuple[int, int]): Grid position to remove from.
"""
def _handle_keydown(self, key):
"""Set movement flags and trigger zoom on key press.
Args:
key (int): pygame key constant.
"""
def _handle_keyup(self, key):
"""Clear movement flags on key release.
Args:
key (int): pygame key constant.
"""
+18 -4
View File
@@ -1,9 +1,18 @@
"""Main menu screen - title label and Play/Quit navigation."""
import sys
import pygame_gui as pgi
import pygame as pg
from game.screens.base_screen import BaseScreen
import pygame as pg
import pygame_gui as pgi
class MainMenu(BaseScreen):
"""The main menu screen with a title, Play button, and Quit button.
Attributes:
title (pgi.elements.UILabel): The menu title label.
play_button (pgi.elements.UIButton): Transitions to the gameplay screen.
quit_button (pgi.elements.UIButton): Exits the application.
"""
def __init__(self, game):
super().__init__(game)
@@ -66,6 +75,11 @@ class MainMenu(BaseScreen):
]
def process_event(self, event):
"""Handle Play and Quit button presses.
Args:
event (pg.event.Event): The pygame event to process.
"""
if event.type == pgi.UI_BUTTON_PRESSED:
@@ -75,4 +89,4 @@ class MainMenu(BaseScreen):
if event.ui_element == self.quit_button:
pg.quit()
exit()
sys.exit()
+80 -27
View File
@@ -1,11 +1,21 @@
import pygame, json
"""Tilemap module - tile storage, serialisation, placement rules, and rendering.
"""
Todo:
* Add prop system for Trees, etc. (like off_grid), handle blocked with game objects over props
"""
import json
import pygame
class Tilemap:
"""Stores and manages the tile grid, waypoints, and rendering.
Attributes:
tile_size (int): Width and height of a single tile in pixels.
tilemap (dict): Tile data keyed by ``"x;y"`` string positions.
planned: offgrid_tiles (list): Tiles placed outside the main grid (e.g. decorations).
waypoints (list): Ordered world positions enemies follow.
tower_placeable (list[dict]): Tile descriptors that allow tower placement.
"""
def __init__(self, game, tile=16):
self.game = game
self.tile_size = tile
@@ -16,20 +26,42 @@ class Tilemap:
self.tower_placeable = [{"type": "desert", "variant": 0}]
def save(self, path):
f = open(path, 'w')
json.dump({'tilemap': self.tilemap, 'tile_size': self.tile_size, 'waypoints': self.waypoints}, f)
f.close()
"""Serialise the tilemap, tile size, and waypoints to a JSON file.
Args:
path (str): File path to write to.
"""
with open(path, 'w', encoding='utf-8') as f:
json.dump({
'tilemap': self.tilemap,
'tile_size': self.tile_size,
'waypoints': self.waypoints
}, f)
f.close()
def load(self, path):
f = open(path, 'r')
map_data = json.load(f)
f.close()
"""Load tilemap data from a JSON file, replacing current state.
self.tilemap = map_data['tilemap']
self.tile_size = map_data['tile_size']
self.waypoints = map_data['waypoints']
Args:
path (str): File path to read from.
"""
with open(path, 'r', encoding='utf-8') as f:
map_data = json.load(f)
f.close()
self.tilemap = map_data['tilemap']
self.tile_size = map_data['tile_size']
self.waypoints = map_data['waypoints']
def placeable(self, tile_pos):
"""Check whether a tower can be placed on the tile at tile_pos.
Args:
tile_pos (tuple[int, int]): Grid position ``(x, y)`` to check.
Returns:
bool: ``True`` if the tile exists and is in ``tower_placeable``.
"""
str_tile_pos = f'{tile_pos[0]};{tile_pos[1]}'
if str_tile_pos not in self.tilemap:
return False
@@ -41,14 +73,39 @@ class Tilemap:
return False
def render(self, surf, offset=(0, 0)):
for x in range(offset[0] // self.tile_size, (offset[0] + surf.get_width()) // self.tile_size + 1):
for y in range(offset[1] // self.tile_size, (offset[1] + surf.get_height()) // self.tile_size + 1):
"""Draw only the tiles visible within surf, using a pixel offset (editor).
Args:
surf (pygame.Surface): Target surface to draw onto.
offset (tuple[int, int]): Pixel scroll offset ``(x, y)``.
"""
start_x = offset[0] // self.tile_size
end_x = (offset[0] + surf.get_width()) // self.tile_size + 1
start_y = offset[1] // self.tile_size
end_y = (offset[1] + surf.get_height()) // self.tile_size + 1
for x in range(start_x, end_x):
for y in range(start_y, end_y):
loc = str(x) + ';' + str(y)
if loc in self.tilemap:
tile = self.tilemap[loc]
surf.blit(self.game.assets[tile['type']][tile['variant']], (tile['pos'][0] * self.tile_size - offset[0], tile['pos'][1] * self.tile_size - offset[1]))
surf.blit(
self.game.assets[tile['type']][tile['variant']],
(
tile['pos'][0] * self.tile_size - offset[0],
tile['pos'][1] * self.tile_size - offset[1]
)
)
def render_game(self, surf, camera):
"""Draw only the tiles visible within surf, scaled to the camera zoom and pixel offset.
Args:
surf (pygame.Surface): Target surface to draw onto.
camera (Camera): Active camera for zoom level and coordinate conversion.
Todo: document this code below, not full understanding
"""
tile_size = self.tile_size
zoom = camera.zoom
scaled_tile_size = int(tile_size * zoom)
@@ -67,16 +124,12 @@ class Tilemap:
tile = self.tilemap[loc]
image = self.game.assets[tile["type"]][tile["variant"]]
scaled_image = pygame.transform.scale(
image,
(scaled_tile_size, scaled_tile_size)
)
world_pos = (
tile["pos"][0] * tile_size,
tile["pos"][1] * tile_size
)
screen_pos = camera.world_to_screen(world_pos)
surf.blit(scaled_image, screen_pos)
surf.blit(pygame.transform.scale(
image,
(scaled_tile_size, scaled_tile_size)
), camera.world_to_screen(
(
tile["pos"][0] * tile_size,
tile["pos"][1] * tile_size
)
))
+66 -3
View File
@@ -1,8 +1,25 @@
import pygame as pg
from game.util import *
"""Tower module - tower placement, targeting, shooting, and rendering."""
import math
import pygame as pg
from game.util import tile_to_pixel
class Tower(pg.sprite.Sprite):
# pylint: disable=too-many-instance-attributes
"""A player-placed tower that targets and damages nearby enemies.
Attributes:
type (str): Asset key identifying the tower variant.
range (float): Attack radius in world-space pixels.
level (int): Current upgrade level.
selected (bool): Whether the tower is selected (draws range circle).
angle (float): Current facing angle in degrees.
target (Enemy | None): The enemy currently being targeted, or ``None``.
fire_rate (int): Frames between shots.
fire_cooldown (int): Frames remaining until the next shot.
damage (float): Damage dealt per shot.
pos (tuple): World-space pixel centre position.
tile_pos (tuple): Grid position the tower occupies.
"""
def __init__(self, game, pos, tile_pos, type, range, level=1):
super().__init__()
self.game = game
@@ -27,6 +44,11 @@ class Tower(pg.sprite.Sprite):
self.set_action('shoot')
def update(self, enemy_group):
"""Advance animation, pick a target, rotate toward it, and shoot if ready.
Args:
enemy_group (EnemyGroup -> SpriteGroup): All active enemies to consider as targets.
"""
self.animation.update()
self.pick_target(enemy_group)
self.rotate()
@@ -39,11 +61,24 @@ class Tower(pg.sprite.Sprite):
self.shoot()
def set_action(self, action):
"""Switch the active animation if it differs from the current one.
Args:
action (str): Animation key (e.g. ``"shoot"``).
"""
if action != self.action:
self.action = action
self.animation = self.game.assets[self.type + '/' + self.action].copy()
def render(self, surf, camera):
"""Draw the tower animation frame, rotated and scaled to the camera zoom.
Also draws a semi-transparent range circle when the tower is selected.
Args:
surf (pg.Surface): Target surface to draw onto.
camera (Camera): Active camera for zoom and coordinate conversion.
"""
screen_pos = camera.world_to_screen(pg.Vector2(
tile_to_pixel(self.tile_pos, self.game.tilemap.tile_size, True)
))
@@ -61,15 +96,22 @@ class Tower(pg.sprite.Sprite):
self.rect = scaled_image.get_rect(center=screen_pos)
if self.selected:
#draw circle around tower if selected
scaled_range = int(self.range * camera.zoom)
range_surf = pg.Surface((scaled_range * 2, scaled_range * 2), pg.SRCALPHA)
pg.draw.circle(range_surf, (200, 200, 200, 100), (scaled_range, scaled_range), scaled_range)
pg.draw.circle(
range_surf,
(200, 200, 200, 100),
(scaled_range, scaled_range),
scaled_range
)
range_rect = range_surf.get_rect(center=screen_pos)
surf.blit(range_surf, range_rect)
surf.blit(scaled_image, self.rect)
def rotate(self):
"""Face the tower toward its current target, or hold the last angle if no target."""
if self.target is None:
# print("no target")
return # keep last angle, or reset to a default — your call
@@ -82,6 +124,13 @@ class Tower(pg.sprite.Sprite):
# print(f"angle {self.angle}")
def pick_target(self, enemy_group):
"""Select the closest enemy within range as the current target.
Sets ``self.target`` to ``None`` if no enemy is in range.
Args:
enemy_group (EnemyGroup): All active enemies to search through.
"""
closest = None
closest_dist = float('inf')
@@ -98,15 +147,29 @@ class Tower(pg.sprite.Sprite):
self.target = closest # explicitly None if nothing in range
def shoot(self):
"""Deal damage to the current target, play the shoot animation, and reset cooldown."""
self.target.take_damage(self.damage)
self.animation.play_animation()
self.fire_cooldown = self.fire_rate
class TowerGroup(pg.sprite.Group):
"""Sprite group that delegates rendering and updates to each tower."""
def render(self, surf, camera):
"""Render all towers in the group.
Args:
surf (pg.Surface): Target surface to draw onto.
camera (Camera): Active camera for zoom and coordinate conversion.
"""
for tower in self.sprites():
tower.render(surf, camera)
def update(self, enemies):
"""Update all towers in the group.
Args:
enemies (EnemyGroup): All active enemies, passed to each tower's update.
"""
for tower in self.sprites():
tower.update(enemies)
+86 -11
View File
@@ -1,49 +1,112 @@
import pygame as pg
"""Utility module - image loading, coordinate conversion, and sprite animation."""
import os
import pygame as pg
img_path='assets/images'
IMG_PATH='assets/images'
WINDOW_SIZE=(800,600)
def load_image(path, colorkey=(0,0,0)):
img = pg.image.load(img_path + path).convert()
"""Load an image and set a colorkey for transparency.
Args:
path (str): Path to the image relative to ``IMG_PATH``.
colorkey (tuple[int,int,int]): RGB colour treated as transparent. Defaults to black.
Returns:
pg.Surface: The loaded and converted image.
"""
img = pg.image.load(IMG_PATH + path).convert()
img.set_colorkey(colorkey)
return img
def load_images(dir_path):
"""Load all images from a directory using colorkey transparency.
Args:
dir_path (str): Directory path relative to ``IMG_PATH``.
Returns:
list[pg.Surface]: All images found in the directory, in filesystem order.
"""
images = []
for img_name in os.listdir(img_path + dir_path):
for img_name in os.listdir(IMG_PATH + dir_path):
images.append(load_image(dir_path + '/' + img_name))
return images
def load_image_alpha(path):
img = pg.image.load(img_path + path).convert_alpha()
"""Load an image preserving its alpha channel.
Args:
path (str): Path to the image relative to ``IMG_PATH``.
Returns:
pg.Surface: The loaded image with per-pixel alpha.
"""
img = pg.image.load(IMG_PATH + path).convert_alpha()
return img
def load_images_alpha(dir_path):
"""Load all images from a directory, preserving alpha channels.
Args:
dir_path (str): Directory path relative to ``IMG_PATH``.
Returns:
list[pg.Surface]: All images found in the directory, in filesystem order.
"""
images = []
for img_name in os.listdir(img_path + dir_path):
for img_name in os.listdir(IMG_PATH + dir_path):
images.append(load_image_alpha(dir_path + '/' + img_name))
return images
def tile_to_pixel(waypoint, tile_size, center=False):
"""Convert a tile grid position to world-space pixel coordinates.
Args:
tile_pos (tuple[int, int]): Grid position ``(x, y)``.
tile_size (int): Width and height of a single tile in pixels.
center (bool): If ``True``, return the centre of the tile. Defaults to ``False``.
Returns:
tuple[int, int]: Pixel position ``(x, y)``.
"""
if center:
return (
waypoint[0] * tile_size + tile_size // 2,
waypoint[1] * tile_size + tile_size // 2
)
else:
return (
waypoint[0] * tile_size,
waypoint[1] * tile_size
)
return (
waypoint[0] * tile_size,
waypoint[1] * tile_size
)
def pixel_to_tile(waypoint, tile_size):
"""Convert a world-space pixel position to tile grid coordinates.
Args:
pixel_pos (tuple[float, float]): Pixel position ``(x, y)``.
tile_size (int): Width and height of a single tile in pixels.
Returns:
tuple[float, float]: Fractional tile position ``(x, y)``.
"""
return (
waypoint[0] / tile_size,
waypoint[1] / tile_size
)
class Animation:
"""Frame-based sprite animation with optional looping and manual play control.
Attributes:
images (list[pg.Surface]): Ordered list of animation frames.
loop (bool): Whether the animation loops continuously.
img_duration (int): Number of update ticks each frame is shown for.
done (bool): ``True`` once a non-looping animation has finished.
frame (int): Current tick counter used to derive the active frame index.
playing (bool): Whether the animation is currently advancing.
"""
def __init__(self, images, img_dur=5, loop=True):
self.images = images
self.loop = loop
@@ -53,9 +116,15 @@ class Animation:
self.playing = False # not playing until told to
def copy(self):
"""Return a fresh copy of this animation, reset to its initial state.
Returns:
Animation: A new Animation sharing the same images and settings.
"""
return Animation(self.images, self.img_duration, self.loop)
def update(self):
"""Advance the animation by one tick if it is currently playing."""
if not self.playing:
return
@@ -68,9 +137,15 @@ class Animation:
self.playing = False
def play_animation(self):
"""Start the animation from the first frame."""
self.playing = True
self.done = False
self.frame = 0
def img(self):
"""Return the image for the current tick.
Returns:
pg.Surface: The active animation frame.
"""
return self.images[self.frame // self.img_duration]