using UnityEngine; using System.Linq; using Animancer; using BaseGames.Core.Events; using BaseGames.Input; using BaseGames.Combat; using BaseGames.Parry; using BaseGames.Skills; namespace BaseGames.Player.States { /// /// 玩家主控制器(协调器)。位于 Player/States/ 程序集,以便引用所有具体状态类型。 /// 实现 IDamageable + IPoiseSource(架构 06_CombatModule §13)。 /// 依赖注入:同节点组件由 RequireComponent + Awake 自动获取;跨节点引用通过 [SerializeField] 绑定。 /// [DefaultExecutionOrder(-100)] [RequireComponent(typeof(InputBuffer))] [RequireComponent(typeof(PlayerMovement))] [RequireComponent(typeof(PlayerStats))] [RequireComponent(typeof(AnimancerComponent))] public class PlayerController : MonoBehaviour, IDamageable, IPoiseSource { // ── 同节点组件(由 RequireComponent 保证存在,Awake 中自动获取)───── private PlayerMovement _movement; private PlayerStats _stats; private AnimancerComponent _animancer; // ── 配置 SO ─────────────────────────────────────────────────────────── [Header("配置")] [SerializeField] private PlayerMovementConfigSO _movementConfig; [SerializeField] private PlayerAnimationConfigSO _animConfig; [SerializeField] private InputReaderSO _inputReader; [SerializeField] private FormConfigSO _formConfig; // ── 战斗组件 ────────────────────────────────────────────────────────── [Header("战斗")] [SerializeField] private PlayerCombat _combat; [SerializeField] private FormController _formController; [SerializeField] private WeaponManager _weaponManager; [SerializeField] private SkillManager _skillManager; [SerializeField] private SpringSystem _springSystem; [SerializeField] private ParrySystem _parrySystem; [SerializeField] private HurtBox _hurtBox; [SerializeField] private ShieldComponent _shield; [SerializeField] private PlayerWallDetector _wallDetector; // ── 事件频道 ────────────────────────────────────────────────────────── [Header("事件频道")] [SerializeField] private VoidEventChannelSO _onPlayerDied; /// /// Start() 时广播玩家 Transform(EnemyBase / ProjectileManager 等订阅此频道)。 /// 替代每个敌人在 Awake 中独立 FindWithTag 的 O(n) 全场景扫描。 /// [SerializeField] private TransformEventChannelSO _onPlayerSpawned; // ── 运行时 ──────────────────────────────────────────────────────────── private InputBuffer _inputBuffer; private bool _missingDependencyLogged; private bool _dependenciesReady; /// /// 当前腾空可用的额外跳跃次数(二段跳)。 /// 由 IdleState/RunState.OnStateEnter 落地时通过 ResetAirJumps() 重置; /// JumpState/FallState 判断 HasAbility(DoubleJump) 后消耗。 /// private int _airJumpsLeft; #if UNITY_EDITOR [Header("调试")] [SerializeField] private bool _debugValidateTransitions = true; [Header("── 运行时状态 ──")] [SerializeField] private string _dbg_CurrentState; [SerializeField] private bool _dbg_IsGrounded; [SerializeField] private int _dbg_AirJumpsLeft; [SerializeField] private bool _dbg_CanDash; [SerializeField] private bool _dbg_IsInvincible; #endif // Overlay Layer(Layer 1):供 SpringState / SoulSkill 等叠加层动画使用 // Base Layer(Layer 0):移动/攻击/受伤/死亡等全身状态动画 private AnimancerLayer _overlayLayer; // ── 状态实例 ────────────────────────────────────────────────────────── private PlayerStateBase _currentState; private readonly System.Collections.Generic.Dictionary _states = new(); // ── IDamageable 实现 ────────────────────────────────────────────────── public bool IsAlive => _stats != null && _stats.IsAlive; public bool IsInvincible => _stats != null && _stats.IsInvincible; public int Defense => 0; public void TakeDamage(DamageInfo info) { if (_stats == null) return; _stats.TakeDamage(info.FinalDamage); // 当前状态标记为无敌(如旧版冲刺状态),或 Stats 层无敌窗口仍激活 // (冲刺无敌帧 DashInvincibilityDuration 内:跳过受击硬直;窗口过期后可被打断) if (_currentState?.IsInvincible == true || (_stats != null && _stats.IsInvincible)) return; if (_stats.IsAlive) { GetState()?.Initialize(info); TransitionTo(GetState()); } else { TransitionTo(GetState()); _onPlayerDied?.Raise(); } } // ── IPoiseSource 实现(架构 06_CombatModule §13)───────────────────── /// /// 玩家不拥有霸体,始终返回 。 /// 设计决策:玩家不拥有霸体,始终返回 。 /// 玩家依靠走位和弹反规避伤害而非硬吃,以保持战斗的负担感和张力。 /// 若未来需要临时霸体(如特定技能动作),请通过独立的覆盖标记实现, /// 而非在此处引入状态,以保持接口语义清晰。 /// public PoiseLevel GetCurrentPoiseLevel() => PoiseLevel.None; // ── 公开属性(供状态类访问)────────────────────────────────────────── public PlayerMovement Movement => _movement; public PlayerStats Stats => _stats; public AnimancerComponent Animancer => _animancer; public PlayerMovementConfigSO MovConfig => _movementConfig; public PlayerAnimationConfigSO AnimConfig => _animConfig; public InputReaderSO Input => _inputReader; public InputBuffer Buffer => _inputBuffer; public PlayerCombat Combat => _combat; public FormController Form => _formController; public WeaponManager Weapon => _weaponManager; public SkillManager Skill => _skillManager; public SpringSystem Spring => _springSystem; public ParrySystem Parry => _parrySystem; public HurtBox HurtBox => _hurtBox; public ShieldComponent Shield => _shield; public PlayerWallDetector WallDetector => _wallDetector; public bool IsGrounded => _movement != null && _movement.IsGrounded; public int FacingDirection => _movement != null ? _movement.FacingDirection : 1; // ── 空中跳跃 API(二段跳)──────────────────────────────────────────── public int AirJumpsLeft => _airJumpsLeft; public void UseAirJump() => _airJumpsLeft = Mathf.Max(0, _airJumpsLeft - 1); /// /// 落地时重置空中跳跃次数(由 IdleState/RunState.OnStateEnter 调用)。 /// 若解锁 DoubleJump 则重置为 MovConfig.MaxAirJumps,否则为 0。 /// public void ResetAirJumps() => _airJumpsLeft = (_stats != null && _stats.HasAbility(AbilityType.DoubleJump)) ? (_movementConfig != null ? _movementConfig.MaxAirJumps : 1) : 0; // ── 抓墙高度记忆 API ────────────────────────────────────────────────── // wallGrabY:首次抓住某面墙壁时记录的 Y 坐标;用于防止单面墙无限向上爬。 // wallGrabDir:当前记录所属墙壁方向(+1=右墙,-1=左墙,0=无)。 // isPostWallJump:蹬墙跳后未落地标记,允许靠近墙壁时自动抓墙。 private float _wallGrabY = float.NegativeInfinity; private int _wallGrabDir = 0; private bool _isPostWallJump; public float WallGrabY => _wallGrabY; public int WallGrabDir => _wallGrabDir; public bool IsPostWallJump => _isPostWallJump; /// /// 进入 WallSlideState 时调用。 /// 不同墙壁(dir != _wallGrabDir)→ 重置并记录新高度; /// 同一面墙壁 → 保留原记录(防止攀爬重置)。 /// public void RecordWallGrab(int dir, float y) { if (dir != _wallGrabDir) { _wallGrabDir = dir; _wallGrabY = y; } // 同一面墙:保留 _wallGrabY,不更新 } /// 落地时重置抓墙记录(由 IdleState/RunState.OnStateEnter 调用)。 public void ResetWallGrab() { _wallGrabY = float.NegativeInfinity; _wallGrabDir = 0; } /// /// 设置蹬墙跳后自动抓墙标记。 /// true:由 WallJumpState.OnStateEnter 设置; /// false:由 IdleState/RunState.OnStateEnter(落地)或 WallSlideState.OnStateEnter(已消耗)清除。 /// public void SetPostWallJump(bool value) => _isPostWallJump = value; // ── Overlay Layer API(供 SpringState / SoulSkill 等叠加动画使用)───── /// /// 在 Overlay Layer(Layer 1)播放动画,叠加于当前 Base Layer 动画之上。 /// 适用于灵泉使用、魂技能等需要与移动动画并行的上半身动作。 /// public AnimancerState PlayOnOverlay(AnimationClip clip) => _overlayLayer?.Play(clip); /// 停止 Overlay Layer 动画,淡出回 Base Layer。 public void StopOverlay(float fadeDuration = 0.1f) => _overlayLayer?.StartFade(0f, fadeDuration); // ── Unity Lifecycle ─────────────────────────────────────────────────── private void Awake() { Debug.Assert(_movementConfig != null, "[PlayerController] _movementConfig 未赋值,请在 Inspector 中指定 PlayerMovementConfigSO。", this); ResolveDependencies(); // 初始化 Animancer 双层动画:Layer 0 = Base(全身状态),Layer 1 = Overlay(叠加层) if (_animancer != null) { // 确保 Layer 1 存在;AnimancerComponent 默认只有 Layer 0 while (_animancer.Layers.Count <= 1) _animancer.Layers.Add(); _overlayLayer = _animancer.Layers[1]; } // 注入 HurtBox 依赖 if (_hurtBox != null) { if (_shield != null) _hurtBox.SetShieldable(_shield); if (_parrySystem != null) _hurtBox.SetParrySystem(_parrySystem); _hurtBox.SetPoiseSource(this); } // 将唯一配置点(_inputReader)注入到 ParrySystem。 // ParrySystem 不再需要在 Inspector 单独配置 InputReaderSO。 if (_parrySystem != null) _parrySystem.SetInputReader(_inputReader); InitializeStates(); // 订阅 ParrySystem C# 事件 if (_parrySystem != null) { _parrySystem.OnParryActivated += OnParryActivated; _parrySystem.OnParryConsumed += OnParryConsumedHandler; } // 订阅灵泉使用输入 if (_inputReader != null) _inputReader.UseSpringEvent += OnUseSpring; } private void OnDestroy() { if (_parrySystem != null) { _parrySystem.OnParryActivated -= OnParryActivated; _parrySystem.OnParryConsumed -= OnParryConsumedHandler; } if (_inputReader != null) _inputReader.UseSpringEvent -= OnUseSpring; } /// 弹反输入激活时由 ParrySystem 触发 → 转换到 ParryState。 private void OnParryActivated() { if (_states.ContainsKey(typeof(ParryState))) TransitionTo(GetState()); } /// 弹反命中成功时由 ParrySystem 触发 → 发放灵力并恢复护盾。 private void OnParryConsumedHandler(BaseGames.Parry.ParryInfo info) { _stats?.AddSoul(info.SoulGained); _shield?.OnParrySuccess(); } /// 灵泉输入:地面且有剩余充能时转入 SpringState 使用一次。 private void OnUseSpring() { if (_stats == null || _stats.CurrentSpringCharges <= 0) return; if (_movement != null && !_movement.IsGrounded) return; if (_states.ContainsKey(typeof(SpringState))) TransitionTo(GetState()); } private void Start() { if (!HasRequiredStateDependencies()) return; // 广播玩家 Transform:EnemyBase / ProjectileManager 等订阅者将通过事件接收引用 // (必须在 Start 中调用,确保所有 Awake/OnEnable 订阅已就绪) _onPlayerSpawned?.Raise(transform); TransitionTo(GetState()); } private void Update() { if (!HasRequiredStateDependencies()) return; // 冲刺冷却计时 GetState()?.TickCooldown(Time.deltaTime); _currentState?.OnStateUpdate(); #if UNITY_EDITOR _dbg_CurrentState = _currentState?.GetType().Name ?? "None"; _dbg_IsGrounded = _movement != null && _movement.IsGrounded; _dbg_AirJumpsLeft = _airJumpsLeft; _dbg_CanDash = GetState()?.CanDash ?? false; _dbg_IsInvincible = _stats != null && _stats.IsInvincible; #endif } private void FixedUpdate() { _currentState?.OnStateFixedUpdate(); } private void LateUpdate() { _movement?.UpdateFacing(); } // ── 状态机 ──────────────────────────────────────────────────────────── public void TransitionTo(PlayerStateBase newState) { #if UNITY_EDITOR if (_debugValidateTransitions && _currentState != null && newState != null) { var allowed = _currentState.ValidTransitions; if (allowed.Count > 0 && !allowed.Contains(newState.GetType())) Debug.LogWarning( $"[PlayerController] 非预期转换: {_currentState.GetType().Name} → {newState.GetType().Name}", this); } #endif _currentState?.OnStateExit(); _currentState = newState; _currentState?.OnStateEnter(); } private void InitializeStates() { _states[typeof(IdleState)] = new IdleState(this); _states[typeof(RunState)] = new RunState(this); _states[typeof(JumpState)] = new JumpState(this); _states[typeof(FallState)] = new FallState(this); _states[typeof(AttackState)] = new AttackState(this); _states[typeof(DashState)] = new DashState(this); _states[typeof(WallSlideState)] = new WallSlideState(this); _states[typeof(WallJumpState)] = new WallJumpState(this); _states[typeof(AirAttackState)] = new AirAttackState(this); _states[typeof(DownAttackState)] = new DownAttackState(this); _states[typeof(UpAttackState)] = new UpAttackState(this); _states[typeof(HurtState)] = new HurtState(this); _states[typeof(DeadState)] = new DeadState(this); _states[typeof(SpringState)] = new SpringState(this); _states[typeof(ParryState)] = new ParryState(this); _states[typeof(SwimState)] = new SwimState(this); } /// /// 按类型获取状态实例。未注册时返回 null(供可选状态调用方安全使用)。 /// public T GetState() where T : PlayerStateBase => _states.TryGetValue(typeof(T), out var s) ? (T)s : null; private void ResolveDependencies() { if (_movement == null) _movement = GetComponent(); if (_stats == null) _stats = GetComponent(); if (_animancer == null) _animancer = GetComponent(); if (_inputBuffer == null) _inputBuffer = GetComponent(); // 将唯一配置点(_inputReader)注入到同一 GameObject 上的 InputBuffer。 // InputBuffer 不再需要在 Inspector 单独配置 InputReaderSO。 if (_inputBuffer != null) _inputBuffer.Init(_inputReader); } private bool HasRequiredStateDependencies() { if (_dependenciesReady) return true; bool ok = _movement != null && _animancer != null && _inputBuffer != null && _inputReader != null; if (ok) { _dependenciesReady = true; _missingDependencyLogged = false; return true; } if (!_missingDependencyLogged) { Debug.LogError($"[PlayerController] Missing required dependencies. " + $"Movement={(_movement != null ? "OK" : "NULL")}, " + $"Animancer={(_animancer != null ? "OK" : "NULL")}, " + $"InputBuffer={(_inputBuffer != null ? "OK" : "NULL")}, " + $"InputReader={(_inputReader != null ? "OK" : "NULL")}"); _missingDependencyLogged = true; } return false; } // ── 状态访问器 ──────────────────────────────────────────────────────── // 使用 GetState() 按类型获取任意状态实例,不再暴露具名属性。 } }