added ground profiles

This commit is contained in:
Nilton Constantino 2026-06-12 16:38:13 +01:00
parent 73a23861a4
commit fd75d7e2d2
No known key found for this signature in database
10 changed files with 148 additions and 41 deletions

View File

@ -14,22 +14,22 @@ namespace Intrepid.Core.Entities
[GroupInjections, SerializeField] private Entity entity; [GroupInjections, SerializeField] private Entity entity;
[GroupConfigurations, SerializeField] private LayerMask groundMask; [GroupConfigurations, SerializeField] private LayerMask groundMask;
[GroupConfigurations, SerializeField] private bool inheritGroundMotionY;
[GroupConfigurations, SerializeField] private float groundCheckHeight = 1.0f; [GroupConfigurations, SerializeField] private float groundCheckHeight = 1.0f;
[GroupConfigurations, SerializeField] private float groundCheckDistance = 3.0f; [GroupConfigurations, SerializeField] private float groundCheckDistance = 3.0f;
[GroupConfigurations, SerializeField] private float groundOffset; [GroupConfigurations, SerializeField] private float groundOffset;
[GroupConfigurations, SerializeField] private float maxSlopeAngle = 45f; [GroupConfigurations, SerializeField] private float maxSlopeAngle = 45f;
[GroupConfigurations, SerializeField] private float groundProbeRadiusGrounded = 0.5f; [GroupConfigurations, SerializeField] private float groundProbeRadiusGrounded = 0.5f;
[GroupConfigurations, SerializeField] private float groundProbeRadiusFalling = 0.2f; [GroupConfigurations, SerializeField] private float groundProbeRadiusFalling = 0.2f;
[GroupConfigurations, SerializeField] private float fallingGroundCheckDistance = 20f; [GroupConfigurations, SerializeField] private float fallingGroundCheckDistance = 150f;
[GroupConfigurations, SerializeField] private float minFallHeight = 0.25f; [GroupConfigurations, SerializeField] private float minFallHeight = 0.25f;
[GroupConfigurations, SerializeField] private float fallAcceleration = 35f; [GroupConfigurations, SerializeField] private float fallAcceleration = 35f;
[GroupConfigurations, SerializeField] private float maxFallSpeed = 18f; [GroupConfigurations, SerializeField] private float maxFallSpeed = 18f;
private Vector3 Origin { get; set; } private Vector3 Origin { get; set; }
private Vector3 End { get; set; } private Vector3 End { get; set; }
private bool HasHit { get; set; } private bool HasHit { get; set; }
private GroundProfiles _currentGroundProfiles = GroundProfiles.None;
private float _fallVerticalSpeed; private float _fallVerticalSpeed;
private RaycastHit _groundHit; private RaycastHit _groundHit;
private float GroundProbeRadius => IsFalling || IsFallingToAbyss private float GroundProbeRadius => IsFalling || IsFallingToAbyss
@ -48,6 +48,7 @@ namespace Intrepid.Core.Entities
{ {
IsFalling = false; IsFalling = false;
IsFallingToAbyss = false; IsFallingToAbyss = false;
_currentGroundProfiles = GroundProfiles.None;
} }
public void Move( public void Move(
@ -60,9 +61,9 @@ namespace Intrepid.Core.Entities
if (IsFallingToAbyss) if (IsFallingToAbyss)
{ {
return; // there is no movement from here.... return;
} }
if (IsFalling) if (IsFalling)
{ {
var nextFalling = ResolveFallingPosition(current, deltaTime); var nextFalling = ResolveFallingPosition(current, deltaTime);
@ -71,19 +72,29 @@ namespace Intrepid.Core.Entities
} }
var movementVelocity = ResolveMovementVelocity(velocity); var movementVelocity = ResolveMovementVelocity(velocity);
var next = current + movementVelocity * deltaTime; var groundMotionDelta = ResolveGroundMotionDelta();
if (TryGetGroundSurfaceAt(next, out _groundHit, out var nextNormal))
var next = current
+ groundMotionDelta
+ movementVelocity * deltaTime;
if (TryGetGroundSurfaceAt(next, out _groundHit, out var nextNormal, out var groundProfiles))
{ {
IsGrounded = true; IsGrounded = true;
IsFallingToAbyss = false; IsFallingToAbyss = false;
_currentGroundProfiles = groundProfiles;
RegisterStableGroundNormal(nextNormal); RegisterStableGroundNormal(nextNormal);
next.y = _groundHit.point.y + groundOffset; next.y = _groundHit.point.y + groundOffset;
LastSafeGroundPosition = new Vector3( LastSafeGroundPosition = new Vector3(
next.x, next.x,
_groundHit.point.y + groundOffset, next.y,
next.z next.z
); );
if (IsFalling) if (IsFalling)
{ {
IsFalling = false; IsFalling = false;
@ -93,17 +104,14 @@ namespace Intrepid.Core.Entities
}); });
} }
} }
else if (options.KeepHeightWhenNoGround) // that is probable an ability like sentinel dash, or enemy jump... else if (options.KeepHeightWhenNoGround)
{ {
IsGrounded = false; IsGrounded = false;
IsFalling = false; IsFalling = false;
IsFallingToAbyss = false; IsFallingToAbyss = false;
// Important: _currentGroundProfiles = GroundProfiles.None;
// Do not set next.y = current.y here.
// The Y value has already been produced by the velocity projected onto the last stable ground normal.
// While crossing gaps, the entity keeps moving along the last GroundSurface plane.
} }
else if (TryAnyGroundSurfaceBelowAt(next, out var fallHit, out _) else if (TryAnyGroundSurfaceBelowAt(next, out var fallHit, out _, out _)
&& IsGroundMeaningfullyBelow(current, fallHit)) && IsGroundMeaningfullyBelow(current, fallHit))
{ {
BeginFall(); BeginFall();
@ -118,6 +126,23 @@ namespace Intrepid.Core.Entities
ApplyMove(body, next); ApplyMove(body, next);
} }
private Vector3 ResolveGroundMotionDelta()
{
if (!IsGrounded || !_currentGroundProfiles.TryGetGroundProfileMotion(out var motion))
{
return Vector3.zero;
}
var delta = motion.DeltaPosition;
if (inheritGroundMotionY)
{
return delta;
}
return new Vector3(delta.x, 0f, delta.z);
}
private Vector3 ResolveFallingPosition(Vector3 current, float deltaTime) private Vector3 ResolveFallingPosition(Vector3 current, float deltaTime)
{ {
IsGrounded = false; IsGrounded = false;
@ -131,7 +156,10 @@ namespace Intrepid.Core.Entities
var next = current; var next = current;
next.y -= _fallVerticalSpeed * deltaTime; next.y -= _fallVerticalSpeed * deltaTime;
if (!TryAnyGroundSurfaceBelowAt(next, out var fallHit, out var fallNormal)) if (!TryAnyGroundSurfaceBelowAt(next,
out var fallHit,
out var fallNormal,
out var fallProfiles))
{ {
BeginFallToAbyss(); BeginFallToAbyss();
return current; // or next? return current; // or next?
@ -148,17 +176,12 @@ namespace Intrepid.Core.Entities
RegisterStableGroundNormal(fallNormal); RegisterStableGroundNormal(fallNormal);
// LastSafeGroundPosition = new Vector3(
// next.x,
// targetY,
// next.z
// );
IsGrounded = true; IsGrounded = true;
IsFalling = false; IsFalling = false;
IsFallingToAbyss = false; IsFallingToAbyss = false;
_fallVerticalSpeed = 0f; _fallVerticalSpeed = 0f;
_currentGroundProfiles = fallProfiles;
Proxy.Instance.EventBus.Raise(new EntityRecoverStableGroundEvent Proxy.Instance.EventBus.Raise(new EntityRecoverStableGroundEvent
{ {
EntityId = entity.Id, EntityId = entity.Id,
@ -178,7 +201,8 @@ namespace Intrepid.Core.Entities
IsFalling = true; IsFalling = true;
IsFallingToAbyss = false; IsFallingToAbyss = false;
_fallVerticalSpeed = 0f; _fallVerticalSpeed = 0f;
_currentGroundProfiles = GroundProfiles.None;
Proxy.Instance.EventBus.Raise(new EntityLeaveStableGroundEvent Proxy.Instance.EventBus.Raise(new EntityLeaveStableGroundEvent
{ {
EntityId = entity.Id, EntityId = entity.Id,
@ -198,6 +222,7 @@ namespace Intrepid.Core.Entities
IsFalling = false; IsFalling = false;
IsFallingToAbyss = true; IsFallingToAbyss = true;
_fallVerticalSpeed = 0f; _fallVerticalSpeed = 0f;
_currentGroundProfiles = GroundProfiles.None;
Proxy.Instance.EventBus.Raise(new EntityFaillingToAbyssEvent Proxy.Instance.EventBus.Raise(new EntityFaillingToAbyssEvent
{ {
@ -219,12 +244,15 @@ namespace Intrepid.Core.Entities
public void RefreshStableGroundNormalAt(Vector3 position) public void RefreshStableGroundNormalAt(Vector3 position)
{ {
if (!TryGetGroundSurfaceAt(position, out _, out var normal)) if (!TryGetGroundSurfaceAt(position, out _, out var normal, out var groundProfiles))
{ {
return; return;
} }
RegisterStableGroundNormal(normal); if (groundProfiles.IsSafe)
{
RegisterStableGroundNormal(normal);
}
} }
private Vector3 ResolveMovementVelocity(Vector3 velocity) private Vector3 ResolveMovementVelocity(Vector3 velocity)
@ -258,15 +286,17 @@ namespace Intrepid.Core.Entities
public bool HasGroundSurfaceAt(Vector3 position) public bool HasGroundSurfaceAt(Vector3 position)
{ {
return TryGetGroundSurfaceAt(position, out _, out _); return TryGetGroundSurfaceAt(position, out _, out _, out _);
} }
private bool TryGetGroundSurfaceAt( private bool TryGetGroundSurfaceAt(
Vector3 position, Vector3 position,
out RaycastHit hit, out RaycastHit hit,
out Vector3 normal) out Vector3 normal,
out GroundProfiles groundProfiles)
{ {
normal = default; normal = default;
groundProfiles = GroundProfiles.None;
if (!TryGetGroundAt(position, out hit)) if (!TryGetGroundAt(position, out hit))
{ {
@ -287,6 +317,8 @@ namespace Intrepid.Core.Entities
return false; return false;
} }
groundProfiles = surface.GroundProfiles;
return true; return true;
} }
@ -311,9 +343,11 @@ namespace Intrepid.Core.Entities
private bool TryAnyGroundSurfaceBelowAt( private bool TryAnyGroundSurfaceBelowAt(
Vector3 position, Vector3 position,
out RaycastHit hit, out RaycastHit hit,
out Vector3 normal) out Vector3 normal,
out GroundProfiles groundProfiles)
{ {
normal = default; normal = default;
groundProfiles = GroundProfiles.None;
var origin = position + Vector3.up * groundCheckHeight; var origin = position + Vector3.up * groundCheckHeight;
@ -341,7 +375,14 @@ namespace Intrepid.Core.Entities
normal = surface.Normal; normal = surface.Normal;
return IsWalkable(normal); if (!IsWalkable(normal))
{
return false;
}
groundProfiles = surface.GroundProfiles;
return true;
} }
private bool IsWalkable(Vector3 normal) private bool IsWalkable(Vector3 normal)

View File

@ -0,0 +1,9 @@
using Intrepid.Utilities;
namespace Intrepid.Core.Grounding
{
public abstract class GroundProfile : Identification
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fdc7306926734d3bb5b74cc1107abb58
timeCreated: 1781275820

View File

@ -0,0 +1,11 @@
using UnityEngine;
namespace Intrepid.Core.Grounding
{
public abstract class GroundProfileMotion : GroundProfile
{
public abstract Vector3 Velocity { get; }
public abstract Vector3 DeltaPosition { get; }
public abstract bool IsMoving { get; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0bf2067f5ce4269a39772ba51ee47e2
timeCreated: 1781275853

View File

@ -0,0 +1,35 @@
using System;
namespace Intrepid.Core.Grounding
{
public readonly struct GroundProfiles
{
public static readonly GroundProfiles None = new(false);
public bool IsSafe { get; }
private GroundProfileMotion GroundProfileMotion { get; }
public GroundProfiles(bool isSafe, GroundProfileMotion groundProfileMotion = null)
{
IsSafe = isSafe;
GroundProfileMotion = groundProfileMotion;
}
private bool TryGet<T>(T value, out T profile) where T: GroundProfile
{
if (value.IsNull())
{
profile = null;
return false;
}
profile = value;
return true;
}
public bool TryGetGroundProfileMotion(out GroundProfileMotion profile)
{
return TryGet(GroundProfileMotion, out profile);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2bcaf8255e9847b8b25727c33769f037
timeCreated: 1781276176

View File

@ -12,21 +12,33 @@ namespace Intrepid.Core.Grounding
public class GroundSurface : MonoBehaviour public class GroundSurface : MonoBehaviour
{ {
[GroupInjections, SerializeField] private BoxCollider boxCollider; [GroupInjections, SerializeField] private BoxCollider boxCollider;
[GroupConfigurations("Profiles"), SerializeField] private bool safe = true;
[GroupConfigurations("Profiles"), SerializeField] private GroundProfileMotion groundProfileMotion;
[GroupDebug, ShowInInspector, ReadOnly] public Vector3 Position { get; private set; } [GroupDebug, ShowInInspector, ReadOnly] public Vector3 Position { get; private set; }
[GroupDebug, ShowInInspector, ReadOnly] public Vector3 Normal { get; private set; } [GroupDebug, ShowInInspector, ReadOnly] public Vector3 Normal { get; private set; }
public GroundProfiles GroundProfiles { get; private set; } = GroundProfiles.None;
private void OnValidate() private void OnValidate()
{ {
if (boxCollider.IsNull()) return; if (boxCollider.IsNull()) return;
DetectPosition(); DetectPosition();
DetectNormal(); DetectNormal();
RefreshProfiles();
} }
private void Awake() private void Awake()
{ {
DetectPosition(); DetectPosition();
DetectNormal(); DetectNormal();
RefreshProfiles();
}
[GroupDebug, Button(ButtonSizes.Medium)]
private void RefreshProfiles()
{
GroundProfiles = new GroundProfiles(safe, groundProfileMotion);
} }
[Button(ButtonSizes.Medium)] [Button(ButtonSizes.Medium)]

View File

@ -1,7 +0,0 @@
namespace Intrepid.Core.Grounding
{
public class MovingGroundSurface : GroundSurface
{
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 488d97fcd10d484b8d9e50393014a05c
timeCreated: 1781271534