Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class BasisLocalCharacterDriver
{
public BasisLocalPlayer LocalPlayer;
[System.NonSerialized] public BasisLocalAnimatorDriver LocalAnimatorDriver;
public CharacterController characterController;
public BasisKinematicCharacterController characterController;
public Vector3 bottomPointLocalSpace;
public Vector3 LastBottomPoint;
public bool groundedPlayer;
Expand Down Expand Up @@ -104,6 +104,23 @@ public void SetMode(Mode mode)
public Quaternion CurrentRotation;
public CollisionFlags Flags;
public float radius;
/// <summary>
/// The direction gravity pulls the character. Defaults to Vector3.down.
/// Changing this allows the character to walk on walls/ceilings.
/// </summary>
public Vector3 GravityDirection
{
get => characterController != null ? characterController.GravityDirection : Vector3.down;
set
{
if (characterController != null)
characterController.GravityDirection = value;
}
}
/// <summary>
/// The up direction for the character, opposite of GravityDirection.
/// </summary>
public Vector3 UpDirection => characterController != null ? characterController.UpDirection : Vector3.up;
public Vector2 MovementVector { get; private set; }
/// <summary>
/// A value between 0 and 1 representing the relative speed of player movement.
Expand Down Expand Up @@ -158,13 +175,56 @@ public void DeInitalize()
HasEvents = false;
}
}
/// <summary>
/// Ensures the KCC component exists on the player GameObject.
/// Migrates from Unity's CharacterController if one is still present on the prefab.
/// </summary>
private void EnsureKCC()
{
if (characterController != null) return;

GameObject go = BasisLocalPlayerTransform.gameObject;

// Check if already has KCC
characterController = go.GetComponent<BasisKinematicCharacterController>();
if (characterController != null) return;

// Migrate from legacy CharacterController if present
CharacterController legacy = go.GetComponent<CharacterController>();
float oldHeight = 2f;
float oldRadius = 0.3f;
Vector3 oldCenter = new Vector3(0f, 1f, 0f);
float oldSkinWidth = 0.01f;
float oldStepOffset = 0.3f;
float oldSlopeLimit = 45f;
if (legacy != null)
{
oldHeight = legacy.height;
oldRadius = legacy.radius;
oldCenter = legacy.center;
oldSkinWidth = legacy.skinWidth;
oldStepOffset = legacy.stepOffset;
oldSlopeLimit = legacy.slopeLimit;
UnityEngine.Object.DestroyImmediate(legacy);
}

characterController = go.AddComponent<BasisKinematicCharacterController>();
characterController.height = oldHeight;
characterController.radius = oldRadius;
characterController.center = oldCenter;
characterController.skinWidth = oldSkinWidth;
characterController.stepOffset = oldStepOffset;
characterController.slopeLimit = oldSlopeLimit;
}
public void Initialize(BasisLocalPlayer localPlayer)
{
LocalPlayer = localPlayer;
BasisLocalPlayerTransform = localPlayer.transform;
LocalAnimatorDriver = localPlayer.LocalAnimatorDriver;
EnsureKCC();
characterController.minMoveDistance = 0;
characterController.skinWidth = 0.01f;
characterController.OnKCCColliderHit = OnKCCHit;
if (!HasEvents)
{
HasEvents = true;
Expand All @@ -176,22 +236,20 @@ public void Initialize(BasisLocalPlayer localPlayer)
SetMode(Mode.Walk);
}

public void OnControllerColliderHit(ControllerColliderHit hit)
private void OnKCCHit(KCCHitInfo hit)
{
if (CanPushRigidbodys)
{
// Check if the hit object has a Rigidbody and if it is not kinematic
Rigidbody body = hit.collider.attachedRigidbody;

if (body == null || body.isKinematic)
{
return;
}

// Ensure we're only pushing objects in the horizontal plane
Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
// Project push direction onto the plane perpendicular to gravity (horizontal)
Vector3 pushDir = hit.moveDirection - UpDirection * Vector3.Dot(hit.moveDirection, UpDirection);

// Apply the force to the object
body.AddForce(pushDir * pushPower, ForceMode.Impulse);
}
}
Expand Down Expand Up @@ -264,7 +322,7 @@ public void SimulateMovement(float DeltaTime)

// Get the current rotation and position of the player
Vector3 pivot = BasisLocalBoneDriver.EyeControl.OutgoingWorldData.position;
Vector3 upAxis = Vector3.up;
Vector3 upAxis = UpDirection;

// Calculate direction from the pivot to the current position
Vector3 directionToPivot = CurrentPosition - pivot;
Expand All @@ -280,7 +338,7 @@ public void SimulateMovement(float DeltaTime)
BasisLocalPlayerTransform.SetPositionAndRotation(FinalRotation, rotation * CurrentRotation);

float HeightOffset = (characterController.height / 2) - characterController.radius;
bottomPointLocalSpace = FinalRotation + (characterController.center - new Vector3(0, HeightOffset, 0));
bottomPointLocalSpace = FinalRotation + (characterController.center - upAxis * HeightOffset);

Quaternion newRot = rotation * CurrentRotation;
Vector3 newPos = FinalRotation;
Expand Down Expand Up @@ -388,16 +446,20 @@ public void SetMovementVector(Vector2 movement)
}
public void HandleMovement(float DeltaTime)
{
// Cache current rotation and zero out x and z components
// Cache current rotation and flatten to the plane perpendicular to gravity
currentRotation = BasisLocalBoneDriver.HeadControl.OutgoingWorldData.rotation;
Vector3 rotationEulerAngles = currentRotation.eulerAngles;
rotationEulerAngles.x = 0;
rotationEulerAngles.z = 0;

Quaternion flattenedRotation = Quaternion.Euler(rotationEulerAngles);
Vector3 up = UpDirection;
Vector3 flatForward = currentRotation * Vector3.forward;
flatForward -= up * Vector3.Dot(flatForward, up);
if (flatForward.sqrMagnitude < 0.0001f)
{
flatForward = -(currentRotation * Vector3.up);
flatForward -= up * Vector3.Dot(flatForward, up);
}
Quaternion flattenedRotation = Quaternion.LookRotation(flatForward.normalized, up);

if (CrouchBlendDelta != 0) UpdateCrouchBlend(CrouchBlendDelta);
// Calculate horizontal movement direction
// Calculate horizontal movement direction (in the character's gravity-relative plane)
Vector3 horizontalMoveDirection = new Vector3(MovementVector.x, 0, MovementVector.y).normalized;

CurrentSpeed = math.lerp(MinimumMovementSpeed, MaximumMovementSpeed, MovementSpeedScale) + MinimumMovementSpeed * MovementSpeedBoost;
Expand Down Expand Up @@ -427,7 +489,8 @@ public void HandleMovement(float DeltaTime)


HasJumpAction = false;
totalMoveDirection.y = currentVerticalSpeed * DeltaTime;
// Apply vertical speed along the gravity up axis instead of world Y
totalMoveDirection += up * (currentVerticalSpeed * DeltaTime);

// Move character
Flags = characterController.Move(totalMoveDirection);
Expand Down Expand Up @@ -479,7 +542,7 @@ public void CalculateCharacterSize()
}

// Clamp stepOffset to something sane relative to height
float maxStep = (finalHeight + 2f * characterController.radius) - 0.001f;
float maxStep = (finalHeight + 2f * radius) - 0.001f;
maxStep = Mathf.Max(0f, maxStep);
maxStep = Mathf.Min(maxStep, finalHeight * 0.25f);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public void Enter(BasisLocalCharacterDriver ctx)
{
if (ctx.characterController != null)
{
ctx.characterController.detectCollisions = true; // solid, but no gravity
ctx.characterController.detectCollisions = true;
ctx.characterController.enabled = true;
}
ctx.currentVerticalSpeed = 0f;
Expand All @@ -24,16 +24,18 @@ public void Exit(BasisLocalCharacterDriver ctx) { }

public void Tick(BasisLocalCharacterDriver ctx, float dt)
{
// Project head forward onto horizontal plane (avoids gimbal lock near ±90° pitch)
Vector3 up = ctx.UpDirection;

// Project head forward onto the plane perpendicular to gravity
Quaternion headRot = BasisLocalBoneDriver.HeadControl.OutgoingWorldData.rotation;
Vector3 flatForward = headRot * Vector3.forward;
flatForward.y = 0f;
flatForward -= up * Vector3.Dot(flatForward, up);
if (flatForward.sqrMagnitude < 0.0001f)
{
flatForward = -(headRot * Vector3.up);
flatForward.y = 0f;
flatForward -= up * Vector3.Dot(flatForward, up);
}
Quaternion facing = Quaternion.LookRotation(flatForward.normalized, Vector3.up);
Quaternion facing = Quaternion.LookRotation(flatForward.normalized, up);

// Planar
Vector3 planar = new Vector3(ctx.MovementVector.x, 0, ctx.MovementVector.y).normalized;
Expand All @@ -44,8 +46,8 @@ public void Tick(BasisLocalCharacterDriver ctx, float dt)

Vector3 move = facing * planar * ctx.CurrentSpeed * dt;

// ===== Vertical input (held) =====
move.y = ctx.GetVerticalMovement() * ctx.CurrentSpeed * dt;
// Vertical input along gravity-relative up axis
move += up * (ctx.GetVerticalMovement() * ctx.CurrentSpeed * dt);

// Clear tap
ctx.HasJumpAction = false;
Expand Down
Loading