/
Vibek
/
First_Game
Обзор
Документация
Войти
/
Vibek
/
First_Game
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
models/enemies/enemy_base.py
169 строк
6 KB
Vibek
Оптимизация, иснхронизация хитбоксов
21 июн 2026, 17:09
21 июн 2026, 17:09
bbdda9b
Код
Авторство
О чём код?
from enum import Enum, auto import math import pygame from pygame.math import Vector2 from models.entity import Entity from core.events import EventBus from core.config import Config from models.hitbox import HitboxDef class EnemyState(Enum): IDLE = auto() CHASE = auto() TELEGRAPH = auto() ATTACK = auto() STUNNED = auto() DEAD = auto() class EnemyBase(Entity): """ Базовая модель врага. Управляет внутренним FSM, индикаторами подготовки и контрактом на получение цели. View читает флаги (is_telegraphing, is_stunned) и таймеры. Model НЕ рисует. """ def __init__( self, x: float, y: float, speed: float, size: tuple[int, int], bus: EventBus, hp: int = Config.ENEMY_HP, color: tuple[int, int, int] = Config.RED, hitbox: HitboxDef | None = None, enemy_type: str = "enemy", art_angle_offset: float = 0.0, bounds: pygame.Rect | None = None, ) -> None: super().__init__( x, y, speed, size, bus, hp, hitbox=hitbox, art_angle_offset=art_angle_offset, ) self.bounds = bounds self.state = EnemyState.IDLE self.color = color self.enemy_type = enemy_type self.target_pos = Vector2(0, 0) self.is_target_alive = True # Таймеры (frame-independent) self.attack_cooldown = 0.0 self.telegraph_timer = 0.0 self.stun_timer = 0.0 # Флаги для View self.is_telegraphing = False self.telegraph_remaining = 0.0 self.is_stunned = False self.stun_remaining = 0.0 self.attack_visual_timer = 0.0 if self.bounds is not None: self.clamp_to_arena() self._sync_rect() def clamp_to_arena(self) -> None: """Ограничивает хитбокс врага границами арены.""" if self.bounds is not None: self.clamp_to_bounds(self.bounds) def _finalize_movement(self) -> None: """Синхронизирует позицию врага с ареной после шага движения.""" self.clamp_to_arena() self._sync_rect() def update(self, dt: float, target_pos: Vector2, is_target_alive: bool) -> None: if not self.is_alive: return self.target_pos = target_pos self.is_target_alive = is_target_alive # Приоритет стана if self.state == EnemyState.STUNNED: self.stun_timer -= dt self.stun_remaining = max(0.0, self.stun_timer) if self.stun_timer <= 0.0: self.state = EnemyState.IDLE self.is_stunned = False self.stun_remaining = 0.0 return if self.attack_cooldown > 0: self.attack_cooldown -= dt if self.attack_visual_timer > 0: self.attack_visual_timer = max(0.0, self.attack_visual_timer - dt) self._face_target() self._step_fsm(dt) def _face_target(self) -> None: """Поворачивает модель (и OBB-хитбокс) в сторону цели. View читает entity.angle.""" if not self.is_target_alive: return to_target = self.target_pos - self.center if to_target.length_squared() == 0: return self.angle = math.degrees(math.atan2(to_target.y, to_target.x)) def _step_fsm(self, dt: float) -> None: """Централизованный шаг автомата""" if self.state == EnemyState.IDLE: self.state = EnemyState.CHASE elif self.state == EnemyState.CHASE: self._try_telegraph() elif self.state == EnemyState.TELEGRAPH: self.is_telegraphing = True self.telegraph_timer -= dt self.telegraph_remaining = max(0.0, self.telegraph_timer) self.bus.emit(Config.EVT_ENEMY_TELEGRAPH, { "enemy_id": id(self), "pos": self.pos.copy(), "remaining": self.telegraph_timer }) if self.telegraph_timer <= 0: self.is_telegraphing = False self.telegraph_remaining = 0.0 self.state = EnemyState.ATTACK elif self.state == EnemyState.ATTACK: self._perform_attack() self.state = EnemyState.IDLE self.attack_cooldown = self._get_attack_cooldown() def _try_telegraph(self) -> None: if self.attack_cooldown <= 0.0 and self.is_target_alive: dist = self.center.distance_to(self.target_pos) if dist <= self._get_attack_range(): self.state = EnemyState.TELEGRAPH self.telegraph_timer = self._get_telegraph_duration() def _perform_attack(self) -> None: self.bus.emit(Config.EVT_ENEMY_ATTACK, { "attacker_id": id(self), "pos": self.pos.copy(), "type": self._get_attack_type() }) def on_death(self) -> None: """Override Entity.on_death""" self.state = EnemyState.DEAD self.is_telegraphing = False self.is_stunned = False self.bus.emit(Config.EVT_ENTITY_DIED, {"target_id": id(self), "type": "enemy"}) # --- ABSTRACT HOOKS (переопределяются в наследниках) --- def _get_telegraph_duration(self) -> float: return Config.ENEMY_ATTACK_COOLDOWN def _get_attack_cooldown(self) -> float: return Config.ENEMY_ATTACK_COOLDOWN def _get_attack_range(self) -> float: return Config.SCOUT_MELEE_RANGE def _get_attack_type(self) -> str: return "melee"