using UnityEngine; namespace BaseGames.Player { /// /// 玩家物理移动组件。封装 Rigidbody2D 操作,提供跑动、跳跃、击退等接口。 /// Phase 2 功能(冲刺、单向平台)在此留存桩方法。 /// [RequireComponent(typeof(Rigidbody2D))] public class PlayerMovement : MonoBehaviour { [Header("配置")] [SerializeField] private PlayerMovementConfigSO _config; [Header("地面检测")] [SerializeField] private Transform _groundCheck; [SerializeField] private Vector2 _groundCheckSize = new Vector2(0.8f, 0.05f); [SerializeField] private LayerMask _groundLayer; // ── 运行时状态 ──────────────────────────────────────────────────────── private Rigidbody2D _rb; private float _coyoteTimer; private bool _isGrounded; private bool _isWallLeft; private bool _isWallRight; private bool _onOneWayPlatform; private int _facingDirection = 1; private bool _cancelWindowOpen; private SurfaceType _currentSurface = SurfaceType.Ground; public bool IsGrounded => _isGrounded; public bool HasCoyoteTime => _coyoteTimer > 0f; public bool IsWallLeft => _isWallLeft; public bool IsWallRight => _isWallRight; public bool OnOneWayPlatform => _onOneWayPlatform; public int FacingDirection => _facingDirection; public Rigidbody2D Rb => _rb; public bool CancelWindowOpen => _cancelWindowOpen; public SurfaceType CurrentSurface => _currentSurface; private void Awake() => _rb = GetComponent(); private void FixedUpdate() { CheckGrounded(); CheckWalls(); if (_isGrounded) _coyoteTimer = _config != null ? _config.CoyoteTime : 0.12f; else _coyoteTimer = Mathf.Max(0f, _coyoteTimer - Time.fixedDeltaTime); } // ── 移动 ────────────────────────────────────────────────────────────── public void Move(float speedX) { if (_config == null) return; float target = speedX; float current = _rb.velocity.x; float accel = Mathf.Abs(speedX) > 0.01f ? _config.Acceleration : _config.Deceleration; float newX = Mathf.MoveTowards(current, target, accel * Time.fixedDeltaTime); _rb.velocity = new Vector2(newX, _rb.velocity.y); } // ── 跳跃 ────────────────────────────────────────────────────────────── public void Jump(bool isVariable = true) { float force = _config != null ? _config.JumpForce : 18f; _rb.velocity = new Vector2(_rb.velocity.x, force); _coyoteTimer = 0f; } public void CutJump() { if (_rb.velocity.y > 0f) _rb.velocity = new Vector2(_rb.velocity.x, _rb.velocity.y * 0.5f); } // ── 重力 ────────────────────────────────────────────────────────────── public void SetGravityScale(float scale) => _rb.gravityScale = scale; // ── 击退 ────────────────────────────────────────────────────────────── public void ApplyKnockback(Vector2 direction, float force) => _rb.velocity = direction.normalized * force; // ── 速度控制 ────────────────────────────────────────────────────────── public void ZeroVelocity() => _rb.velocity = Vector2.zero; public void ZeroHorizontalVelocity() => _rb.velocity = new Vector2(0f, _rb.velocity.y); // ── 朝向 ────────────────────────────────────────────────────────────── public void UpdateFacing() { float vx = _rb.velocity.x; if (Mathf.Abs(vx) < 0.1f) return; int dir = vx > 0f ? 1 : -1; if (dir == _facingDirection) return; _facingDirection = dir; Vector3 s = transform.localScale; transform.localScale = new Vector3(Mathf.Abs(s.x) * dir, s.y, s.z); } // ── 取消窗口 ────────────────────────────────────────────────────────── public void SetCancelWindowOpen(bool open) => _cancelWindowOpen = open; // ── Phase 2 桩 ──────────────────────────────────────────────────────── /// Phase 2 实现冲刺。 public void Dash(Vector2 direction, float speed) { } /// Phase 2 实现单向平台穿透。 public void DropThroughPlatform() { } // ── Physics 检测 ────────────────────────────────────────────────────── private void CheckGrounded() { bool wasGrounded = _isGrounded; Vector2 origin = _groundCheck != null ? (Vector2)_groundCheck.position : (Vector2)transform.position + Vector2.down * 0.5f; _isGrounded = Physics2D.OverlapBox(origin, _groundCheckSize, 0f, _groundLayer); if (_isGrounded && !wasGrounded) _coyoteTimer = _config != null ? _config.CoyoteTime : 0.12f; } private void CheckWalls() { if (_config == null) return; float len = _config.WallRayLength; float offY = _config.WallRayOffsetY; Vector2 pos = (Vector2)transform.position + Vector2.up * offY; _isWallLeft = Physics2D.Raycast(pos, Vector2.left, len, _groundLayer); _isWallRight = Physics2D.Raycast(pos, Vector2.right, len, _groundLayer); } private void OnDrawGizmosSelected() { if (_groundCheck == null) return; Gizmos.color = Color.green; Gizmos.DrawWireCube(_groundCheck.position, _groundCheckSize); } } /// 当前所在地面类型(用于脚步声等反馈)。 public enum SurfaceType { Ground, OneWayPlatform, Slope, Ice, } }