Sim speed: one SimClock steps the zone; presenters draw ahead by Lead
SimClock (engine-free) turns real frame seconds into whole fixed steps; ActionGameManager.Update feeds it outside the pause gate and Zone.Update(Time.deltaTime) is gone, with IntroCutscene (dead, Unity-driven pose writes). Entity.TurnRate is the sum of the rotation actuators' applied turn per sim second, zeroed each Update; DrawAhead reads it so EntityInstance and ShipInstance draw the latest sim state ahead by Lead. Unverified at this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
594d347704
commit
c4bd6e153e
11 changed files with 393 additions and 40 deletions
|
|
@ -177,8 +177,6 @@ public class ActionGameManager : MonoBehaviour
|
|||
public Sprite ShieldIcon;
|
||||
public Sprite NoShieldIcon;
|
||||
|
||||
public float IntroDuration;
|
||||
|
||||
//public PlayerInput Input;
|
||||
|
||||
// private CinemachineFramingTransposer _transposer;
|
||||
|
|
@ -231,6 +229,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
|
||||
public ItemManager ItemManager { get; private set; }
|
||||
public Zone Zone { get; private set; }
|
||||
public static SimClock Clock { get; private set; }
|
||||
|
||||
private readonly (float2 direction, string name)[] _directions = {
|
||||
(float2(0, 1), "Front"),
|
||||
|
|
@ -728,6 +727,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
galaxyZone.Contents = new Zone(ItemManager, Settings.PlanetSettings, galaxyZone.PackedContents, galaxyZone, CurrentGalaxy);
|
||||
}
|
||||
Zone = galaxyZone.Contents;
|
||||
Clock = new SimClock(1f / 60f);
|
||||
PlayMusic(MusicType.Overworld);
|
||||
|
||||
Zone.Log = s => Debug.Log($"Zone: {s}");
|
||||
|
|
@ -818,7 +818,6 @@ public class ActionGameManager : MonoBehaviour
|
|||
}
|
||||
else
|
||||
{
|
||||
//StartCoroutine(IntroCutscene(targetEntity as Ship));
|
||||
BindToEntity(targetEntity);
|
||||
}
|
||||
|
||||
|
|
@ -866,35 +865,6 @@ public class ActionGameManager : MonoBehaviour
|
|||
BindToEntity(staged.Player);
|
||||
}
|
||||
|
||||
private IEnumerator IntroCutscene(Ship ship)
|
||||
{
|
||||
ZoneRenderer.PerspectiveEntity = ship;
|
||||
var entityPosition = ship.Position.xz;
|
||||
var followOrbit = Zone.Orbits.Keys.MinBy(o => lengthsq(Zone.GetOrbitPosition(o) - entityPosition));
|
||||
var followPlanet = ZoneRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit.Key.Equals(followOrbit)).Key];
|
||||
DockCamera.Follow = followPlanet.Body.transform;
|
||||
var rootOrbit = followOrbit;
|
||||
while (Zone.Orbits[rootOrbit].Data.Parent.IsSet())
|
||||
rootOrbit = Zone.Orbits[rootOrbit].Data.Parent.Key;
|
||||
var rootPlanet = ZoneRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit.Key.Equals(rootOrbit)).Key];
|
||||
DockCamera.LookAt = rootPlanet.Body.transform;
|
||||
|
||||
var shipVelocity = ship.GetBehavior<VelocityLimit>().Limit;
|
||||
var followOrbitPosition = Zone.GetOrbitPosition(followOrbit);
|
||||
var shipDirection = normalize(Zone.GetOrbitPosition(rootOrbit) - followOrbitPosition);
|
||||
ship.Position.xz = followOrbitPosition - shipDirection * shipVelocity * IntroDuration;
|
||||
|
||||
var startTime = Time.time;
|
||||
while (Time.time - startTime < IntroDuration)
|
||||
{
|
||||
ship.Direction = shipDirection;
|
||||
ship.Velocity = shipDirection * shipVelocity;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
BindToEntity(ship);
|
||||
}
|
||||
|
||||
public void Dock()
|
||||
{
|
||||
if (CurrentEntity.Parent != null) return;
|
||||
|
|
@ -1356,8 +1326,9 @@ public class ActionGameManager : MonoBehaviour
|
|||
CurrentEntity.TractorPower =
|
||||
saturate(CurrentEntity.TractorPower + sign(tractorPower - CurrentEntity.TractorPower) * Time.deltaTime * 2);
|
||||
}
|
||||
Zone.Update(Time.deltaTime);
|
||||
}
|
||||
// One clock steps the zone, paused or not: a paused frame feeds it no real time.
|
||||
Clock.Advance(_paused ? 0f : Time.deltaTime, Zone.Update);
|
||||
}
|
||||
|
||||
private static string ResultLabel(ShotResult result) => result switch
|
||||
|
|
|
|||
|
|
@ -416,9 +416,10 @@ public class EntityInstance : MonoBehaviour
|
|||
var direction = FireControl.Solution(weapon, Entity, Entity.Target.Value).Direction;
|
||||
group.Value.position = barrel + new Vector3(direction.x, 0, direction.y) * aimDistance;
|
||||
}
|
||||
LocalSpace.localPosition = transform.position = Entity.Position.ToUnity();
|
||||
var drawn = DrawAhead.Position(Entity, ActionGameManager.Clock.Lead);
|
||||
LocalSpace.localPosition = transform.position = drawn.ToUnity();
|
||||
if (_influenceInstance)
|
||||
_influenceInstance.position = new Vector3(Entity.Position.x, 0, Entity.Position.z);
|
||||
_influenceInstance.position = new Vector3(drawn.x, 0, drawn.z);
|
||||
}
|
||||
|
||||
public virtual void OnDestroy()
|
||||
|
|
|
|||
|
|
@ -138,6 +138,6 @@ public class ShipInstance : EntityInstance
|
|||
emissionModule.rateOverTimeMultiplier = thrusterInstance.BaseEmission * thrusterInstance.Thruster.Axis * thrusterInstance.Thruster.Condition;
|
||||
}
|
||||
|
||||
transform.rotation = Ship.Rotation.ToUnity();
|
||||
transform.rotation = DrawAhead.Rotation(Ship, ActionGameManager.Clock.Lead).ToUnity();
|
||||
}
|
||||
}
|
||||
|
|
@ -151,8 +151,9 @@ public class AetherDrive : Behavior, IPowerConsumer
|
|||
ThrustDirection = forward * (_axis.x * force.x / Entity.Mass) + right * (_axis.y * force.y / Entity.Mass);
|
||||
Entity.Velocity += ThrustDirection;
|
||||
|
||||
Entity.Direction = mul(Entity.Direction,
|
||||
CultMath.float2x2.Rotate(force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass));
|
||||
var turn = force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass;
|
||||
Entity.Direction = mul(Entity.Direction, CultMath.float2x2.Rotate(turn));
|
||||
if (dt > 0) Entity.TurnRate += turn / dt;
|
||||
|
||||
if(float.IsNaN(Entity.Velocity.x))
|
||||
ItemManager.Log("FUCK FUCK FUCK FUCK");
|
||||
|
|
|
|||
|
|
@ -125,8 +125,9 @@ public class Thruster : Behavior, IAnalogBehavior, IPowerConsumer
|
|||
{
|
||||
var thrust = Thrust;
|
||||
Entity.Velocity -= Direction.xz * _input * thrust / Entity.Mass * dt;
|
||||
Entity.Direction = mul(Entity.Direction,
|
||||
CultMath.float2x2.Rotate(_input * Torque * thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt));
|
||||
var turnRate = _input * Torque * thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass;
|
||||
Entity.Direction = mul(Entity.Direction, CultMath.float2x2.Rotate(turnRate * dt));
|
||||
Entity.TurnRate += turnRate;
|
||||
AddHeat(_input * Evaluate(_data.Heat) * dt);
|
||||
var vis = _input * Evaluate(_data.Visibility);
|
||||
if (!Entity.VisibilitySources.ContainsKey(this) || vis > Entity.VisibilitySources[this])
|
||||
|
|
|
|||
36
Assets/Scripts/ServerShared/DrawAhead.cs
Normal file
36
Assets/Scripts/ServerShared/DrawAhead.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
// Where a presenter draws the latest sim state when the frame falls Lead sim seconds after the last step. Pure reads
|
||||
// of the simulation for presenters (never called by ServerShared): the sim owns every pose and no presenter stores
|
||||
// one, a previous state or a velocity of its own.
|
||||
public static class DrawAhead
|
||||
{
|
||||
public static float3 Position(Entity entity, float lead) =>
|
||||
entity.Position + float3(entity.Velocity.x, 0, entity.Velocity.y) * lead;
|
||||
|
||||
// The ship's rotation turned about its own up axis by the rate its last step turned, for lead seconds.
|
||||
public static quaternion Rotation(Ship ship, float lead)
|
||||
{
|
||||
var q = ship.Rotation;
|
||||
var u = float3(q.x, q.y, q.z);
|
||||
var up = float3(0, 1, 0);
|
||||
up += 2 * (q.w * cross(u, up) + cross(u, cross(u, up)));
|
||||
var half = -ship.TurnRate * lead * .5f;
|
||||
var s = sin(half);
|
||||
var t = new quaternion(up.x * s, up.y * s, up.z * s, cos(half));
|
||||
// Hamilton product t * q: CultMath carries no quaternion product (a gap in CultMath, not filled here).
|
||||
return normalize(new quaternion(
|
||||
t.w * q.x + t.x * q.w + t.y * q.z - t.z * q.y,
|
||||
t.w * q.y - t.x * q.z + t.y * q.w + t.z * q.x,
|
||||
t.w * q.z + t.x * q.y - t.y * q.x + t.z * q.w,
|
||||
t.w * q.w - t.x * q.x - t.y * q.y - t.z * q.z));
|
||||
}
|
||||
|
||||
// Zone time for a presenter that evaluates a sim function of time (FireControl.RoundAt).
|
||||
public static float Time(Zone zone, float lead) => zone.Time + lead;
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/DrawAhead.cs.meta
Normal file
11
Assets/Scripts/ServerShared/DrawAhead.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 007fe4255cd84c26a010151a399f45c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -27,6 +27,10 @@ public abstract class Entity
|
|||
public float3 Position;
|
||||
public float2 Direction = float2(0,1);
|
||||
public float2 Velocity;
|
||||
// Rad per sim second, signed as the step turns Direction: the sum of this step's rotation actuators' applied
|
||||
// angle over the step. Runtime only, zeroed at the start of Update, so a staged, loaded or wormhole write to
|
||||
// Direction is never a turn. Read only by DrawAhead.
|
||||
public float TurnRate;
|
||||
|
||||
// Runtime only, never saved; Entity.Update is the one writer of both. Acceleration is the planar velocity change
|
||||
// across the base update over its delta (thrust, not the drag or gravity a subclass applies before calling it),
|
||||
|
|
@ -1359,6 +1363,7 @@ public abstract class Entity
|
|||
|
||||
public virtual void Update(float delta)
|
||||
{
|
||||
TurnRate = 0;
|
||||
if (!_active) return;
|
||||
|
||||
var velocityBefore = Velocity;
|
||||
|
|
|
|||
54
Assets/Scripts/ServerShared/SimClock.cs
Normal file
54
Assets/Scripts/ServerShared/SimClock.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
|
||||
// The one relation between simulation time and real time. Real seconds go in; whole fixed steps come out, each
|
||||
// handed to the caller's step action with the same Step. Nothing else in the game converts frame time to sim time,
|
||||
// and nothing integrates a sim quantity on this clock: presenters read Lead to draw ahead of the latest step
|
||||
// (DrawAhead) and decide no sim fact.
|
||||
public sealed class SimClock
|
||||
{
|
||||
// The longest real time one frame may feed. A hitch (a load, a breakpoint) is a slow frame, not a burst of
|
||||
// catch-up steps.
|
||||
public const float MaxFrameSeconds = .1f;
|
||||
|
||||
private readonly double _period;
|
||||
private double _accumulator;
|
||||
|
||||
public SimClock(float step, float stepsPerRealSecond = 60)
|
||||
{
|
||||
if (!(step > 0)) throw new ArgumentOutOfRangeException(nameof(step));
|
||||
if (!(stepsPerRealSecond > 0)) throw new ArgumentOutOfRangeException(nameof(stepsPerRealSecond));
|
||||
Step = step;
|
||||
StepsPerRealSecond = stepsPerRealSecond;
|
||||
_period = 1.0 / stepsPerRealSecond;
|
||||
}
|
||||
|
||||
// Sim seconds per step, fixed for the clock's life.
|
||||
public float Step { get; }
|
||||
|
||||
public float StepsPerRealSecond { get; }
|
||||
|
||||
public long Steps { get; private set; }
|
||||
|
||||
// Sim seconds since the last step, 0 <= Lead < Step.
|
||||
public float Lead => (float) (_accumulator * StepsPerRealSecond * Step);
|
||||
|
||||
// Feeds real seconds (negative counts as none, one frame counts at most MaxFrameSeconds) and runs the whole
|
||||
// steps they pay for. Returns how many ran.
|
||||
public int Advance(float realSeconds, Action<float> step)
|
||||
{
|
||||
_accumulator += Math.Min(Math.Max(realSeconds, 0f), MaxFrameSeconds);
|
||||
var ran = 0;
|
||||
while (_accumulator >= _period)
|
||||
{
|
||||
_accumulator -= _period;
|
||||
step(Step);
|
||||
Steps++;
|
||||
ran++;
|
||||
}
|
||||
return ran;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/SimClock.cs.meta
Normal file
11
Assets/Scripts/ServerShared/SimClock.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7a9e3edac65f4a8e8313bdbfe69ccebd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in a new issue