单向平台
This commit is contained in:
12
Assets/_Game/Scripts/Core/IDropThrough.cs
Normal file
12
Assets/_Game/Scripts/Core/IDropThrough.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 单向平台接口:挂载在平台上,允许角色从上方穿落。
|
||||
/// 定义在 BaseGames.Core 以避免 Player ↔ World 程序集循环引用。
|
||||
/// </summary>
|
||||
public interface IDropThrough
|
||||
{
|
||||
/// <summary>触发穿落:临时禁用碰撞器,使角色向下穿过平台。</summary>
|
||||
void TriggerDropThrough();
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,9 @@ namespace BaseGames.Editor
|
||||
Transform groundCheckT = GetOrCreateChild(root.transform, "GroundCheck");
|
||||
groundCheckT.localPosition = new Vector3(0f, -0.75f, 0f);
|
||||
AssignReference(playerMovement, "_groundCheck", groundCheckT, report);
|
||||
AssignLayerMask(playerMovement, "_groundLayer", "Platform", report);
|
||||
AssignLayerMask(playerMovement, "_groundLayer",
|
||||
new[] { "Platform", "OneWayPlatform", "MovingOneWayPlatform", "MidHeightOneWayPlatform" },
|
||||
report);
|
||||
|
||||
// ── SkillHitBox_Slot 子节点(技能 HitBox 实例化挂点)────────────────
|
||||
Transform skillSocketT = GetOrCreateChild(root.transform, "SkillHitBox_Slot");
|
||||
@@ -1001,6 +1003,31 @@ namespace BaseGames.Editor
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
/// <summary>将多个 Layer 名称合并为一个 LayerMask 并写入 SerializedProperty。</summary>
|
||||
private static void AssignLayerMask(Object target, string propName, string[] layerNames, List<string> report)
|
||||
{
|
||||
int mask = 0;
|
||||
foreach (var name in layerNames)
|
||||
{
|
||||
int layer = LayerMask.NameToLayer(name);
|
||||
if (layer == -1)
|
||||
report.Add($"Layer '{name}' 不存在,已跳过({target.GetType().Name}.{propName})。");
|
||||
else
|
||||
mask |= 1 << layer;
|
||||
}
|
||||
if (mask == 0) return;
|
||||
|
||||
var so = new SerializedObject(target);
|
||||
var sp = so.FindProperty(propName);
|
||||
if (sp == null)
|
||||
{
|
||||
report.Add($"{target.GetType().Name}.{propName} 字段不存在,跳过 LayerMask 赋值。");
|
||||
return;
|
||||
}
|
||||
sp.intValue = mask;
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
private static void AssignInt(Object target, string propName, int value)
|
||||
{
|
||||
var so = new SerializedObject(target);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using BaseGames.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Player
|
||||
@@ -34,7 +35,8 @@ namespace BaseGames.Player
|
||||
private int _facingDirection = 1;
|
||||
private bool _cancelWindowOpen;
|
||||
private SurfaceType _currentSurface = SurfaceType.Ground;
|
||||
private readonly Collider2D[] _groundBuffer = new Collider2D[4];
|
||||
private readonly Collider2D[] _groundBuffer = new Collider2D[4];
|
||||
private int _groundHitCount;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// ── 运行时调试(Inspector 中可见)───────────────────────────────
|
||||
@@ -43,6 +45,7 @@ namespace BaseGames.Player
|
||||
[SerializeField] private float _dbg_VelocityX;
|
||||
[SerializeField] private float _dbg_VelocityY;
|
||||
[SerializeField] private bool _dbg_IsGrounded;
|
||||
[SerializeField] private bool _dbg_OnOneWayPlatform;
|
||||
[SerializeField] private bool _dbg_HasCoyoteTime;
|
||||
[SerializeField] private bool _dbg_IsWallLeft;
|
||||
[SerializeField] private bool _dbg_IsWallRight;
|
||||
@@ -91,10 +94,11 @@ namespace BaseGames.Player
|
||||
_coyoteTimer = Mathf.Max(0f, _coyoteTimer - Time.fixedDeltaTime);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
_dbg_VelocityX = _rb.velocity.x;
|
||||
_dbg_VelocityY = _rb.velocity.y;
|
||||
_dbg_IsGrounded = _isGrounded;
|
||||
_dbg_HasCoyoteTime = _coyoteTimer > 0f;
|
||||
_dbg_VelocityX = _rb.velocity.x;
|
||||
_dbg_VelocityY = _rb.velocity.y;
|
||||
_dbg_IsGrounded = _isGrounded;
|
||||
_dbg_OnOneWayPlatform = _onOneWayPlatform;
|
||||
_dbg_HasCoyoteTime = _coyoteTimer > 0f;
|
||||
_dbg_IsWallLeft = _isWallLeft;
|
||||
_dbg_IsWallRight = _isWallRight;
|
||||
_dbg_CancelWindowOpen = _cancelWindowOpen;
|
||||
@@ -243,19 +247,54 @@ namespace BaseGames.Player
|
||||
_rb.velocity = new Vector2(_rb.velocity.x, 0f);
|
||||
}
|
||||
|
||||
/// <summary>单向平台穿透(输入下行 + 跳跃键时触发)。</summary>
|
||||
public void DropThroughPlatform() { }
|
||||
/// <summary>
|
||||
/// 单向平台穿落(↓ + 跳跃键触发)。
|
||||
/// 找到脚下的 <see cref="IDropThrough"/> 并临时禁用其碰撞器;
|
||||
/// 同帧清除 IsGrounded / OnOneWayPlatform,状态机无需等到下一物理帧即可转换到 FallState。
|
||||
/// </summary>
|
||||
public void DropThroughPlatform()
|
||||
{
|
||||
if (!_onOneWayPlatform) return;
|
||||
|
||||
for (int i = 0; i < _groundHitCount; i++)
|
||||
{
|
||||
if (_groundBuffer[i] != null &&
|
||||
_groundBuffer[i].TryGetComponent<IDropThrough>(out var platform))
|
||||
{
|
||||
platform.TriggerDropThrough();
|
||||
// 立即清除着地状态,让状态机本帧即可切换到 FallState
|
||||
_isGrounded = false;
|
||||
_onOneWayPlatform = false;
|
||||
_coyoteTimer = 0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Physics 检测 ──────────────────────────────────────────────────────
|
||||
private void CheckGrounded()
|
||||
{
|
||||
if (_groundCheck == null) return;
|
||||
|
||||
bool wasGrounded = _isGrounded;
|
||||
_isGrounded = Physics2D.OverlapBoxNonAlloc(_groundCheck.position, _groundCheckSize, 0f, _groundBuffer, _groundLayer) > 0;
|
||||
_groundHitCount = Physics2D.OverlapBoxNonAlloc(
|
||||
_groundCheck.position, _groundCheckSize, 0f, _groundBuffer, _groundLayer);
|
||||
_isGrounded = _groundHitCount > 0;
|
||||
|
||||
if (_isGrounded && !wasGrounded)
|
||||
_coyoteTimer = _config.CoyoteTime;
|
||||
// 检测是否站在单向平台(含 IDropThrough 组件的碰撞体)
|
||||
_onOneWayPlatform = false;
|
||||
for (int i = 0; i < _groundHitCount; i++)
|
||||
{
|
||||
if (_groundBuffer[i] != null &&
|
||||
_groundBuffer[i].TryGetComponent<IDropThrough>(out _))
|
||||
{
|
||||
_onOneWayPlatform = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_currentSurface = (_isGrounded && _onOneWayPlatform)
|
||||
? SurfaceType.OneWayPlatform
|
||||
: SurfaceType.Ground;
|
||||
}
|
||||
|
||||
private void CheckWalls()
|
||||
|
||||
@@ -26,6 +26,13 @@ namespace BaseGames.Player.States
|
||||
_owner.TransitionTo(_owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
// 单向平台穿落:↓ + 跳跃键,优先于普通跳跃,避免误消耗跳跃缓冲
|
||||
if (Move.OnOneWayPlatform && Input.MoveInput.y < -0.5f && Buffer.ConsumeJump())
|
||||
{
|
||||
Move.DropThroughPlatform();
|
||||
_owner.TransitionTo(_owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
if (Buffer.ConsumeJump())
|
||||
{
|
||||
_owner.TransitionTo(_owner.GetState<JumpState>());
|
||||
|
||||
@@ -25,6 +25,13 @@ namespace BaseGames.Player.States
|
||||
_owner.TransitionTo(_owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
// 单向平台穿落:↓ + 跳跃键,优先于普通跳跃
|
||||
if (Move.OnOneWayPlatform && Input.MoveInput.y < -0.5f && Buffer.ConsumeJump())
|
||||
{
|
||||
Move.DropThroughPlatform();
|
||||
_owner.TransitionTo(_owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
if (Buffer.ConsumeJump())
|
||||
{
|
||||
_owner.TransitionTo(_owner.GetState<JumpState>());
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using BaseGames.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.World
|
||||
@@ -10,7 +11,7 @@ namespace BaseGames.World
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
[RequireComponent(typeof(PlatformEffector2D))]
|
||||
public class PhantomPlate : MonoBehaviour
|
||||
public class PhantomPlate : MonoBehaviour, IDropThrough
|
||||
{
|
||||
[Header("下蹲跌落")]
|
||||
[Tooltip("按住下方向 + 跳跃时临时禁用碰撞器,允许玩家向下穿过平台")]
|
||||
|
||||
Reference in New Issue
Block a user