added now functional cannon needs projectiles (and alot more)

This commit is contained in:
2026-07-09 08:24:12 +02:00
parent f3a5647e5b
commit 1248390b11
23 changed files with 141 additions and 17 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 205 B

+55 -2
View File
@@ -2,7 +2,7 @@ from game.util import *
import pygame as pg import pygame as pg
import math import math
class Enemy(pg.sprite.Group): class Enemy(pg.sprite.Sprite):
def __init__(self, game, speed, health): def __init__(self, game, speed, health):
super().__init__() super().__init__()
self.game = game self.game = game
@@ -23,6 +23,12 @@ class Enemy(pg.sprite.Group):
self.rect.center = self.pos self.rect.center = self.pos
self.healthbar = HealthBar(
18,
5,
self.health
)
def update(self): def update(self):
self.move() self.move()
self.rotate() self.rotate()
@@ -71,4 +77,51 @@ class Enemy(pg.sprite.Group):
scaled_pos = camera.world_to_screen(self.pos) scaled_pos = camera.world_to_screen(self.pos)
scaled_rect.center = scaled_pos scaled_rect.center = scaled_pos
surf.blit(scaled_image, scaled_rect) 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)
+1 -1
View File
@@ -31,7 +31,7 @@ class Game:
"enemy": load_image('/entities/enemies/enemy.png'), "enemy": load_image('/entities/enemies/enemy.png'),
"cannon": load_image('/entities/towers/cannon/shoot/0.png'), "cannon": load_image('/entities/towers/cannon/shoot/0.png'),
"red_cannon": load_image('/entities/towers/cannon/red_cannon.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 = { self.screens = {
+7 -6
View File
@@ -1,5 +1,5 @@
from game.screens.base_screen import BaseScreen 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.camera import Camera
from game.tower import Tower, TowerGroup from game.tower import Tower, TowerGroup
from game.util import * from game.util import *
@@ -29,10 +29,11 @@ class GamePlay(BaseScreen):
except FileNotFoundError: except FileNotFoundError:
print('File not Found') print('File not Found')
self.enemy_group = pg.sprite.Group() self.enemy_group = EnemyGroup()
self.tower_group = TowerGroup() 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( self.buy_button = pgi.elements.UIButton(
relative_rect=pg.Rect( relative_rect=pg.Rect(
@@ -134,8 +135,8 @@ class GamePlay(BaseScreen):
self.movment[3] = False self.movment[3] = False
def update(self, dt): def update(self, dt):
self.enemy.update() self.enemy_group.update()
self.tower_group.update("sigma") 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)
@@ -145,7 +146,7 @@ class GamePlay(BaseScreen):
self.game.tilemap.render_game(self.game_surface, self.camera) 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) self.tower_group.render(self.game_surface, self.camera)
if self.bought: if self.bought:
+56 -6
View File
@@ -1,5 +1,6 @@
import pygame as pg import pygame as pg
from game.util import * from game.util import *
import math
class Tower(pg.sprite.Sprite): class Tower(pg.sprite.Sprite):
def __init__(self, game, pos, tile_pos, type, range, level=1): def __init__(self, game, pos, tile_pos, type, range, level=1):
@@ -12,6 +13,12 @@ class Tower(pg.sprite.Sprite):
self.range = range self.range = range
self.selected = False 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.pos = pos
self.tile_pos = tile_pos self.tile_pos = tile_pos
@@ -19,8 +26,17 @@ class Tower(pg.sprite.Sprite):
self.action = "" self.action = ""
self.set_action('shoot') self.set_action('shoot')
def update(self, enemies): def update(self, enemy_group):
self.animation.update() 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): def set_action(self, action):
if action != self.action: if action != self.action:
@@ -33,15 +49,16 @@ class Tower(pg.sprite.Sprite):
)) ))
anim_img = self.animation.img() anim_img = self.animation.img()
rotated_img = pg.transform.rotate(anim_img, self.angle)
scaled_image = pg.transform.scale( scaled_image = pg.transform.scale(
anim_img, rotated_img,
( (
int(anim_img.get_width() * camera.zoom), int(rotated_img.get_width() * camera.zoom),
int(anim_img.get_height() * 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: if self.selected:
scaled_range = int(self.range * camera.zoom) 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) range_rect = range_surf.get_rect(center=screen_pos)
surf.blit(range_surf, range_rect) 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): class TowerGroup(pg.sprite.Group):
def render(self, surf, camera): def render(self, surf, camera):
+22 -2
View File
@@ -15,6 +15,16 @@ def load_images(dir_path):
images.append(load_image(dir_path + '/' + img_name)) images.append(load_image(dir_path + '/' + img_name))
return images 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): def tile_to_pixel(waypoint, tile_size, center=False):
if center: if center:
return ( return (
@@ -40,17 +50,27 @@ class Animation:
self.img_duration = img_dur self.img_duration = img_dur
self.done = False self.done = False
self.frame = 0 self.frame = 0
self.playing = False # not playing until told to
def copy(self): def copy(self):
return Animation(self.images, self.img_duration, self.loop) return Animation(self.images, self.img_duration, self.loop)
def update(self): def update(self):
if not self.playing:
return
if self.loop: if self.loop:
self.frame = (self.frame + 1) % (self.img_duration * len(self.images)) self.frame = (self.frame + 1) % (self.img_duration * len(self.images))
else: 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: if self.frame >= self.img_duration * len(self.images) - 1:
self.done = True self.done = True
self.playing = False
def play_animation(self):
self.playing = True
self.done = False
self.frame = 0
def img(self): def img(self):
return self.images[int(self.frame / self.img_duration) - 1] return self.images[self.frame // self.img_duration]