""" Enemy module - enemy movement, rendering, and health management. """ import math 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. Attributes: 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. (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.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.rect.center = self.pos def update(self): """Advance movement and update rotation each frame.""" self.move() self.rotate() def move(self): """Step toward the current waypoint, advancing to the next when reached.""" if self.target_index >= len(self.waypoints): return tile_size = self.game.tilemap.tile_size self.target = pg.Vector2( tile_to_pixel( self.waypoints[self.target_index], tile_size, True ) ) direction = self.target - self.pos distance = direction.length() if distance <= self.speed: self.pos = self.target self.target_index += 1 else: direction = direction.normalize() self.pos += direction * self.speed def rotate(self): """Face the enemy toward its current target waypoint.""" dist = self.target - self.pos self.angle = math.degrees(math.atan2(-dist[1], dist[0])) self.image = pg.transform.rotate(self.game.assets['enemy'].copy(), self.angle) self.rect = self.image.get_rect() self.rect.center = self.pos def render(self, surf, camera): """Draw the enemy and its health bar scaled to the camera zoom. Args: surf (pg.Surface): Target surface to draw onto. camera (Camera): Active camera used for coordinate conversion and zoom. """ zoom = camera.zoom scaled_rect = self.rect.scale_by(zoom) scaled_image = pg.transform.scale( self.image, (scaled_rect.size[0], scaled_rect.size[1]) ) scaled_pos = camera.world_to_screen(self.pos) scaled_rect.center = scaled_pos surf.blit(scaled_image, scaled_rect) self.healthbar.render( surf, camera, self.pos, self.rect.height ) def take_damage(self, amount): """Reduce health by amount and kill the sprite if health reaches zero. Args: amount (float): Damage to apply. """ self.health -= amount self.healthbar.hp = self.health if self.health <= 0: self.kill() class EnemyGroup(pg.sprite.Group): """Sprite group that delegates rendering and updates to each enemy.""" def render(self, surf, camera): """Render all enemies in the group. Args: surf (pg.Surface): Target surface to draw onto. camera (Camera): Active camera for zoom and coordinate conversion. """ for enemy in self.sprites(): enemy.render(surf, camera) def update(self): """Update all enemies in the group.""" for enemy in self.sprites(): enemy.update() class HealthBar(): # pylint: disable=too-few-public-methods """A simple two-rect health bar drawn in world space. Attributes: w (int): Width in world pixels. h (int): Height in world pixels. hp (float): Current health value. max_hp (float): Maximum health value. """ def __init__(self, w, h, max_hp): self.w = w self.h = h self.hp = max_hp self.max_hp = max_hp def render(self, surf, camera, pos, enemy_height): """Draw the health bar above the enemy, scaled to the camera zoom. Args: surf (pg.Surface): Target surface to draw onto. camera (Camera): Active camera for zoom and coordinate conversion. pos (pg.Vector2): Enemy's world-space centre position. enemy_height (int): Enemy sprite height, used to position the bar. """ ratio = self.hp / self.max_hp world_pos = ( pos.x - self.w / 2, pos.y + enemy_height / 2 -20 ) screen_pos = camera.world_to_screen(world_pos) scaled_w = self.w * camera.zoom scaled_h = self.h * camera.zoom bg_rect = pg.Rect(screen_pos[0], screen_pos[1], scaled_w, scaled_h) fg_rect = pg.Rect(screen_pos[0], screen_pos[1], scaled_w * ratio, scaled_h) pg.draw.rect(surf, "red", bg_rect) pg.draw.rect(surf, "green", fg_rect)