## PlayerManager — 玩家管理 Autoload ## S0:WASD 移动 + 位置查询 ## S1:自动施法 ## S4:停步蓄力 / 冲刺边沿检测(P6-N69、P6-N36) extends Node const MOVE_SPEED: float = 200.0 const MOVE_THRESHOLD_NORM: float = 0.1 const CHARGE_TRIGGER_TIME: float = 1.5 var _position: Vector2 = Vector2.ZERO var _velocity: Vector2 = Vector2.ZERO var _player_node: Node2D = null var _equipped_core: CoreDefinition = null var _equipped_compiled: CompiledDeck = null var _cast_timer: float = 0.0 var _cast_interval: float = 0.5 var _is_moving: bool = false var _stationary_time: float = 0.0 var _charge_triggered: bool = false var _last_dash_time: float = -999.0 var kill_count: int = 0 func _ready() -> void: EventBus.subscribe(EventID.ENEMY_KILLED, _on_enemy_killed) func _physics_process(delta: float) -> void: PlayerStats.regen_mana(delta) PlayerStats.mana_heat = maxf(0.0, PlayerStats.mana_heat - SettingsManager.mana_heat_decay() * delta) _handle_movement(delta) _handle_charge_state(delta) _handle_auto_cast(delta) func _handle_movement(delta: float) -> void: var dir := Vector2( Input.get_axis("ui_left", "ui_right"), Input.get_axis("ui_up", "ui_down") ) _is_moving = dir.length() > MOVE_THRESHOLD_NORM if _is_moving: dir = dir.normalized() _velocity = dir * MOVE_SPEED else: _velocity = Vector2.ZERO _position += _velocity * delta if _player_node: _player_node.global_position = _position func _handle_charge_state(delta: float) -> void: if Input.is_action_just_pressed("ui_accept"): _last_dash_time = Time.get_ticks_msec() / 1000.0 EventBus.emit(EventID.DASH_TRIGGERED, { "caster_id": 0, "stationary_time": _stationary_time, }) _stationary_time = 0.0 _charge_triggered = false return if _is_moving: if _stationary_time > 0.0: _stationary_time = 0.0 _charge_triggered = false EventBus.emit(EventID.CHARGE_STATE_CHANGED, {"caster_id": 0, "progress": 0.0}) else: _stationary_time += delta if _stationary_time >= 0.3: var prog: float = clampf(_stationary_time / CHARGE_TRIGGER_TIME, 0.0, 1.0) EventBus.emit(EventID.CHARGE_STATE_CHANGED, {"caster_id": 0, "progress": prog}) if _stationary_time >= CHARGE_TRIGGER_TIME and not _charge_triggered: _charge_triggered = true EventBus.emit(EventID.CHARGE_FIRED, {"caster_id": 0}) func _handle_auto_cast(delta: float) -> void: if _equipped_compiled == null or _equipped_compiled.is_empty(): return _cast_timer -= delta if _cast_timer <= 0.0: _cast_timer = _cast_interval SpellEvaluator.execute_compiled(_equipped_compiled, 0, _position) func equip_wand(core: CoreDefinition, compiled: CompiledDeck) -> void: _equipped_core = core _equipped_compiled = compiled _cast_interval = core.cast_interval if core else 0.5 _cast_timer = 0.0 if core: PlayerStats.set_mana_pool(core.base_mana_max, core.base_mana_regen) else: PlayerStats.set_mana_pool(100.0, 5.0) PlayerStats.mana_heat = 0.0 func get_position() -> Vector2: return _position func get_velocity() -> Vector2: return _velocity func get_stationary_time() -> float: return _stationary_time func is_charge_triggered() -> bool: return _charge_triggered func set_player_node(node: Node2D) -> void: _player_node = node if node: _position = node.global_position func _on_enemy_killed(_payload: Dictionary) -> void: kill_count += 1 func reset() -> void: _position = Vector2.ZERO _velocity = Vector2.ZERO _player_node = null _equipped_compiled = null _equipped_core = null _cast_timer = 0.0 _is_moving = false _stationary_time = 0.0 _charge_triggered = false kill_count = 0