chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

View File

@@ -0,0 +1,65 @@
using BaseGames.Combat;
namespace BaseGames.Player.States
{
/// <summary>
/// 地面攻击状态3 段连击)。
/// 由 PlayerController 实例化AttackEvent 触发切换。
/// 通过 Animancer 帧事件驱动 HitBox 激活/关闭。
/// </summary>
public class AttackState : PlayerStateBase
{
private int _comboIndex;
public AttackState(PlayerController owner) : base(owner) { }
public override void OnStateEnter()
{
_comboIndex = 0;
PlayAttackClip();
Input.AttackEvent += OnAttackInput;
}
public override void OnStateExit()
{
Input.AttackEvent -= OnAttackInput;
Owner.Combat?.DisableAllWeaponHitBoxes();
}
public override void OnStateUpdate() { }
public override void OnStateFixedUpdate() { }
// ── 内部 ──────────────────────────────────────────────────────────────
private void PlayAttackClip()
{
// ⚠️ 字段名 GroundAttacks非 AttackChainClips
var clip = AnimCfg.GroundAttacks[_comboIndex];
var state = Anim.Play(clip);
var events = state.Events(this);
events.OnEnd = OnClipEnd;
// HitBox 由 Animancer 归一化时间事件驱动
events.Add(0.3f,
() => Owner.Combat?.EnableWeaponHitBox(AttackDirection.Ground));
events.Add(0.6f,
() => Owner.Combat?.DisableAllWeaponHitBoxes());
}
private void OnClipEnd()
{
Input.AttackEvent -= OnAttackInput;
Owner.Combat?.DisableAllWeaponHitBoxes();
Owner.TryTransitionState(Owner.IdleState);
}
private void OnAttackInput()
{
if (_comboIndex < 2)
{
_comboIndex++;
PlayAttackClip();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7d976cdcc6a9c44ba569bff0147f6c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
{
"excludePlatforms": [],
"allowUnsafeCode": false,
"precompiledReferences": [],
"name": "BaseGames.Player.States",
"defineConstraints": [],
"noEngineReferences": false,
"versionDefines": [],
"rootNamespace": "BaseGames.Player.States",
"references": [
"BaseGames.Player",
"BaseGames.Core.Events",
"BaseGames.Input",
"BaseGames.Combat",
"BaseGames.Parry",
"BaseGames.Feedback",
"Kybernetik.Animancer"
],
"autoReferenced": true,
"overrideReferences": false,
"includePlatforms": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c336a32eed62ced4280d1d4c9782ec91
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
using UnityEngine;
namespace BaseGames.Player.States
{
/// <summary>下落状态。着地后转为 Idle 或 Run持有郊狼时间允许跳跃。</summary>
public class FallState : PlayerStateBase
{
public FallState(PlayerController owner) : base(owner) { }
public override void OnStateEnter()
{
if (AnimCfg?.Fall != null)
Anim.Play(AnimCfg.Fall);
}
public override void OnStateUpdate()
{
// 郊狼时间跳跃
if (Buffer.ConsumeJump() && Move.HasCoyoteTime)
{
_owner.TransitionTo(_owner.JumpState);
return;
}
// 着地
if (Move.IsGrounded)
{
Move.ZeroVelocity();
if (Mathf.Abs(Input.MoveInput.x) > 0.1f)
_owner.TransitionTo(_owner.RunState);
else
_owner.TransitionTo(_owner.IdleState);
return;
}
// 空中水平移动
if (Mathf.Abs(Input.MoveInput.x) > 0.01f)
Move.Move(Input.MoveInput.x * (Cfg != null ? Cfg.RunSpeed : 7f));
}
public override void OnStateFixedUpdate()
{
// 增强下落重力
if (Cfg != null && Move.Rb.velocity.y < 0f)
{
float extraGrav = Physics2D.gravity.y * (Cfg.FallGravityMult - 1f) * Time.fixedDeltaTime;
Move.Rb.velocity = new Vector2(
Move.Rb.velocity.x,
Mathf.Max(Move.Rb.velocity.y + extraGrav, -Cfg.MaxFallSpeed));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d81f2601a90beeb4382680e53e18be63
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
using UnityEngine;
namespace BaseGames.Player.States
{
/// <summary>闲置状态。默认入口状态,播放 Idle 动画。</summary>
public class IdleState : PlayerStateBase
{
public IdleState(PlayerController owner) : base(owner) { }
public override void OnStateEnter()
{
if (AnimCfg?.Idle != null)
Anim.Play(AnimCfg.Idle);
Move?.ZeroHorizontalVelocity();
}
public override void OnStateUpdate()
{
if (!Move.IsGrounded)
{
_owner.TransitionTo(_owner.FallState);
return;
}
if (Buffer.ConsumeJump())
{
_owner.TransitionTo(_owner.JumpState);
return;
}
if (Mathf.Abs(Input.MoveInput.x) > 0.1f)
{
_owner.TransitionTo(_owner.RunState);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84b6b9fa502d3d34a8d7284831404d75
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using UnityEngine;
namespace BaseGames.Player.States
{
/// <summary>跳跃状态。在 OnStateEnter 触发跳跃,速度降为零时转为 FallState。</summary>
public class JumpState : PlayerStateBase
{
public JumpState(PlayerController owner) : base(owner) { }
public override void OnStateEnter()
{
if (AnimCfg?.Jump != null)
Anim.Play(AnimCfg.Jump);
Move.Jump();
Input.JumpCancelledEvent += OnJumpCancelled;
}
public override void OnStateUpdate()
{
// 上升结束时转为下落
if (Move.Rb.velocity.y <= 0f)
{
_owner.TransitionTo(_owner.FallState);
return;
}
// 水平移动
if (Mathf.Abs(Input.MoveInput.x) > 0.01f)
Move.Move(Input.MoveInput.x * (Cfg != null ? Cfg.RunSpeed : 7f));
}
public override void OnStateExit()
{
Input.JumpCancelledEvent -= OnJumpCancelled;
}
private void OnJumpCancelled() => Move.CutJump();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 643f44aee61bd684ebd893779e1122aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,151 @@
using UnityEngine;
using Animancer;
using BaseGames.Core.Events;
using BaseGames.Input;
using BaseGames.Combat;
using BaseGames.Parry;
namespace BaseGames.Player.States
{
/// <summary>
/// 玩家主控制器(协调器)。位于 Player/States/ 程序集,以便引用所有具体状态类型。
/// 实现 IDamageableIsInvincible/Defense 委托 PlayerStatsTakeDamage 委托 _stats。
/// 依赖注入:所有子系统通过 [SerializeField] 字段在 Inspector 中绑定。
/// </summary>
[DefaultExecutionOrder(-100)]
[RequireComponent(typeof(InputBuffer))]
public class PlayerController : MonoBehaviour, IDamageable
{
// ── 移动 & 数值 ───────────────────────────────────────────────────────
[Header("核心组件")]
[SerializeField] private PlayerMovement _movement;
[SerializeField] private PlayerStats _stats;
[SerializeField] private AnimancerComponent _animancer;
// ── 配置 SO ───────────────────────────────────────────────────────────
[Header("配置")]
[SerializeField] private PlayerMovementConfigSO _movementConfig;
[SerializeField] private PlayerAnimationConfigSO _animConfig;
[SerializeField] private InputReaderSO _inputReader; [SerializeField] private PlayerStatsSO _statsConfig; // 数值基准HP/弹簧等初始化用)
[SerializeField] private FormConfigSO _formConfig; // Phase 2三形态切换参数
// ── 战斗组件 ──────────────────────────────────────────────────────────
[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;
// ── 事件频道 ──────────────────────────────────────────────────────────
[Header("事件频道")]
[SerializeField] private VoidEventChannelSO _onPlayerDied;
[SerializeField] private IntEventChannelSO _onHPChanged;
// ── 运行时 ────────────────────────────────────────────────────────────
private InputBuffer _inputBuffer;
// ── 状态实例 ──────────────────────────────────────────────────────────
private PlayerStateBase _currentState;
private IdleState _idleState;
private RunState _runState;
private JumpState _jumpState;
private FallState _fallState;
private AttackState _attackState;
// ── IDamageable 实现 ──────────────────────────────────────────────────
public bool IsInvincible => _stats != null && _stats.IsInvincible;
public int Defense => 0; // Phase 2从 PlayerStatsSO 读取
public void TakeDamage(DamageInfo info)
{
if (_stats == null) return;
_stats.TakeDamage(info.FinalDamage);
// Phase 2若非 DashState切换 HurtState
}
// ── 公开属性(供状态类访问)──────────────────────────────────────────
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 bool IsGrounded => _movement != null && _movement.IsGrounded;
public int FacingDirection => _movement != null ? _movement.FacingDirection : 1;
// ── Unity Lifecycle ───────────────────────────────────────────────────
private void Awake()
{
_inputBuffer = GetComponent<InputBuffer>();
// 注入 HurtBox 依赖Phase 1只注入护盾弹反/霸体 Phase 2
if (_hurtBox != null && _shield != null)
_hurtBox.SetShieldable(_shield);
InitializeStates();
}
private void Start()
{
TransitionTo(_idleState);
}
private void Update()
{
_currentState?.OnStateUpdate();
}
private void FixedUpdate()
{
_currentState?.OnStateFixedUpdate();
}
private void LateUpdate()
{
_movement?.UpdateFacing();
}
// ── 状态机 ────────────────────────────────────────────────────────────
public void TransitionTo(PlayerStateBase newState)
{
_currentState?.OnStateExit();
_currentState = newState;
_currentState?.OnStateEnter();
}
/// <summary>尝试切换状态(供状态内部的条件转换使用)。</summary>
public void TryTransitionState(PlayerStateBase newState)
=> TransitionTo(newState);
private void InitializeStates()
{
_idleState = new IdleState(this);
_runState = new RunState(this);
_jumpState = new JumpState(this);
_fallState = new FallState(this);
_attackState = new AttackState(this);
}
// ── 状态访问器 ────────────────────────────────────────────────────────
public IdleState IdleState => _idleState;
public RunState RunState => _runState;
public JumpState JumpState => _jumpState;
public FallState FallState => _fallState;
public AttackState AttackState => _attackState;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e20d2200567c4ca4d8fa1a047c7bbd58
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using Animancer;
using BaseGames.Input;
using BaseGames.Player;
namespace BaseGames.Player.States
{
/// <summary>
/// 所有玩家状态的抽象基类。持有 PlayerController 引用并提供便捷属性访问。
/// 状态不继承 MonoBehaviour生命周期由 PlayerController 驱动。
/// </summary>
public abstract class PlayerStateBase
{
protected PlayerController _owner;
protected PlayerStateBase(PlayerController owner) => _owner = owner;
public virtual void OnStateEnter() { }
public virtual void OnStateUpdate() { }
public virtual void OnStateFixedUpdate() { }
public virtual void OnStateExit() { }
// ── 便捷属性 ──────────────────────────────────────────────────────────
protected PlayerController Owner => _owner;
protected InputReaderSO Input => _owner.Input;
protected InputBuffer Buffer => _owner.Buffer;
protected PlayerMovement Move => _owner.Movement;
protected PlayerStats Stats => _owner.Stats;
protected AnimancerComponent Anim => _owner.Animancer;
protected PlayerMovementConfigSO Cfg => _owner.MovConfig;
protected PlayerAnimationConfigSO AnimCfg => _owner.AnimConfig;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0e192113c871fe44ba2d9d56c95c27e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using UnityEngine;
namespace BaseGames.Player.States
{
/// <summary>跑步状态。播放 Run 动画并驱动水平移动。</summary>
public class RunState : PlayerStateBase
{
public RunState(PlayerController owner) : base(owner) { }
public override void OnStateEnter()
{
if (AnimCfg?.Run != null)
Anim.Play(AnimCfg.Run);
}
public override void OnStateUpdate()
{
if (!Move.IsGrounded)
{
_owner.TransitionTo(_owner.FallState);
return;
}
if (Buffer.ConsumeJump())
{
_owner.TransitionTo(_owner.JumpState);
return;
}
if (Mathf.Abs(Input.MoveInput.x) < 0.1f)
{
_owner.TransitionTo(_owner.IdleState);
return;
}
Move.Move(Input.MoveInput.x * (Cfg != null ? Cfg.RunSpeed : 7f));
}
public override void OnStateFixedUpdate()
{
Move.Move(Input.MoveInput.x * (Cfg != null ? Cfg.RunSpeed : 7f));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f695c81f3a6b0bc4eafc75125bbf47aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
// Placeholder to prevent asmdef-no-scripts warning.
namespace BaseGames.Player.States { }

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee579de5ae36a9448b7463976699bc20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: