- Step rule + lower-body collider: no walking onto tables, steps <= 0.35 m - Structural hand strength: two-hand mantle/pull-up works, one hand can't - Arms aim from the physical shoulder horizontally, keep True Body pose vertically - Crawl pose, crawl carry, 35 deg slope limit, ground feed-forward ignores play-space corrections (fixes a runaway feedback loop) - Grab static world (climbing), procedural visual legs - Playground sandbox; menu strength slider, spectator (old drag) and turn toggles - Physics 144 Hz with interpolation; legacy chase-body spikes pin 90 Hz - RagdollSpike: 27 phases incl. mantle, pull-up, crawl, ledge drop, ramps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
3.4 KiB
C#
110 lines
3.4 KiB
C#
using Godot;
|
|
|
|
public partial class Main : Node3D
|
|
{
|
|
[Signal]
|
|
public delegate void FocusLostEventHandler();
|
|
|
|
[Signal]
|
|
public delegate void FocusGainedEventHandler();
|
|
|
|
[Signal]
|
|
public delegate void PoseRecenteredEventHandler();
|
|
|
|
[Export]
|
|
public int MaximumRefreshRate { get; set; } = 90;
|
|
|
|
public const int PhysicsRate = 144;
|
|
|
|
private OpenXRInterface _xrInterface;
|
|
private bool _xrIsFocused;
|
|
|
|
public override void _Ready()
|
|
{
|
|
_xrInterface = (OpenXRInterface)XRServer.FindInterface("OpenXR");
|
|
if (_xrInterface != null && _xrInterface.IsInitialized())
|
|
{
|
|
GD.Print("OpenXR instantiated successfully.");
|
|
Viewport vp = GetViewport();
|
|
vp.UseXR = true;
|
|
DisplayServer.WindowSetVsyncMode(DisplayServer.VSyncMode.Disabled);
|
|
if (RenderingServer.GetRenderingDevice() != null)
|
|
vp.VrsMode = Viewport.VrsModeEnum.XR;
|
|
|
|
_xrInterface.SessionBegun += OnSessionBegun;
|
|
_xrInterface.SessionVisible += OnSessionVisible;
|
|
_xrInterface.SessionFocussed += OnSessionFocussed;
|
|
_xrInterface.SessionStopping += OnSessionStopping;
|
|
_xrInterface.PoseRecentered += OnPoseRecentered;
|
|
}
|
|
else
|
|
{
|
|
GD.PushWarning("OpenXR not instantiated, running in flat preview mode. Check headset and XR runtime.");
|
|
}
|
|
}
|
|
|
|
private void OnSessionBegun()
|
|
{
|
|
float currentRefreshRate = _xrInterface.DisplayRefreshRate;
|
|
GD.Print(currentRefreshRate > 0.0f
|
|
? $"OpenXR: refresh rate reported as {currentRefreshRate}"
|
|
: "OpenXR: no refresh rate given by XR runtime");
|
|
|
|
float newRate = currentRefreshRate;
|
|
var availableRates = _xrInterface.GetAvailableDisplayRefreshRates();
|
|
if (availableRates.Count == 0)
|
|
GD.Print("OpenXR: target does not support refresh rate extension");
|
|
else if (availableRates.Count == 1)
|
|
newRate = (float)availableRates[0];
|
|
else
|
|
{
|
|
foreach (float rate in availableRates)
|
|
{
|
|
if (rate > newRate && rate <= MaximumRefreshRate)
|
|
newRate = rate;
|
|
}
|
|
}
|
|
|
|
if (currentRefreshRate != newRate)
|
|
{
|
|
GD.Print($"OpenXR: setting refresh rate to {newRate}");
|
|
_xrInterface.DisplayRefreshRate = newRate;
|
|
currentRefreshRate = newRate;
|
|
}
|
|
|
|
// Physics runs at a fixed PhysicsRate (the ragdoll's joints like small steps), decoupled from
|
|
// the display; physics interpolation smooths rendering (XR origin subtree excluded — the
|
|
// runtime and locomotion place it every frame).
|
|
Engine.PhysicsTicksPerSecond = PhysicsRate;
|
|
}
|
|
|
|
private void OnSessionVisible()
|
|
{
|
|
if (_xrIsFocused)
|
|
{
|
|
GD.Print("OpenXR lost focus");
|
|
_xrIsFocused = false;
|
|
GetTree().Paused = true;
|
|
EmitSignal(SignalName.FocusLost);
|
|
}
|
|
}
|
|
|
|
private void OnSessionFocussed()
|
|
{
|
|
GD.Print("OpenXR gained focus");
|
|
_xrIsFocused = true;
|
|
GetTree().Paused = false;
|
|
EmitSignal(SignalName.FocusGained);
|
|
}
|
|
|
|
private void OnSessionStopping()
|
|
{
|
|
GD.Print("OpenXR is stopping");
|
|
}
|
|
|
|
private void OnPoseRecentered()
|
|
{
|
|
EmitSignal(SignalName.PoseRecentered);
|
|
}
|
|
}
|