diff --git a/assets/images/entities/towers/0.png b/assets/images/entities/towers/0.png new file mode 100644 index 0000000..2fcde4b Binary files /dev/null and b/assets/images/entities/towers/0.png differ diff --git a/assets/images/entities/towers/1.png b/assets/images/entities/towers/1.png new file mode 100644 index 0000000..68c1ac3 Binary files /dev/null and b/assets/images/entities/towers/1.png differ diff --git a/assets/images/entities/towers/2.png b/assets/images/entities/towers/2.png new file mode 100644 index 0000000..08f55e4 Binary files /dev/null and b/assets/images/entities/towers/2.png differ diff --git a/assets/images/entities/towers/3.png b/assets/images/entities/towers/3.png new file mode 100644 index 0000000..7c597e6 Binary files /dev/null and b/assets/images/entities/towers/3.png differ diff --git a/assets/images/entities/towers/4.png b/assets/images/entities/towers/4.png new file mode 100644 index 0000000..38638d8 Binary files /dev/null and b/assets/images/entities/towers/4.png differ diff --git a/assets/images/entities/towers/5.png b/assets/images/entities/towers/5.png new file mode 100644 index 0000000..01c0cba Binary files /dev/null and b/assets/images/entities/towers/5.png differ diff --git a/assets/images/entities/towers/6.png b/assets/images/entities/towers/6.png new file mode 100644 index 0000000..e1e9ba0 Binary files /dev/null and b/assets/images/entities/towers/6.png differ diff --git a/assets/images/entities/towers/7.png b/assets/images/entities/towers/7.png new file mode 100644 index 0000000..b1f1bbc Binary files /dev/null and b/assets/images/entities/towers/7.png differ diff --git a/assets/images/entities/towers/8.png b/assets/images/entities/towers/8.png new file mode 100644 index 0000000..4def5bd Binary files /dev/null and b/assets/images/entities/towers/8.png differ diff --git a/assets/images/entities/towers/cannon/shoot/0.png b/assets/images/entities/towers/cannon/shoot/0.png index 2fcde4b..af47b3f 100644 Binary files a/assets/images/entities/towers/cannon/shoot/0.png and b/assets/images/entities/towers/cannon/shoot/0.png differ diff --git a/assets/images/entities/towers/cannon/shoot/1.png b/assets/images/entities/towers/cannon/shoot/1.png index 68c1ac3..e129863 100644 Binary files a/assets/images/entities/towers/cannon/shoot/1.png and b/assets/images/entities/towers/cannon/shoot/1.png differ diff --git a/assets/images/entities/towers/cannon/shoot/2.png b/assets/images/entities/towers/cannon/shoot/2.png index 08f55e4..5eac680 100644 Binary files a/assets/images/entities/towers/cannon/shoot/2.png and b/assets/images/entities/towers/cannon/shoot/2.png differ diff --git a/assets/images/entities/towers/cannon/shoot/3.png b/assets/images/entities/towers/cannon/shoot/3.png index 7c597e6..9d9b9aa 100644 Binary files a/assets/images/entities/towers/cannon/shoot/3.png and b/assets/images/entities/towers/cannon/shoot/3.png differ diff --git a/assets/images/entities/towers/cannon/shoot/4.png b/assets/images/entities/towers/cannon/shoot/4.png index 38638d8..bc168e3 100644 Binary files a/assets/images/entities/towers/cannon/shoot/4.png and b/assets/images/entities/towers/cannon/shoot/4.png differ diff --git a/assets/images/entities/towers/cannon/shoot/5.png b/assets/images/entities/towers/cannon/shoot/5.png index 01c0cba..6b3f376 100644 Binary files a/assets/images/entities/towers/cannon/shoot/5.png and b/assets/images/entities/towers/cannon/shoot/5.png differ diff --git a/assets/images/entities/towers/cannon/shoot/6.png b/assets/images/entities/towers/cannon/shoot/6.png index e1e9ba0..6b1f6c7 100644 Binary files a/assets/images/entities/towers/cannon/shoot/6.png and b/assets/images/entities/towers/cannon/shoot/6.png differ diff --git a/assets/images/entities/towers/cannon/shoot/7.png b/assets/images/entities/towers/cannon/shoot/7.png index b1f1bbc..1ce26a6 100644 Binary files a/assets/images/entities/towers/cannon/shoot/7.png and b/assets/images/entities/towers/cannon/shoot/7.png differ diff --git a/assets/images/entities/towers/cannon/shoot/8.png b/assets/images/entities/towers/cannon/shoot/8.png index 4def5bd..fa6493f 100644 Binary files a/assets/images/entities/towers/cannon/shoot/8.png and b/assets/images/entities/towers/cannon/shoot/8.png differ diff --git a/game/enemy.py b/game/enemy.py index 49660fa..ddd1fe0 100644 --- a/game/enemy.py +++ b/game/enemy.py @@ -2,7 +2,7 @@ from game.util import * import pygame as pg import math -class Enemy(pg.sprite.Group): +class Enemy(pg.sprite.Sprite): def __init__(self, game, speed, health): super().__init__() self.game = game @@ -23,6 +23,12 @@ class Enemy(pg.sprite.Group): self.rect.center = self.pos + self.healthbar = HealthBar( + 18, + 5, + self.health + ) + def update(self): self.move() self.rotate() @@ -71,4 +77,51 @@ class Enemy(pg.sprite.Group): scaled_pos = camera.world_to_screen(self.pos) scaled_rect.center = scaled_pos - surf.blit(scaled_image, scaled_rect) \ No newline at end of file + surf.blit(scaled_image, scaled_rect) + self.healthbar.render( + surf, + camera, + self.pos, + self.rect.height + ) + + def take_damage(self, amount): + self.health -= amount + self.healthbar.hp = self.health + if self.health <= 0: + self.kill() + +class EnemyGroup(pg.sprite.Group): + def render(self, surf, camera): + for enemy in self.sprites(): + enemy.render(surf, camera) + + def update(self): + for enemy in self.sprites(): + enemy.update() + +class HealthBar(): + 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): + 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) diff --git a/game/game.py b/game/game.py index dade825..7098faf 100644 --- a/game/game.py +++ b/game/game.py @@ -31,7 +31,7 @@ class Game: "enemy": load_image('/entities/enemies/enemy.png'), "cannon": load_image('/entities/towers/cannon/shoot/0.png'), "red_cannon": load_image('/entities/towers/cannon/red_cannon.png'), - "cannon/shoot": Animation(load_images('/entities/towers/cannon/shoot'), 10, False) + "cannon/shoot": Animation(load_images_alpha('/entities/towers/cannon/shoot'), 10, False) } self.screens = { diff --git a/game/screens/game_play.py b/game/screens/game_play.py index a4162d5..3c0db7b 100644 --- a/game/screens/game_play.py +++ b/game/screens/game_play.py @@ -1,5 +1,5 @@ from game.screens.base_screen import BaseScreen -from game.enemy import Enemy +from game.enemy import Enemy, EnemyGroup from game.camera import Camera from game.tower import Tower, TowerGroup from game.util import * @@ -29,10 +29,11 @@ class GamePlay(BaseScreen): except FileNotFoundError: print('File not Found') - self.enemy_group = pg.sprite.Group() + self.enemy_group = EnemyGroup() self.tower_group = TowerGroup() - self.enemy = Enemy(self.game, 0.5, 100) + self.enemy = Enemy(self.game, 0.5, 200) + self.enemy_group.add(self.enemy) self.buy_button = pgi.elements.UIButton( relative_rect=pg.Rect( @@ -134,8 +135,8 @@ class GamePlay(BaseScreen): self.movment[3] = False def update(self, dt): - self.enemy.update() - self.tower_group.update("sigma") + 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) @@ -145,7 +146,7 @@ class GamePlay(BaseScreen): self.game.tilemap.render_game(self.game_surface, self.camera) - self.enemy.render(self.game_surface, self.camera) + self.enemy_group.render(self.game_surface, self.camera) self.tower_group.render(self.game_surface, self.camera) if self.bought: diff --git a/game/tower.py b/game/tower.py index 3683740..f746748 100644 --- a/game/tower.py +++ b/game/tower.py @@ -1,5 +1,6 @@ import pygame as pg from game.util import * +import math class Tower(pg.sprite.Sprite): def __init__(self, game, pos, tile_pos, type, range, level=1): @@ -12,6 +13,12 @@ class Tower(pg.sprite.Sprite): self.range = range self.selected = False + self.angle = 0 + self.target = None + + self.fire_rate = 180 # frames between shots — tune to taste + self.fire_cooldown = 0 + self.damage = 50 self.pos = pos self.tile_pos = tile_pos @@ -19,8 +26,17 @@ class Tower(pg.sprite.Sprite): self.action = "" self.set_action('shoot') - def update(self, enemies): + def update(self, enemy_group): self.animation.update() + self.pick_target(enemy_group) + self.rotate() + + if self.fire_cooldown > 0: + self.fire_cooldown -= 1 + + if self.target is not None and self.fire_cooldown == 0: + print("shoot fired") + self.shoot() def set_action(self, action): if action != self.action: @@ -33,15 +49,16 @@ class Tower(pg.sprite.Sprite): )) anim_img = self.animation.img() + rotated_img = pg.transform.rotate(anim_img, self.angle) scaled_image = pg.transform.scale( - anim_img, + rotated_img, ( - int(anim_img.get_width() * camera.zoom), - int(anim_img.get_height() * camera.zoom) + int(rotated_img.get_width() * camera.zoom), + int(rotated_img.get_height() * camera.zoom) ) ) - rect = scaled_image.get_rect(center=screen_pos) + self.rect = scaled_image.get_rect(center=screen_pos) if self.selected: scaled_range = int(self.range * camera.zoom) @@ -50,7 +67,40 @@ class Tower(pg.sprite.Sprite): range_rect = range_surf.get_rect(center=screen_pos) surf.blit(range_surf, range_rect) - surf.blit(scaled_image, rect) + surf.blit(scaled_image, self.rect) + + def rotate(self): + if self.target is None: + # print("no target") + return # keep last angle, or reset to a default — your call + + tower_world_pos = pg.Vector2( + tile_to_pixel(self.tile_pos, self.game.tilemap.tile_size, True) + ) + direction = self.target.pos - tower_world_pos + self.angle = math.degrees(math.atan2(-direction[1], direction[0])) -90 + # print(f"angle {self.angle}") + + def pick_target(self, enemy_group): + closest = None + closest_dist = float('inf') + + tower_world_pos = pg.Vector2( + tile_to_pixel(self.tile_pos, self.game.tilemap.tile_size, True) + ) + + for enemy in enemy_group: + distance = tower_world_pos.distance_to(enemy.pos) + if distance <= self.range and distance < closest_dist: + closest = enemy + closest_dist = distance + + self.target = closest # explicitly None if nothing in range + + def shoot(self): + self.target.take_damage(self.damage) + self.animation.play_animation() + self.fire_cooldown = self.fire_rate class TowerGroup(pg.sprite.Group): def render(self, surf, camera): diff --git a/game/util.py b/game/util.py index 3c750f8..9d63f3a 100644 --- a/game/util.py +++ b/game/util.py @@ -15,6 +15,16 @@ def load_images(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() + return img + +def load_images_alpha(dir_path): + images = [] + 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): if center: return ( @@ -40,17 +50,27 @@ class Animation: self.img_duration = img_dur self.done = False self.frame = 0 + self.playing = False # not playing until told to def copy(self): return Animation(self.images, self.img_duration, self.loop) def update(self): + if not self.playing: + return + if self.loop: self.frame = (self.frame + 1) % (self.img_duration * len(self.images)) else: - self.frame = min(self.frame + 1, self.img_duration * len(self.images)) + self.frame = min(self.frame + 1, self.img_duration * len(self.images) - 1) if self.frame >= self.img_duration * len(self.images) - 1: self.done = True + self.playing = False + + def play_animation(self): + self.playing = True + self.done = False + self.frame = 0 def img(self): - return self.images[int(self.frame / self.img_duration) - 1] \ No newline at end of file + return self.images[self.frame // self.img_duration] \ No newline at end of file