摄像机区域的优化
This commit is contained in:
@@ -42,7 +42,7 @@ namespace BaseGames.Player
|
||||
|
||||
// ── 能力强化 ──────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// 无敌冲刺强化(类比空洞骑士暗影披风)。
|
||||
/// 无敌冲刺强化(解锁后冲刺前段获得无敌窗口)。
|
||||
/// 仅持有 Dash 时:冲刺无无敌帧。
|
||||
/// 解锁 InvincibleDash 后:冲刺期间完全无敌(地面 DashState + 空中 AerialDashState)。
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace BaseGames.Player
|
||||
/// 玩家物理移动组件。封装 Rigidbody2D 操作,提供跑动、跳跃、击退等接口。
|
||||
|
||||
/// </summary>
|
||||
// 执行顺序必须早于 PlayerController(-100),确保每帧 FixedUpdate
|
||||
// 开头能在状态机写入速度之前先应用"强制清零"标记。
|
||||
[DefaultExecutionOrder(-200)]
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
public class PlayerMovement : MonoBehaviour
|
||||
{
|
||||
@@ -24,6 +27,10 @@ namespace BaseGames.Player
|
||||
private Rigidbody2D _rb;
|
||||
private float _coyoteTimer;
|
||||
private bool _isGrounded;
|
||||
// Update 中调用 ZeroHorizontalVelocity 后设置此标记;
|
||||
// 下一个 FixedUpdate(-200,先于状态机 -100)读取并清零,
|
||||
// 防止状态机用旧输入把速度重新写成非零值。
|
||||
private bool _pendingHorizontalZero;
|
||||
private bool _isWallLeft;
|
||||
private bool _isWallRight;
|
||||
private bool _onOneWayPlatform;
|
||||
@@ -48,12 +55,21 @@ namespace BaseGames.Player
|
||||
{
|
||||
Debug.Assert(_config != null, "[PlayerMovement] _config 未赋值,请在 Inspector 中指定 PlayerMovementConfigSO。", this);
|
||||
_rb = GetComponent<Rigidbody2D>();
|
||||
// 关闭位置插值:若开启插值,渲染位置会在速度清零后仍追赶 1~2 渲染帧,产生视觉滑行。
|
||||
_rb.interpolation = RigidbodyInterpolation2D.None;
|
||||
if (_spriteRenderer == null)
|
||||
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
// 优先处理来自 Update 的强制清零请求(在状态机 OnStateFixedUpdate 之前执行)。
|
||||
if (_pendingHorizontalZero)
|
||||
{
|
||||
_rb.velocity = new Vector2(0f, _rb.velocity.y);
|
||||
_pendingHorizontalZero = false;
|
||||
}
|
||||
|
||||
CheckGrounded();
|
||||
CheckWalls();
|
||||
|
||||
@@ -64,12 +80,25 @@ namespace BaseGames.Player
|
||||
}
|
||||
|
||||
// ── 移动 ──────────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// 直接赋予目标水平速度(按键即全速,松键即停,无加速过渡)。
|
||||
/// 地面状态每帧直接到达全速;空中调用时同样即时,但配合 ApplyAirDrag
|
||||
/// 在无输入时自然减速,保留跳出时的动量。
|
||||
/// </summary>
|
||||
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(speedX, _rb.velocity.y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 空中无输入时施加空气阻力:水平速度乘以 <paramref name="factor"/>,
|
||||
/// 低于阈值时归零,避免速度无限趋近 0。
|
||||
/// 在 FallState / JumpState / WallJumpState 的 OnStateFixedUpdate 中调用。
|
||||
/// </summary>
|
||||
public void ApplyAirDrag(float factor)
|
||||
{
|
||||
float newX = _rb.velocity.x * factor;
|
||||
if (Mathf.Abs(newX) < 0.05f) newX = 0f;
|
||||
_rb.velocity = new Vector2(newX, _rb.velocity.y);
|
||||
}
|
||||
|
||||
@@ -87,7 +116,7 @@ namespace BaseGames.Player
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二段跳(Monarch Wings 等效)。覆盖当前垂直速度为 DoubleJumpForce。
|
||||
/// 二段跳。覆盖当前垂直速度为 DoubleJumpForce。
|
||||
/// FallState / JumpState 在检测到 HasAbility(DoubleJump) && AirJumpsLeft > 0 时调用。
|
||||
/// </summary>
|
||||
public void DoubleJump()
|
||||
@@ -105,7 +134,13 @@ namespace BaseGames.Player
|
||||
|
||||
// ── 速度控制 ──────────────────────────────────────────────────────────
|
||||
public void ZeroVelocity() => _rb.velocity = Vector2.zero;
|
||||
public void ZeroHorizontalVelocity() => _rb.velocity = new Vector2(0f, _rb.velocity.y);
|
||||
public void ZeroHorizontalVelocity()
|
||||
{
|
||||
_rb.velocity = new Vector2(0f, _rb.velocity.y);
|
||||
// 设置标记:下一个 FixedUpdate 开头再次强制清零,
|
||||
// 防止因读到旧输入而把速度重新写成非零值。
|
||||
_pendingHorizontalZero = true;
|
||||
}
|
||||
|
||||
// ── 朝向 ──────────────────────────────────────────────────────────────
|
||||
public void UpdateFacing()
|
||||
|
||||
@@ -7,40 +7,44 @@ namespace BaseGames.Player
|
||||
{
|
||||
[Header("地面移动")]
|
||||
public float RunSpeed = 7f;
|
||||
public float Acceleration = 50f;
|
||||
public float Deceleration = 80f;
|
||||
|
||||
[Header("跳跃(对齐空洞骑士手感)")]
|
||||
[Tooltip("一段跳初速度。HK 约 18-20,对应 ~4-5 格高度。")]
|
||||
[Header("空中移动")]
|
||||
[Tooltip("无水平输入时每个 FixedUpdate 帧水平速度的保留比例(0~1)。" +
|
||||
"0.92 ≈ 半衰期 0.17s(50Hz),松开方向键后空中动量自然衰减。")]
|
||||
[Range(0f, 1f)]
|
||||
public float AirDragFactor = 0.92f;
|
||||
|
||||
[Header("跳跃")]
|
||||
[Tooltip("一段跳初速度。推荐 18-20,对应 ~4-5 格高度。")]
|
||||
public float JumpForce = 19f;
|
||||
[Tooltip("按住跳跃键可保持的郊狼时间。HK ~0.12s。")]
|
||||
[Tooltip("按住跳跃键可保持的郊狼时间。推荐 0.12s。")]
|
||||
public float CoyoteTime = 0.12f;
|
||||
[Tooltip("下落阶段额外重力倍率。HK ~3.5,使下落比上升更快、手感更紧实。")]
|
||||
[Tooltip("下落阶段额外重力倍率。推荐 3.5,使下落比上升更快、手感更紧实。")]
|
||||
public float FallGravityMult = 3.5f;
|
||||
[Tooltip("最大下落速度(终端速度)。HK ~22。")]
|
||||
[Tooltip("最大下落速度(终端速度)。推荐 22。")]
|
||||
public float MaxFallSpeed = 22f;
|
||||
[Tooltip("松开跳跃键时速度保留比例(变高跳)。HK ~0.45,越小跳跃越低。")]
|
||||
[Tooltip("松开跳跃键时速度保留比例(变高跳)。推荐 0.45,越小跳跃越低。")]
|
||||
[Range(0f, 1f)]
|
||||
public float JumpCutMultiplier = 0.45f;
|
||||
|
||||
[Header("二段跳(Monarch Wings 等效)")]
|
||||
[Tooltip("二段跳初速度。设为与 JumpForce 相同可获得等高二段跳(HK 风格)。")]
|
||||
[Header("二段跳")]
|
||||
[Tooltip("二段跳初速度。设为与 JumpForce 相同可获得等高二段跳。")]
|
||||
public float DoubleJumpForce = 19f;
|
||||
|
||||
[Header("冲刺(对齐空洞骑士 Mothwing Cloak 手感)")]
|
||||
[Tooltip("冲刺速度(单位/秒)。HK ~25,在 0.35s 内约穿越 7-8 格。")]
|
||||
[Header("冲刺")]
|
||||
[Tooltip("冲刺速度(单位/秒)。推荐 25,在 0.35s 内约穿越 7-8 格。")]
|
||||
public float DashSpeed = 25f;
|
||||
[Tooltip("冲刺持续时长(秒)。HK ~0.35s。")]
|
||||
[Tooltip("冲刺持续时长(秒)。推荐 0.35s。")]
|
||||
public float DashDuration = 0.35f;
|
||||
[Tooltip("冲刺冷却时长(秒)。HK ~0.6s,落地后才可再次冲刺。")]
|
||||
[Tooltip("冲刺冷却时长(秒)。推荐 0.6s,落地后才可再次冲刺。")]
|
||||
public float DashCooldown = 0.6f;
|
||||
[Tooltip("每次腾空可使用的最大空中冲刺次数。HK = 1(Mothwing Cloak)。")]
|
||||
[Tooltip("每次腾空可使用的最大空中冲刺次数。通常设为 1(单次空中冲刺)。")]
|
||||
public int MaxAerialDashes = 1;
|
||||
|
||||
[Header("冲刺无敌帧(对齐空洞骑士:窗口 < 冲刺时长,且有独立 CD)")]
|
||||
[Tooltip("冲刺无敌窗口时长(秒)。仅为冲刺前段;窗口结束后即使仍在冲刺中也可受伤被打断(HK ~0.20s)。")]
|
||||
[Header("冲刺无敌帧(窗口 < 冲刺时长,且有独立 CD)")]
|
||||
[Tooltip("冲刺无敌窗口时长(秒)。仅为冲刺前段;窗口结束后即使仍在冲刺中也可受伤被打断(推荐 0.20s)。")]
|
||||
public float DashInvincibilityDuration = 0.20f;
|
||||
[Tooltip("无敌的独立冷却(秒)。CD 内再次冲刺不会获得无敌帧,防止连冲变相持续无敌(HK ~0.9s)。")]
|
||||
[Tooltip("无敌的独立冷却(秒)。CD 内再次冲刺不会获得无敌帧,防止连冲变相持续无敌(推荐 0.9s)。")]
|
||||
public float DashInvincibilityCooldown = 0.9f;
|
||||
|
||||
[Header("蹬墙 / 壁滑")]
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace BaseGames.Player.States
|
||||
/// <summary>
|
||||
/// 空中冲刺状态(架构 05_PlayerModule §12)。
|
||||
/// 与地面 DashState 独立,消耗 MaxAerialDashes 次数;
|
||||
/// 空中冲刺可向任意方向(使用移动输入方向,无输入则使用朝向)。
|
||||
/// 冲刺方向在进入时锁定为当前朝向(进入时锁定朝向,冲刺期间不可通过输入改变方向)。
|
||||
/// </summary>
|
||||
public class AerialDashState : PlayerStateBase
|
||||
{
|
||||
@@ -41,10 +41,9 @@ namespace BaseGames.Player.States
|
||||
dashState.ResetInvincibilityCooldown(Cfg.DashInvincibilityCooldown);
|
||||
}
|
||||
|
||||
// 关闭重力,施加冲刺速度(空中冲刺不改变垂直速度)
|
||||
// 关闭重力,施加冲刺速度(方向锁定为进入时朝向,不受输入影响)
|
||||
Move?.SetGravityScale(0f);
|
||||
float dir = Input.MoveInput.x != 0 ? Mathf.Sign(Input.MoveInput.x) : _facingDir;
|
||||
Move?.Dash(new Vector2(dir, 0f), Cfg.DashSpeed);
|
||||
Move?.Dash(new Vector2(_facingDir, 0f), Cfg.DashSpeed);
|
||||
|
||||
// 播放冲刺动画(复用地面冲刺动画)
|
||||
if (AnimCfg?.Dash != null) Anim?.Play(AnimCfg.Dash);
|
||||
@@ -54,6 +53,15 @@ namespace BaseGames.Player.States
|
||||
{
|
||||
_timer -= Time.deltaTime;
|
||||
if (_timer <= 0f)
|
||||
{
|
||||
Move?.SetGravityScale(Cfg.DefaultGravityScale);
|
||||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
|
||||
// 撞墙立即终止冲刺(碰到实体墙立即中止,避免压墙卡住)
|
||||
var wd = Owner.WallDetector;
|
||||
if (wd != null && wd.IsTouchingWall && wd.WallDirection == _facingDir)
|
||||
{
|
||||
Move?.SetGravityScale(Cfg.DefaultGravityScale);
|
||||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||||
@@ -67,12 +75,9 @@ namespace BaseGames.Player.States
|
||||
|
||||
public override void OnStateFixedUpdate()
|
||||
{
|
||||
// 冲刺期间锁定速度
|
||||
// 冲刺期间保持锁定方向速度(与 DashState 一致,使用 _facingDir)
|
||||
if (_timer > 0f)
|
||||
{
|
||||
float dir = Input.MoveInput.x != 0 ? Mathf.Sign(Input.MoveInput.x) : _facingDir;
|
||||
Move?.Dash(new Vector2(dir, 0f), Cfg.DashSpeed);
|
||||
}
|
||||
Move?.Dash(new Vector2(_facingDir, 0f), Cfg.DashSpeed);
|
||||
}
|
||||
|
||||
/// <summary>着地时重置空中冲刺次数(由 PlayerController 在着地时调用)。</summary>
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace BaseGames.Player.States
|
||||
// 无敌帧:
|
||||
// 条件 1:已解锁 InvincibleDash
|
||||
// 条件 2:无敌冷却已就绪(防止 spam 冲刺连序无敌)
|
||||
// 窗口时长 = DashInvincibilityDuration < DashDuration,冲刺后段无保护(对齐 HK)
|
||||
// 窗口时长 = DashInvincibilityDuration < DashDuration,冲刺后段无保护
|
||||
if (Stats != null && Stats.HasAbility(AbilityType.InvincibleDash) && CanGrantInvincibility)
|
||||
{
|
||||
Stats.BeginInvincibility(Cfg.DashInvincibilityDuration);
|
||||
@@ -66,6 +66,14 @@ namespace BaseGames.Player.States
|
||||
{
|
||||
_timer -= Time.deltaTime;
|
||||
if (_timer <= 0f)
|
||||
{
|
||||
EndDash();
|
||||
return;
|
||||
}
|
||||
|
||||
// 撞墙立即终止冲刺(碰到实体墙立即中止,避免压墙卡住)
|
||||
var wd = Owner.WallDetector;
|
||||
if (wd != null && wd.IsTouchingWall && wd.WallDirection == _facingDir)
|
||||
EndDash();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using UnityEngine;
|
||||
namespace BaseGames.Player.States
|
||||
{
|
||||
/// <summary>
|
||||
/// 下落状态(对齐空洞骑士手感)。
|
||||
/// 下落状态。
|
||||
/// - 郊狼跳:CoyoteTimer > 0 时按跳跃 → 一段跳(JumpState,使用 JumpForce)。
|
||||
/// - 二段跳:CoyoteTimer 耗尽后按跳跃且 AirJumpsLeft > 0 → JumpState(使用 DoubleJumpForce)。
|
||||
/// - 空中冲刺:HasAbility(AirDash) && HasAerialDash → AerialDashState。
|
||||
@@ -38,7 +38,7 @@ namespace BaseGames.Player.States
|
||||
_owner.TransitionTo(_owner.GetState<JumpState>());
|
||||
return;
|
||||
}
|
||||
// 无跳跃机会:输入已消耗,静默忽略(HK 相同行为)
|
||||
// 无跳跃机会:输入已消耗,静默忽略(无可用跳跃机会时静默消耗输入缓冲)
|
||||
}
|
||||
|
||||
// ── 空中冲刺────────────────────────────────────────────────────────
|
||||
@@ -64,14 +64,17 @@ namespace BaseGames.Player.States
|
||||
return;
|
||||
}
|
||||
|
||||
// 空中水平移动
|
||||
if (Mathf.Abs(Input.MoveInput.x) > 0.01f)
|
||||
Move.Move(Input.MoveInput.x * Cfg.RunSpeed);
|
||||
}
|
||||
|
||||
public override void OnStateFixedUpdate()
|
||||
{
|
||||
// 增强下落重力(FallGravityMult 对齐 HK:下落比上升更快)
|
||||
// 空中水平移动:有输入时立即覆盖至目标速度;无输入时施加空气阻力保留动量
|
||||
if (Mathf.Abs(Input.MoveInput.x) > 0.01f)
|
||||
Move.Move(Input.MoveInput.x * Cfg.RunSpeed);
|
||||
else
|
||||
Move.ApplyAirDrag(Cfg.AirDragFactor);
|
||||
|
||||
// 增强下落重力(FallGravityMult:下落比上升更快,手感更紧实)
|
||||
if (Move.Rb.velocity.y < 0f)
|
||||
{
|
||||
float extraGrav = Physics2D.gravity.y * (Cfg.FallGravityMult - 1f) * Time.fixedDeltaTime;
|
||||
|
||||
@@ -3,9 +3,9 @@ using UnityEngine;
|
||||
namespace BaseGames.Player.States
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳跃状态(对齐空洞骑士手感)。
|
||||
/// 跳跃状态。
|
||||
/// - 一段跳 / 郊狼跳:OnStateEnter 时调用 Move.Jump()。
|
||||
/// - 二段跳(Monarch Wings 等效):上升或下落途中再按跳跃且 AirJumpsLeft > 0,
|
||||
/// - 二段跳(二段跳能力解锁后可用):上升或下落途中再按跳跃且 AirJumpsLeft > 0,
|
||||
/// 调用 Move.DoubleJump(),重播跳跃动画,不离开本状态(保持速度截断逻辑)。
|
||||
/// - 空中冲刺:上升途中按冲刺且 HasAbility(AirDash) → AerialDashState。
|
||||
/// - 变高跳:松开跳跃键触发 JumpCancelledEvent → CutJump()(系数 = JumpCutMultiplier)。
|
||||
@@ -59,7 +59,7 @@ namespace BaseGames.Player.States
|
||||
}
|
||||
}
|
||||
|
||||
// 二段跳:上升阶段即可触发(类比 HK Monarch Wings,随时可二段跳)
|
||||
// 二段跳:上升阶段即可触发(上升途中任意时刻可二段跳)
|
||||
if (Buffer.ConsumeJump() && Owner.AirJumpsLeft > 0)
|
||||
{
|
||||
Owner.UseAirJump();
|
||||
@@ -68,10 +68,15 @@ namespace BaseGames.Player.States
|
||||
if (AnimCfg?.Jump != null) Anim?.Play(AnimCfg.Jump);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 水平移动(HK 空中控制:与跑步同速)
|
||||
public override void OnStateFixedUpdate()
|
||||
{
|
||||
// 空中水平移动:有输入时立即覆盖至目标速度;无输入时施加空气阻力保留动量
|
||||
if (Mathf.Abs(Input.MoveInput.x) > 0.01f)
|
||||
Move.Move(Input.MoveInput.x * Cfg.RunSpeed);
|
||||
else
|
||||
Move.ApplyAirDrag(Cfg.AirDragFactor);
|
||||
}
|
||||
|
||||
public override void OnStateExit()
|
||||
|
||||
@@ -105,7 +105,8 @@ namespace BaseGames.Player.States
|
||||
// ── IPoiseSource 实现(架构 06_CombatModule §13)─────────────────────
|
||||
/// <summary>
|
||||
/// 玩家不拥有霸体,始终返回 <see cref="PoiseLevel.None"/>。
|
||||
/// 设计决策:类似 Hollow Knight,玩家依靠走位和弹反规避伤害,而非硬吃。
|
||||
/// 设计决策:玩家不拥有霸体,始终返回 <see cref="PoiseLevel.None"/>。
|
||||
/// 玩家依靠走位和弹反规避伤害而非硬吃,以保持战斗的负担感和张力。
|
||||
/// 若未来需要临时霸体(如特定技能动作),请通过独立的覆盖标记实现,
|
||||
/// 而非在此处引入状态,以保持接口语义清晰。
|
||||
/// </summary>
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace BaseGames.Player.States
|
||||
}
|
||||
if (Mathf.Abs(Input.MoveInput.x) < 0.1f)
|
||||
{
|
||||
Move.ZeroHorizontalVelocity();
|
||||
_owner.TransitionTo(_owner.GetState<IdleState>());
|
||||
return;
|
||||
}
|
||||
@@ -45,7 +46,11 @@ namespace BaseGames.Player.States
|
||||
|
||||
public override void OnStateFixedUpdate()
|
||||
{
|
||||
Move.Move(Input.MoveInput.x * Cfg.RunSpeed);
|
||||
float inputX = Input.MoveInput.x;
|
||||
if (Mathf.Abs(inputX) > 0.1f)
|
||||
Move.Move(inputX * Cfg.RunSpeed);
|
||||
else
|
||||
Move.ZeroHorizontalVelocity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +39,17 @@ namespace BaseGames.Player.States
|
||||
|
||||
public override void OnStateUpdate()
|
||||
{
|
||||
_inputLockTimer -= Time.deltaTime;
|
||||
|
||||
// 上升结束 → 下落
|
||||
if (!Move.IsRising)
|
||||
{
|
||||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStateFixedUpdate()
|
||||
{
|
||||
_inputLockTimer -= Time.fixedDeltaTime;
|
||||
|
||||
// 输入锁结束后允许水平控制
|
||||
if (_inputLockTimer <= 0f && Mathf.Abs(Input.MoveInput.x) > 0.01f)
|
||||
|
||||
Reference in New Issue
Block a user