154 lines
7.2 KiB
C#
154 lines
7.2 KiB
C#
using UnityEngine;
|
|
|
|
namespace BaseGames.Player
|
|
{
|
|
/// <summary>
|
|
/// 玩家物理移动组件。封装 Rigidbody2D 操作,提供跑动、跳跃、击退等接口。
|
|
/// Phase 2 功能(冲刺、单向平台)在此留存桩方法。
|
|
/// </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;
|
|
|
|
// ── 运行时状态 ────────────────────────────────────────────────────────
|
|
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<Rigidbody2D>();
|
|
|
|
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 桩 ────────────────────────────────────────────────────────
|
|
/// <summary>Phase 2 实现冲刺。</summary>
|
|
public void Dash(Vector2 direction, float speed) { }
|
|
|
|
/// <summary>Phase 2 实现单向平台穿透。</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.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);
|
|
}
|
|
}
|
|
|
|
/// <summary>当前所在地面类型(用于脚步声等反馈)。</summary>
|
|
public enum SurfaceType
|
|
{
|
|
Ground,
|
|
OneWayPlatform,
|
|
Slope,
|
|
Ice,
|
|
}
|
|
}
|