单向平台

This commit is contained in:
2026-05-21 11:44:01 +08:00
parent 94113a9c1b
commit 67e2d3a8ff
7 changed files with 107 additions and 14 deletions

View File

@@ -0,0 +1,12 @@
namespace BaseGames.Core
{
/// <summary>
/// 单向平台接口:挂载在平台上,允许角色从上方穿落。
/// 定义在 BaseGames.Core 以避免 Player ↔ World 程序集循环引用。
/// </summary>
public interface IDropThrough
{
/// <summary>触发穿落:临时禁用碰撞器,使角色向下穿过平台。</summary>
void TriggerDropThrough();
}
}

View File

@@ -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);

View File

@@ -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()

View File

@@ -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>());

View File

@@ -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>());

View File

@@ -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("按住下方向 + 跳跃时临时禁用碰撞器,允许玩家向下穿过平台")]