Files
pixel_tower_defense/game/screens/game_play.py
T
ScriptSorcerer e103605f00 Added tower upgrading
Issue #8 (pylint problem resolved with this change)

Upgrade system not done, only simplel upgrading added
2026-07-17 22:56:47 +02:00

264 lines
9.3 KiB
Python

"""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 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.camera = Camera()
self.center = (self.game_surface.get_width() /2, self.game_surface.get_height() /2)
self.movment = [False, False, False, False]
self.selected = None
self.map = 'desert'
self.path = 'assets/maps/' + str(self.map) + '.json'
try:
self.game.tilemap.load(self.path)
except FileNotFoundError:
print('File not Found')
self.enemy_group = EnemyGroup()
self.tower_group = TowerGroup()
self.enemy = Enemy(self.game, 0.5, 200)
self.enemy_group.add(self.enemy)
self.buy_button = pgi.elements.UIButton(
relative_rect=pg.Rect(
self.width - 200,
self.height -250,
150,
50
),
text="Buy Tower",
manager=self.game.ui_manager
)
self.cancel_button = pgi.elements.UIButton(
relative_rect=pg.Rect(
self.width - 200,
self.height -175,
150,
50
),
text="Cancel",
manager=self.game.ui_manager
)
self.upgrade_button = pgi.elements.UIButton(
relative_rect=pg.Rect(
self.width - 200,
self.height -100,
150,
50
),
text="Upgrade Tower",
manager=self.game.ui_manager
)
self.buy_button.hide()
self.cancel_button.hide()
self.upgrade_button.hide()
self.bought = False
self.elements = [
self.buy_button,
self.cancel_button,
self.upgrade_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))
if game_rect.collidepoint(self.mpos):
local_mpos = (
self.mpos[0] - game_rect.x,
self.mpos[1] - game_rect.y
)
world_pos = self.camera.screen_to_world(local_mpos)
self.tile_pos_decimal = pixel_to_tile(world_pos, self.game.tilemap.tile_size)
self.tile_pos = (int(self.tile_pos_decimal[0]), int(self.tile_pos_decimal[1]))
else:
self.tile_pos = None
self.tile_pos = (int(self.tile_pos_decimal[0]), int(self.tile_pos_decimal[1]))
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if self.tile_pos is not None:
if game_rect.collidepoint(self.mpos):
# only clear and reselect when clicking inside the game surface
self.clear_selection()
self.selected = self.select_tower(self.tile_pos)
if self.selected is not None:
self.selected.selected = True
if self.bought:
if self.game.tilemap.placeable(self.tile_pos):
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')
self.tower_group.add(tower)
self.bought = False
if event.button == 3:
for tower in self.tower_group:
if tower.tile_pos == self.tile_pos:
tower.kill()
if event.type == pgi.UI_BUTTON_PRESSED:
if event.ui_element == self.buy_button:
self.bought = True
# self.tower = Tower(self.game, self.mpos, self.tile_pos, 'cannon')
if event.ui_element == self.cancel_button:
self.bought = False
if event.ui_element == self.upgrade_button:
if self.selected:
self.selected.upgrade()
if event.type == pg.KEYDOWN:
if event.key == pg.K_a:
self.movment[0] = True
if event.key == pg.K_d:
self.movment[1] = True
if event.key == pg.K_w:
self.movment[2] = True
if event.key == pg.K_s:
self.movment[3] = True
if event.key == pg.K_q:
self.camera.zoom_offset(self.center, -0.25)
if event.key == pg.K_e:
self.camera.zoom_offset(self.center ,0.25)
if event.type == pg.KEYUP:
if event.key == pg.K_a:
self.movment[0] = False
if event.key == pg.K_d:
self.movment[1] = False
if event.key == pg.K_w:
self.movment[2] = False
if event.key == pg.K_s:
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
)
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))
self.game.tilemap.render_game(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:
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)
)
)
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))
)
surface.blit(self.game_surface, (0, self.height/6))
def select_tower(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.
"""