Passenger/driver sit still in moving cars: seat pose sent relative to the vehicle (backward-compatible), no backward pull; remote cars/traffic extrapolate in physics time and interpolate; UI polish (shadows, borders, gradient buttons, server list hover, fill bars, colored ping)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nico Meister
2026-09-28 20:52:56 +02:00
co-authored by Claude Opus 5.5
parent 33a415139b
commit ce2fbdfc10
11 changed files with 274 additions and 24 deletions
+25 -5
View File
@@ -25,6 +25,8 @@ namespace MSCO
public int OwnerId = NoOwner;
public bool OwnerActive; // Besitzer hält/fährt es gerade aktiv
public bool WasKinematic; // Zustand bevor ein anderer Spieler es übernommen hat
public RigidbodyInterpolation WasInterpolation;
public bool InterpolationSet; // Fahrzeug eines anderen: bei uns interpoliert (siehe FreezeForRemote)
// Gelenke der angebauten Teile, solange ein anderer das Fahrzeug fährt: unzerbrechlich (vorherige Bruchkraft/-moment)
public Dictionary<Joint, Vector2> HeldJoints;
public float NextPartScan;
@@ -42,7 +44,7 @@ namespace MSCO
public Vector3 TargetPos;
public Quaternion TargetRot;
public Vector3 TargetVel; // Geschwindigkeit beim Besitzer: damit wird die Position vorausberechnet
public float TargetTime; // wann TargetPos ankam
public float TargetTime; // wann TargetPos ankam (Physik-Zeit, Time.fixedTime)
public float LastRecvTime;
public float AvatarHoldTime = -100f; // zuletzt von der Hand eines Mitspielers platziert
@@ -209,6 +211,8 @@ namespace MSCO
/// <summary>Fährt der lokale Spieler gerade selbst?</summary>
public bool IsDriving { get; private set; }
/// <summary>Das Fahrzeug, das der lokale Spieler gerade fährt (sonst null).</summary>
public SyncedBody Driven { get; private set; }
/// <summary>Fahrzeuge, die gerade ein anderer Spieler aktiv fährt.</summary>
public List<SyncedBody> RemoteDrivenVehicles()
@@ -315,6 +319,7 @@ namespace MSCO
SyncedBody held = HeldBody();
SyncedBody driven = DrivenBody();
Driven = driven;
IsDriving = driven != null;
bool motionClaimAllowed = Time.time - loadTime > MotionClaimGrace;
@@ -387,7 +392,9 @@ namespace MSCO
if (Time.time - b.AvatarHoldTime < 0.2f) continue; // sitzt gerade in der Hand eines Mitspielers
// Dort, wo das Objekt beim Besitzer JETZT ungefähr ist: letzte Position + Geschwindigkeit * Alter.
// Ohne das hinken schnelle Objekte (fahrende Autos) immer einen Paketabstand hinterher und ruckeln.
float age = Mathf.Min(Time.time - b.TargetTime, MaxExtrapolation);
// Beides in Physik-Zeit: mit der Bildzeit (Time.time beim Empfang) wurde das Alter in den ersten
// Physik-Schritten danach negativ - das Auto sprang bei jedem Paket ein Stück zurück (Ruckeln)
float age = Mathf.Clamp(Time.fixedTime - b.TargetTime, 0f, MaxExtrapolation);
Vector3 predicted = b.TargetPos + b.TargetVel * age;
if ((b.Rb.position - predicted).sqrMagnitude > SnapDistance * SnapDistance)
{
@@ -489,7 +496,15 @@ namespace MSCO
{
b.WasKinematic = b.Rb.isKinematic;
b.Rb.isKinematic = true;
if (b.IsVehicle) HoldParts(b);
if (b.IsVehicle)
{
HoldParts(b);
// Physik läuft mit 50 Hz, das Bild öfter: ohne Interpolation springt das Auto nur in Physik-Schritten
// weiter - wer darin mitfährt, sieht die Welt ruckeln. Nur bei Fahrzeugen (Items setzt die Hand direkt).
b.WasInterpolation = b.Rb.interpolation;
b.InterpolationSet = true;
b.Rb.interpolation = RigidbodyInterpolation.Interpolate;
}
}
void HoldParts(SyncedBody b)
@@ -508,7 +523,12 @@ namespace MSCO
/// <summary>Wieder selbst simulieren: Auto zurück auf seinen Zustand, Gelenke wieder mit ihrer Bruchkraft.</summary>
void RestoreKinematic(SyncedBody b)
{
if (b.Rb != null) b.Rb.isKinematic = b.WasKinematic;
if (b.Rb != null)
{
b.Rb.isKinematic = b.WasKinematic;
if (b.InterpolationSet) b.Rb.interpolation = b.WasInterpolation;
}
b.InterpolationSet = false;
if (b.HeldJoints == null) return;
foreach (KeyValuePair<Joint, Vector2> kv in b.HeldJoints)
{
@@ -580,7 +600,7 @@ namespace MSCO
b.TargetPos = pos;
b.TargetRot = rot;
b.TargetVel = vel.sqrMagnitude < 90f * 90f ? vel : Vector3.zero;
b.TargetTime = Time.time;
b.TargetTime = Time.fixedTime;
}
break;
}