Files
zeling_v2/Assets/Scripts/Player/PlayerMovement.cs
2026-05-12 15:34:08 +08:00

193 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
namespace BaseGames.Player
{
/// <summary>
/// 玩家物理移动组件。封装 Rigidbody2D 操作,提供跑动、跳跃、击退等接口。
/// </summary>
[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;
[Header("朝向")]
[SerializeField] private SpriteRenderer _spriteRenderer;
// ── 运行时状态 ────────────────────────────────────────────────────────
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;
private readonly Collider2D[] _groundBuffer = new Collider2D[4];
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;
/// <summary>垂直速度大于零(处于上升弧段)。供 WallJumpState 判断起跳是否结束。</summary>
public bool IsRising => _rb.velocity.y > 0f;
private void Awake()
{
Debug.Assert(_config != null, "[PlayerMovement] _config 未赋值,请在 Inspector 中指定 PlayerMovementConfigSO。", this);
_rb = GetComponent<Rigidbody2D>();
if (_spriteRenderer == null)
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
}
private void FixedUpdate()
{
CheckGrounded();
CheckWalls();
if (_isGrounded)
_coyoteTimer = _config.CoyoteTime;
else
_coyoteTimer = Mathf.Max(0f, _coyoteTimer - Time.fixedDeltaTime);
}
// ── 移动 ──────────────────────────────────────────────────────────────
public void Move(float speedX)
{
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)
{
_rb.velocity = new Vector2(_rb.velocity.x, _config.JumpForce);
_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;
if (_spriteRenderer != null)
_spriteRenderer.flipX = dir < 0;
}
// ── 取消窗口 ──────────────────────────────────────────────────────────
public void SetCancelWindowOpen(bool open) => _cancelWindowOpen = open;
// ── 冲刺 ──────────────────────────────────────────────────────────────
/// <summary>
/// 施加冲刺速度DashState/AerialDashState 调用)。
/// direction 应为归一化水平向量±1, 0
/// 冲刺期间调用 SetGravityScale(0) 可关闭重力;结束后恢复默认重力。
/// </summary>
public void Dash(Vector2 direction, float speed)
{
_rb.velocity = new Vector2(direction.x * speed, 0f);
}
/// <summary>
/// 壁滑:将垂直速度限制为 -WallSlideSpeed向下缓慢滑动
/// WallSlideState.OnStateFixedUpdate 每帧调用。
/// </summary>
public void ApplyWallSlide()
{
float targetY = -_config.WallSlideSpeed;
if (_rb.velocity.y < targetY)
_rb.velocity = new Vector2(_rb.velocity.x, targetY);
}
/// <summary>
/// 蹬墙跳:对墙方向施加相反水平力 + 向上力。
/// wallDir = +1 (右墙) 或 -1 (左墙),跳跃方向与之相反。
/// </summary>
public void WallJump(int wallDir)
{
float forceX = -wallDir * _config.WallJumpForceX;
float forceY = _config.WallJumpForceY;
_rb.velocity = new Vector2(forceX, forceY);
_coyoteTimer = 0f;
}
/// <summary>单向平台穿透(输入下行 + 跳跃键时触发)。</summary>
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.OverlapBoxNonAlloc(origin, _groundCheckSize, 0f, _groundBuffer, _groundLayer) > 0;
if (_isGrounded && !wasGrounded)
_coyoteTimer = _config.CoyoteTime;
}
private void CheckWalls()
{
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);
}
}
/// <summary>当前所在地面类型(用于脚步声等反馈)。</summary>
public enum SurfaceType
{
Ground,
OneWayPlatform,
Slope,
Ice,
}
}