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;
[GroupConfigurations, SerializeField] private LayerMask groundMask;
[GroupConfigurations, SerializeField] private bool inheritGroundMotionY;
[GroupConfigurations, SerializeField] private float groundCheckHeight = 1.0f;
[GroupConfigurations, SerializeField] private float groundCheckDistance = 3.0f;
[GroupConfigurations, SerializeField] private float groundOffset;
[GroupConfigurations, SerializeField] private float maxSlopeAngle = 45f;
[GroupConfigurations, SerializeField] private float groundProbeRadiusGrounded = 0.5f;
[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 fallAcceleration = 35f;
[GroupConfigurations, SerializeField] private float maxFallSpeed = 18f;
private Vector3 Origin { get; set; }
private Vector3 End { get; set; }
private bool HasHit { get; set; }
private GroundProfiles _currentGroundProfiles = GroundProfiles.None;
private float _fallVerticalSpeed;
private RaycastHit _groundHit;
private float GroundProbeRadius => IsFalling || IsFallingToAbyss
@ -48,6 +48,7 @@ namespace Intrepid.Core.Entities
{
IsFalling = false;
IsFallingToAbyss = false;
_currentGroundProfiles = GroundProfiles.None;
}
public void Move(
@ -60,9 +61,9 @@ namespace Intrepid.Core.Entities
if (IsFallingToAbyss)
{
return; // there is no movement from here....
return;
}
if (IsFalling)
{
var nextFalling = ResolveFallingPosition(current, deltaTime);
@ -71,19 +72,29 @@ namespace Intrepid.Core.Entities
}
var movementVelocity = ResolveMovementVelocity(velocity);
var next = current + movementVelocity * deltaTime;
if (TryGetGroundSurfaceAt(next, out _groundHit, out var nextNormal))
var groundMotionDelta = ResolveGroundMotionDelta();
var next = current
+ groundMotionDelta
+ movementVelocity * deltaTime;
if (TryGetGroundSurfaceAt(next, out _groundHit, out var nextNormal, out var groundProfiles))
{
IsGrounded = true;
IsFallingToAbyss = false;
_currentGroundProfiles = groundProfiles;
RegisterStableGroundNormal(nextNormal);
next.y = _groundHit.point.y + groundOffset;
LastSafeGroundPosition = new Vector3(
next.x,
_groundHit.point.y + groundOffset,
next.y,
next.z
);
if (IsFalling)
{
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;
IsFalling = false;
IsFallingToAbyss = false;
// Important:
// 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.
_currentGroundProfiles = GroundProfiles.None;
}
else if (TryAnyGroundSurfaceBelowAt(next, out var fallHit, out _)
else if (TryAnyGroundSurfaceBelowAt(next, out var fallHit, out _, out _)
&& IsGroundMeaningfullyBelow(current, fallHit))
{
BeginFall();
@ -118,6 +126,23 @@ namespace Intrepid.Core.Entities
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)
{
IsGrounded = false;
@ -131,7 +156,10 @@ namespace Intrepid.Core.Entities
var next = current;
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();
return current; // or next?
@ -148,17 +176,12 @@ namespace Intrepid.Core.Entities
RegisterStableGroundNormal(fallNormal);
// LastSafeGroundPosition = new Vector3(
// next.x,
// targetY,
// next.z
// );
IsGrounded = true;
IsFalling = false;
IsFallingToAbyss = false;
_fallVerticalSpeed = 0f;
_currentGroundProfiles = fallProfiles;
Proxy.Instance.EventBus.Raise(new EntityRecoverStableGroundEvent
{
EntityId = entity.Id,
@ -178,7 +201,8 @@ namespace Intrepid.Core.Entities
IsFalling = true;
IsFallingToAbyss = false;
_fallVerticalSpeed = 0f;
_currentGroundProfiles = GroundProfiles.None;
Proxy.Instance.EventBus.Raise(new EntityLeaveStableGroundEvent
{
EntityId = entity.Id,
@ -198,6 +222,7 @@ namespace Intrepid.Core.Entities
IsFalling = false;
IsFallingToAbyss = true;
_fallVerticalSpeed = 0f;
_currentGroundProfiles = GroundProfiles.None;
Proxy.Instance.EventBus.Raise(new EntityFaillingToAbyssEvent
{
@ -219,12 +244,15 @@ namespace Intrepid.Core.Entities
public void RefreshStableGroundNormalAt(Vector3 position)
{
if (!TryGetGroundSurfaceAt(position, out _, out var normal))
if (!TryGetGroundSurfaceAt(position, out _, out var normal, out var groundProfiles))
{
return;
}
RegisterStableGroundNormal(normal);
if (groundProfiles.IsSafe)
{
RegisterStableGroundNormal(normal);
}
}
private Vector3 ResolveMovementVelocity(Vector3 velocity)
@ -258,15 +286,17 @@ namespace Intrepid.Core.Entities
public bool HasGroundSurfaceAt(Vector3 position)
{
return TryGetGroundSurfaceAt(position, out _, out _);
return TryGetGroundSurfaceAt(position, out _, out _, out _);
}
private bool TryGetGroundSurfaceAt(
Vector3 position,
out RaycastHit hit,
out Vector3 normal)
out Vector3 normal,
out GroundProfiles groundProfiles)
{
normal = default;
groundProfiles = GroundProfiles.None;
if (!TryGetGroundAt(position, out hit))
{
@ -287,6 +317,8 @@ namespace Intrepid.Core.Entities
return false;
}
groundProfiles = surface.GroundProfiles;
return true;
}
@ -311,9 +343,11 @@ namespace Intrepid.Core.Entities
private bool TryAnyGroundSurfaceBelowAt(
Vector3 position,
out RaycastHit hit,
out Vector3 normal)
out Vector3 normal,
out GroundProfiles groundProfiles)
{
normal = default;
groundProfiles = GroundProfiles.None;
var origin = position + Vector3.up * groundCheckHeight;
@ -341,7 +375,14 @@ namespace Intrepid.Core.Entities
normal = surface.Normal;
return IsWalkable(normal);
if (!IsWalkable(normal))
{
return false;
}
groundProfiles = surface.GroundProfiles;
return true;
}
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
{
[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 Normal { get; private set; }
public GroundProfiles GroundProfiles { get; private set; } = GroundProfiles.None;
private void OnValidate()
{
if (boxCollider.IsNull()) return;
DetectPosition();
DetectNormal();
RefreshProfiles();
}
private void Awake()
{
DetectPosition();
DetectNormal();
RefreshProfiles();
}
[GroupDebug, Button(ButtonSizes.Medium)]
private void RefreshProfiles()
{
GroundProfiles = new GroundProfiles(safe, groundProfileMotion);
}
[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