Riding along: hold the current position inside the vehicle

Instead of snapping to a computed passenger seat, a player whose head is
inside a vehicle's (shrunk) outline is held exactly there and moves with it;
works for cabins, open doors and beds, with or without a driver. Getting out
puts the player beside the vehicle on the nearer side. P next to the car
still uses the passenger seat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 17:40:13 +02:00
co-authored by Claude Opus 5.5
parent 5f8062c649
commit dd31587d61
2 changed files with 81 additions and 59 deletions
+6 -26
View File
@@ -17,32 +17,12 @@
- Konsole (F7): `model classic` zeigt wieder die MSCO-Figur, `model game` die MSC-Menschen; `rig` zeigt, wer welches
Modell hat. Findet MSCO den Körper nicht, bleibt automatisch die MSCO-Figur
**Beifahrer**
- **Automatisch einsteigen**: in die offene Beifahrertür an den Sitz treten - man setzt sich von selbst hin, keine Taste
nötig. Aussteigen mit Leertaste oder der Beifahrer-Taste (P)
- Geht jetzt auch, **wenn noch niemand fährt** (Sitzposition kommt aus dem Fahrersitz des Spiels); fährt danach jemand
los, fährt man mit. Setzt man sich selbst ans Steuer, endet der Beifahrer-Modus
**Server mit neuem Spiel**
- Im Server-Panel (Welt & Backups) gibt es „Neues Spiel starten“: statt eines hochgeladenen Spielstands startet der
erste Spieler ein frisches My-Summer-Car-Spiel, der Server übernimmt danach dessen Spielstand (braucht Server 2.9.3)
**Launcher einklappen**
- Knopf **–** oben rechts im Launcher: er klappt zu einer kleinen MSCO-Leiste oben zusammen, das Startmenü des Spiels
(Optionen, Farbe, Musik ...) ist wieder sichtbar und bedienbar, die Menü-Musik läuft wieder. Klick auf die Leiste
öffnet den Launcher. Bei Meldungen (getrennt, neu verbinden, Beitreten bestätigen) klappt er von selbst auf
**Satsuma vollständiger synchron**
- Zustand aller Satsuma-Teile wird jetzt übertragen - auch der Teile, die das Spiel außerhalb des Autos verwaltet
(CARPARTS und die Teile-Datenbank): Verschleiß (Kolben, Kupplung, Getriebe, Lichtmaschine ...), beschädigte Teile
(z.B. Kolben/Zylinderkopf nach Überhitzen), Reifen-Verschleiß, Öl im Ölwannen-Teil (Menge, Verschmutzung), Batterie-
Ladung, Vergaser-/Zündeinstellungen, Fahrwerk, Kilometerstand. Maßgeblich ist, wer den Satsuma gerade fährt
- **Lack**: Lackfarben werden übertragen und sofort auf das Teil gelegt
- Teile, die erst während des Spiels angebaut werden (Radio, Sitze, Armaturen ...), werden jetzt auch erfasst - vorher
wurden sie einmal als „nicht am Auto“ gemerkt und nie wieder angesehen
- Mehr Bedienelemente: Handbremse, Sonnenblenden, Sitzlehne, Gurt, CD-Spieler, Innenlicht
- **Später Beitretende** bekommen Schalter (Licht an, Motor läuft ...) und Jobs/Laden vom Server (ab Server 2.9.2);
im P2P gleicht der Host die Schalter alle 15 s ab
**Mitfahren**
- **Wer im Fahrzeug ist, fährt mit - genau an seiner Stelle**: Kopf in der Kabine, in der offenen Tür oder auf der
Ladefläche -> die Position im Fahrzeug wird festgehalten, kein Umsetzen auf einen berechneten Sitz. Geht auch, wenn
noch niemand fährt. Aussteigen mit Leertaste oder der Beifahrer-Taste (P) - man steht dann auf der näheren Seite
neben dem Fahrzeug. P neben dem Auto setzt einen wie bisher auf den Beifahrersitz
- Beim Ein-/Aussteigen am Fahrersitz und kurz nach dem Fahren wird man nicht festgehalten
**Satsuma zerfällt nicht mehr beim gemeinsamen Fahren**
- Hing das Spiel des Fahrers kurz (z.B. beim Speichern für den Server), gab der Mitfahrer das Auto nach 3 s frei: es
+75 -33
View File
@@ -4,11 +4,10 @@ using UnityEngine;
namespace MSCO
{
/// <summary>
/// Beifahrer: Wer in die offene Beifahrertür an den Sitz tritt, setzt sich automatisch hin - egal ob schon jemand
/// fährt oder das Auto noch steht (die Taste geht weiterhin, auch zum Aussteigen; Leertaste steigt ebenfalls aus).
/// Der Sitz ist die Fahrerposition des Spiels (DriverHeadPivot), auf die andere Seite gespiegelt (bei Fahrzeugen mit
/// Sitz in der Mitte, z.B. Traktor oder Moped: dahinter). Während der Fahrt wird der Spieler jedes Frame auf den Sitz
/// gesetzt und dreht mit dem Fahrzeug mit; umschauen geht weiter.
/// Mitfahren: Wer mit dem Kopf im Fahrzeug ist (Kabine, offene Tür, Ladefläche ...), wird genau dort festgehalten und
/// fährt mit - egal ob schon jemand fährt oder das Auto noch steht. Aussteigen mit Leertaste oder der Beifahrer-Taste.
/// Steht man per Taste neben dem Auto, kommt man auf den Beifahrersitz (Fahrerposition des Spiels, gespiegelt).
/// Während der Fahrt wird der Spieler jedes Frame an seine Stelle im Fahrzeug gesetzt und dreht mit; umschauen geht.
/// </summary>
public class Passenger
{
@@ -58,6 +57,12 @@ namespace MSCO
return "Got out";
}
if (cam == null || objects.IsDriving) return null;
SyncedBody inside = VehicleAround(objects, cam.transform.position);
if (inside != null)
{
Enter(inside, inside.Rb.transform.InverseTransformPoint(cam.transform.position), player);
return "Riding along (press again or Space to get out)";
}
SyncedBody best = null;
Vector3 bestSeat = Vector3.zero;
@@ -81,16 +86,16 @@ namespace MSCO
}
/// <summary>
/// Jedes Frame (zu Fuß): steht man mit dem Kopf am Beifahrersitz (in der offenen Tür), automatisch hinsetzen.
/// Gibt einen Statustext zurück, wenn man eingestiegen ist.
/// Jedes Frame (zu Fuß): ist man mit dem Kopf IM Fahrzeug (Kabine, Ladefläche ...), wird die aktuelle Position im
/// Fahrzeug festgehalten - man fährt genau dort mit, wo man ist (kein Umsetzen auf einen berechneten Sitz).
/// Gibt einen Statustext zurück, wenn man "eingestiegen" ist.
/// </summary>
public string AutoUpdate(Transform player, Camera cam, ObjectSync objects, IDictionary<int, PlayerInfo> players)
{
if (objects.IsDriving) lastDriving = Time.time;
if (IsRiding || cam == null || player == null || objects.IsDriving) { dwellStart = -1f; return null; }
// Steigt man auf der Fahrerseite ein, setzt das Spiel einen über die Sitze auf den Fahrersitz - dabei kommt der
// Kopf kurz am Beifahrersitz vorbei. Solange das Spiel die Steuerung hat (CharacterController aus, Spieler an
// ein Fahrzeug gehängt) oder kurz nach dem Fahren: nicht automatisch einsteigen.
// Beim Ein-/Aussteigen am Fahrersitz übernimmt das Spiel die Steuerung (CharacterController aus, Spieler an ein
// Fahrzeug gehängt) - dann und kurz nach dem Fahren nicht festhalten
if (Time.time - lastDriving < AfterDrivingPause) { dwellStart = -1f; return null; }
CharacterController cc = player.GetComponent<CharacterController>();
if ((cc != null && !cc.enabled) || player.parent != null) { lastDriving = Time.time; dwellStart = -1f; return null; }
@@ -98,31 +103,14 @@ namespace MSCO
nextAutoCheck = Time.time + AutoCheckInterval;
Vector3 head = cam.transform.position;
// Nach dem Aussteigen erst einmal weggehen, sonst säße man sofort wieder drin
// Nach dem Aussteigen erst einmal aus dem Fahrzeug heraus, sonst säße man sofort wieder drin
if (!autoArmed)
{
if ((head - lastExitSeat).sqrMagnitude > AutoRearmRange * AutoRearmRange) autoArmed = true;
if (VehicleAround(objects, head) == null) autoArmed = true;
else return null;
}
SyncedBody found = null;
Vector3 foundSeat = Vector3.zero;
foreach (SyncedBody b in objects.AllVehicles())
{
if (b.Rb == null || (b.Rb.position - head).sqrMagnitude > 8f * 8f) continue;
Vector3? seat = SeatFor(b, objects, players);
if (seat == null) continue;
if ((b.Rb.transform.TransformPoint(seat.Value) - head).sqrMagnitude > AutoEnterRange * AutoEnterRange) continue;
Vector3 local = b.Rb.transform.InverseTransformPoint(head);
if (Mathf.Abs(local.x) > Mathf.Abs(seat.Value.x) + AutoEnterSide) continue;
// Nur von der Beifahrerseite her: Kopf auf der Seite des Beifahrersitzes und nicht weiter innen als der Sitz
// (wer auf der Fahrerseite einsteigt, ist auf der anderen Seite bzw. in der Mitte)
if (Mathf.Abs(seat.Value.x) >= CenterSeatThreshold
&& (Mathf.Sign(local.x) != Mathf.Sign(seat.Value.x) || Mathf.Abs(local.x) < Mathf.Abs(seat.Value.x) - 0.1f)) continue;
found = b;
foundSeat = seat.Value;
break;
}
SyncedBody found = VehicleAround(objects, head);
if (found == null || found != dwellVehicle)
{
dwellVehicle = found;
@@ -132,8 +120,57 @@ namespace MSCO
if (Time.time - dwellStart < AutoEnterDwell) return null;
dwellStart = -1f;
dwellVehicle = null;
Enter(found, foundSeat, player);
return "Riding as passenger (Space or the passenger key to get out)";
Enter(found, found.Rb.transform.InverseTransformPoint(head), player); // genau hier festhalten
return "Riding along (Space or the passenger key to get out)";
}
/// <summary>Fahrzeug, in dessen Umriss der Kopf gerade ist (null = in keinem).</summary>
SyncedBody VehicleAround(ObjectSync objects, Vector3 head)
{
foreach (SyncedBody b in objects.AllVehicles())
{
if (b.Rb == null || (b.Rb.position - head).sqrMagnitude > 10f * 10f) continue;
Bounds inner = InnerBounds(b);
if (inner.Contains(b.Rb.transform.InverseTransformPoint(head))) return b;
}
return null;
}
readonly Dictionary<Rigidbody, Bounds> innerBounds = new Dictionary<Rigidbody, Bounds>();
/// <summary>
/// Innenraum des Fahrzeugs (lokal): Umriss aller festen Collider, seitlich und vorne/hinten etwas kleiner - neben
/// der geschlossenen Tür steht man noch draußen, erst in der offenen Tür bzw. drin ist man "im Auto".
/// </summary>
Bounds InnerBounds(SyncedBody b)
{
Bounds result;
if (innerBounds.TryGetValue(b.Rb, out result)) return result;
Transform t = b.Rb.transform;
bool any = false;
foreach (Collider c in b.Rb.GetComponentsInChildren<Collider>())
{
if (c == null || c.isTrigger || !c.enabled) continue;
Bounds wb = c.bounds;
for (int i = 0; i < 8; i++)
{
Vector3 corner = wb.center + Vector3.Scale(wb.extents, new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1));
Vector3 lp = t.InverseTransformPoint(corner);
if (!any) { result = new Bounds(lp, Vector3.zero); any = true; }
else result.Encapsulate(lp);
}
}
if (!any) result = new Bounds(Vector3.zero, Vector3.zero);
else
{
Vector3 size = result.size;
// Weltachsen-Umrisse schräger Teile sind zu groß: seitlich 0.25 m, vorne/hinten 0.3 m kleiner,
// oben 0.6 m Luft (Kopf über dem Dach der Ladefläche / im offenen Fahrzeug)
result.size = new Vector3(Mathf.Max(0.1f, size.x - 0.5f), size.y + 0.6f, Mathf.Max(0.1f, size.z - 0.6f));
result.center += new Vector3(0f, 0.3f, 0f);
}
innerBounds[b.Rb] = result;
return result;
}
void Enter(SyncedBody b, Vector3 seat, Transform player)
@@ -222,8 +259,12 @@ namespace MSCO
lastExitSeat = vehicle.Rb.transform.TransformPoint(seatHeadLocal);
autoArmed = false;
// Auf der Seite des Sitzes neben das Fahrzeug stellen
// Zur näheren Seite neben das Fahrzeug (außerhalb seines Umrisses), auf Kopfhöhe von vorher
float side = seatHeadLocal.x >= 0f ? 1f : -1f;
Vector3 outside = vehicle.Rb.transform.TransformPoint(seatHeadLocal + new Vector3(side * ExitSideOffset, 0f, 0f));
Bounds inner = InnerBounds(vehicle);
float x = inner.size.sqrMagnitude > 0f ? inner.center.x + side * (inner.extents.x + 0.85f) : seatHeadLocal.x + side * ExitSideOffset;
var local = new Vector3(x, seatHeadLocal.y, seatHeadLocal.z);
Vector3 outside = vehicle.Rb.transform.TransformPoint(local);
Vector3 cameraOffset = cam != null ? cam.transform.position - player.position : Vector3.up * 1.6f;
player.position = outside - cameraOffset + Vector3.up * 0.1f;
}
@@ -238,6 +279,7 @@ namespace MSCO
dwellVehicle = null;
dwellStart = -1f;
driverHeads.Clear();
innerBounds.Clear();
SetMovementEnabled(null, true);
}