Add CharacterController.Resize for quick crouching implementation for characters

This commit is contained in:
Wojtek Figat
2025-06-05 15:07:51 +02:00
parent f6feae5cf2
commit e982a23ed3
4 changed files with 106 additions and 26 deletions

View File

@@ -96,15 +96,11 @@ void CharacterController::SetStepOffset(float value)
{
if (value == _stepOffset)
return;
_stepOffset = value;
if (_controller)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float contactOffset = Math::Max(_contactOffset, ZeroTolerance);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, CC_MIN_SIZE);
float height, radius;
GetControllerSize(height, radius);
PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - CC_MIN_SIZE));
}
}
@@ -180,6 +176,37 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
return result;
}
void CharacterController::Resize(float height, float radius)
{
const float heightDiff = height - _height;
const float radiusDiff = radius - _radius;
if (Math::IsZero(heightDiff) && Math::IsZero(radiusDiff))
return;
_height = height;
_radius = radius;
if (_controller)
{
float centerDiff = heightDiff * 0.5f + radiusDiff;
// Change physics size
GetControllerSize(height, radius);
PhysicsBackend::SetControllerSize(_controller, radius, height);
Vector3 positionDelta = _upDirection * centerDiff;
// Change physics position to maintain feet placement (base)
Vector3 position = PhysicsBackend::GetControllerPosition(_controller);
position += positionDelta;
_center += positionDelta;
PhysicsBackend::SetControllerPosition(_controller, position);
// Change actor position
_isUpdatingTransform = true;
SetPosition(position - _center);
_isUpdatingTransform = false;
}
UpdateBounds();
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
@@ -187,23 +214,45 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
void CharacterController::DrawPhysicsDebug(RenderView& view)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
Quaternion rotation = Quaternion::Euler(90, 0, 0);
const Vector3 position = _transform.LocalToWorld(_center);
if (view.Mode == ViewMode::PhysicsColliders)
DEBUG_DRAW_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true);
DEBUG_DRAW_CAPSULE(position, rotation, _radius, _height, Color::LightYellow, 0, true);
else
DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true);
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow * 0.8f, 0, true);
}
void CharacterController::OnDebugDrawSelected()
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
Quaternion rotation = Quaternion::Euler(90, 0, 0);
const Vector3 position = _transform.LocalToWorld(_center);
DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false);
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow, 0, false);
if (_contactOffset > 0)
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius - _contactOffset, _height, Color::Blue.AlphaMultiplied(0.4f), 0, false);
#if 0
// More technical visuals debugging
if (_controller)
{
float height, radius;
GetControllerSize(height, radius);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerBasePosition(_controller), 5.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller), 4.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) + Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) - Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) + Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(PhysicsBackend::GetControllerPosition(_controller) - Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false);
DEBUG_DRAW_WIRE_CYLINDER(PhysicsBackend::GetControllerPosition(_controller), Quaternion::Identity, radius, height, Color::Red.AlphaMultiplied(0.2f), 0, false);
}
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, 3.0f), Color::GreenYellow, 0, false);
#else
if (_controller)
{
// Physics backend capsule shape
float height, radius;
GetControllerSize(height, radius);
DEBUG_DRAW_WIRE_CAPSULE(PhysicsBackend::GetControllerPosition(_controller), rotation, radius, height, Color::Blue.AlphaMultiplied(0.2f), 0, false);
}
#endif
// Base
Collider::OnDebugDrawSelected();
@@ -216,8 +265,10 @@ void CharacterController::CreateController()
// Create controller
ASSERT(_controller == nullptr && _shape == nullptr);
_cachedScale = GetScale().GetAbsolute().MaxValue();
float height, radius;
GetControllerSize(height, radius);
const Vector3 position = _transform.LocalToWorld(_center);
_controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape);
_controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, radius, height, _stepOffset, _shape);
// Setup
PhysicsBackend::SetControllerUpDirection(_controller, _upDirection);
@@ -240,13 +291,24 @@ void CharacterController::UpdateSize() const
{
if (_controller)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
float height, radius;
GetControllerSize(height, radius);
PhysicsBackend::SetControllerSize(_controller, radius, height);
}
}
void CharacterController::GetControllerSize(float& height, float& radius) const
{
height = Math::Abs(_height) * _cachedScale;
radius = Math::Abs(_radius) * _cachedScale;
{
// Exclude contact offset around the capsule (otherwise character floats in the air)
radius = radius - Math::Max(_contactOffset, 0.0f);
}
height = Math::Max(height, CC_MIN_SIZE);
radius = Math::Max(radius, CC_MIN_SIZE);
}
void CharacterController::CreateShape()
{
// Not used
@@ -254,9 +316,9 @@ void CharacterController::CreateShape()
void CharacterController::UpdateBounds()
{
const float scaling = GetScale().GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
_cachedScale = GetScale().GetAbsolute().MaxValue();
float height, radius;
GetControllerSize(height, radius);
const Vector3 position = _transform.LocalToWorld(_center);
const Vector3 extent(radius, height * 0.5f + radius, radius);
_box = BoundingBox(position - extent, position + extent);

View File

@@ -84,13 +84,13 @@ public:
API_PROPERTY() void SetRadius(float value);
/// <summary>
/// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
/// Gets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetHeight() const;
/// <summary>
/// Sets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
/// Sets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale.
/// </summary>
API_PROPERTY() void SetHeight(float value);
@@ -194,6 +194,13 @@ public:
/// <returns>The collision flags. It can be used to trigger various character animations.</returns>
API_FUNCTION() CollisionFlags Move(const Vector3& displacement);
/// <summary>
/// Updates the character height and center position to ensure its feet position stays the same. This can be used to implement a 'crouch' functionality for example. Maintains the same actor position to stay in the middle of capsule by adjusting center of collider accordingly to height difference.
/// </summary>
/// <param name="height">The height of the capsule, measured in the object's local space.</param>
/// <param name="radius">The radius of the capsule, measured in the object's local space.</param>
API_FUNCTION() void Resize(float height, float radius);
protected:
/// <summary>
/// Creates the physics actor.
@@ -210,6 +217,9 @@ protected:
/// </summary>
void UpdateSize() const;
private:
void GetControllerSize(float& height, float& radius) const;
public:
// [Collider]
#if USE_EDITOR
@@ -220,6 +230,7 @@ public:
void AddMovement(const Vector3& translation, const Quaternion& rotation) override;
bool CanAttach(RigidBody* rigidBody) const override;
RigidBody* GetAttachedRigidBody() const override;
void SetCenter(const Vector3& value) override;
// [IPhysicsActor]
void OnActiveTransformChanged() override;

View File

@@ -3157,7 +3157,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic
desc.material = DefaultMaterial;
const float minSize = 0.001f;
desc.height = Math::Max(height, minSize);
desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize);
desc.radius = Math::Max(radius, minSize);
desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize);
auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc);
PxRigidActor* actorPhysX = controllerPhysX->getActor();
@@ -3183,7 +3183,7 @@ void PhysicsBackend::SetControllerSize(void* controller, float radius, float hei
{
auto controllerPhysX = (PxCapsuleController*)controller;
controllerPhysX->setRadius(radius);
controllerPhysX->resize(height);
controllerPhysX->setHeight(height);
}
void PhysicsBackend::SetControllerSlopeLimit(void* controller, float value)
@@ -3204,6 +3204,12 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value)
controllerPhysX->setStepOffset(value);
}
Vector3 PhysicsBackend::GetControllerBasePosition(void* controller)
{
auto controllerPhysX = (PxCapsuleController*)controller;
return P2C(controllerPhysX->getFootPosition());
}
Vector3 PhysicsBackend::GetControllerUpDirection(void* controller)
{
auto controllerPhysX = (PxCapsuleController*)controller;

View File

@@ -248,6 +248,7 @@ public:
static void SetControllerSlopeLimit(void* controller, float value);
static void SetControllerNonWalkableMode(void* controller, int32 value);
static void SetControllerStepOffset(void* controller, float value);
static Vector3 GetControllerBasePosition(void* controller);
static Vector3 GetControllerUpDirection(void* controller);
static void SetControllerUpDirection(void* controller, const Vector3& value);
static Vector3 GetControllerPosition(void* controller);