using System.Collections.Generic; using UnityEngine; namespace MSCO { /// /// 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. /// public class Passenger { const float EnterRange = 3f; // per Taste: so nah muss man an der Karosserie stehen const float AutoEnterRange = 1.0f; // automatisch: Kopf so nah am Beifahrersitz ... const float AutoEnterSide = 0.6f; // ... und seitlich höchstens so weit außerhalb des Sitzes - an der // geschlossenen Tür steht man weiter draußen, nur in der offenen Tür so nah const float AutoEnterDwell = 0.35f; // ... so lange (nicht beim bloßen Vorbeilaufen) const float AutoRearmRange = 2f; // nach dem Aussteigen erst wieder, wenn man so weit weg war const float AutoCheckInterval = 0.15f; const float CenterSeatThreshold = 0.2f; const float BehindCenterSeat = 0.7f; const float ExitSideOffset = 1.4f; // beim Aussteigen so weit neben den Sitz // Bewegungs-Skripte des Spielers (Unity Standard Assets), während der Fahrt aus static readonly string[] MovementScripts = { "CharacterMotor", "FPSInputController" }; SyncedBody vehicle; Vector3 seatHeadLocal; float lastYaw; readonly List disabled = new List(); CharacterController controller; // Feste Collider des Spielers, die während der Fahrt aus sind (sonst stecken sie in Sitzen und Teilen des Autos) readonly List disabledColliders = new List(); readonly List frozenBodies = new List(); // Fahrerkopf je Fahrzeug (lokal), aus DriverHeadPivot - null = nicht gefunden readonly Dictionary driverHeads = new Dictionary(); float nextAutoCheck, dwellStart = -1f; SyncedBody dwellVehicle; bool autoArmed = true; Vector3 lastExitSeat; public bool IsRiding { get { return vehicle != null; } } /// Fahrzeug, in dem wir mitfahren (sonst null). public SyncedBody Vehicle { get { return vehicle; } } /// Kopf auf dem Sitz, relativ zum Fahrzeug (genau, nicht aus der Weltposition des letzten Frames). public Vector3 SeatHeadLocal { get { return seatHeadLocal; } } /// Taste gedrückt: einsteigen oder aussteigen. Gibt einen Statustext zurück. public string Toggle(Transform player, Camera cam, ObjectSync objects, IDictionary players) { if (IsRiding) { Exit(player, cam); return "Got out"; } if (cam == null || objects.IsDriving) return null; SyncedBody best = null; Vector3 bestSeat = Vector3.zero; float bestDist = EnterRange * EnterRange, nearest = float.MaxValue; foreach (SyncedBody b in objects.AllVehicles()) { if (b.Rb == null || (b.Rb.position - player.position).sqrMagnitude > 30f * 30f) continue; float d = DistanceToVehicleSq(b, player.position); Vector3? seat = SeatFor(b, objects, players); if (seat == null) continue; nearest = Mathf.Min(nearest, d); if (d < bestDist) { bestDist = d; best = b; bestSeat = seat.Value; } } if (best == null) { if (nearest == float.MaxValue) return "Passenger: no vehicle nearby"; return "Passenger: get closer to the car (" + Mathf.Sqrt(nearest).ToString("0") + " m away, max " + EnterRange.ToString("0") + " m)"; } Enter(best, bestSeat, player); return "Riding as passenger (press again or Space to get out)"; } /// /// 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. /// public string AutoUpdate(Transform player, Camera cam, ObjectSync objects, IDictionary players) { if (IsRiding || cam == null || player == null || objects.IsDriving) { dwellStart = -1f; return null; } if (Time.time < nextAutoCheck) return null; nextAutoCheck = Time.time + AutoCheckInterval; Vector3 head = cam.transform.position; // Nach dem Aussteigen erst einmal weggehen, sonst säße man sofort wieder drin if (!autoArmed) { if ((head - lastExitSeat).sqrMagnitude > AutoRearmRange * AutoRearmRange) 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; found = b; foundSeat = seat.Value; break; } if (found == null || found != dwellVehicle) { dwellVehicle = found; dwellStart = found != null ? Time.time : -1f; return null; } 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)"; } void Enter(SyncedBody b, Vector3 seat, Transform player) { seatHeadLocal = seat; vehicle = b; lastYaw = b.Rb.transform.eulerAngles.y; SetMovementEnabled(player, false); } /// Beifahrersitz (Kopfposition, lokal im Fahrzeug) - aus dem Fahrersitz des Spiels oder dem Fahrer. Vector3? SeatFor(SyncedBody b, ObjectSync objects, IDictionary players) { Vector3? driverHead; if (!driverHeads.TryGetValue(b.Rb, out driverHead)) { driverHead = null; foreach (Transform t in b.Rb.GetComponentsInChildren(true)) { if (t.name != "DriverHeadPivot") continue; driverHead = b.Rb.transform.InverseTransformPoint(t.position); break; } driverHeads[b.Rb] = driverHead; } // Ohne DriverHeadPivot: nur, wenn gerade jemand fährt (dessen Kopf ist die Fahrerposition) if (driverHead == null && objects.IsRemoteOwnedBody(b) && b.OwnerActive) { PlayerInfo owner; if (players.TryGetValue(b.OwnerId, out owner) && owner.Avatar != null) driverHead = b.Rb.transform.InverseTransformPoint(owner.Avatar.HeadPosition); } if (driverHead == null) return null; Vector3 d = driverHead.Value; return Mathf.Abs(d.x) < CenterSeatThreshold ? d - new Vector3(0f, 0f, BehindCenterSeat) // Sitz in der Mitte: dahinter : new Vector3(-d.x, d.y, d.z); // gegenüber vom Fahrer } /// /// Abstand zur nächsten Stelle der Karosserie (Collider), nicht zum Schwerpunkt - den legt MSC teils weit weg, /// dann hieß es "26 m entfernt", obwohl man direkt an der Tür stand. /// static float DistanceToVehicleSq(SyncedBody b, Vector3 point) { float best = (b.Rb.position - point).sqrMagnitude; foreach (Collider c in b.Rb.GetComponentsInChildren()) { if (c == null || !c.enabled || c.isTrigger) continue; float d = (c.bounds.ClosestPoint(point) - point).sqrMagnitude; if (d < best) best = d; } return best; } /// Nach der Physik (LateUpdate): Spieler auf den Sitz setzen und mitdrehen. public void LateUpdate(Transform player, Camera cam, ObjectSync objects) { if (!IsRiding) return; if (vehicle.Rb == null || cam == null) { Exit(player, cam); return; } // Selbst ans Steuer gesetzt (Fahrersitz des Spiels): Beifahrer-Modus ohne Umsetzen beenden if (objects.IsDriving) { Reset(); return; } Transform vt = vehicle.Rb.transform; float yaw = vt.eulerAngles.y; player.Rotate(0f, Mathf.DeltaAngle(lastYaw, yaw), 0f, Space.World); lastYaw = yaw; Vector3 cameraOffset = cam.transform.position - player.position; player.position = vt.TransformPoint(seatHeadLocal) - cameraOffset; } public void Exit(Transform player, Camera cam) { if (!IsRiding) return; if (player != null && vehicle.Rb != null) { lastExitSeat = vehicle.Rb.transform.TransformPoint(seatHeadLocal); autoArmed = false; // Auf der Seite des Sitzes neben das Fahrzeug stellen float side = seatHeadLocal.x >= 0f ? 1f : -1f; Vector3 outside = vehicle.Rb.transform.TransformPoint(seatHeadLocal + new Vector3(side * ExitSideOffset, 0f, 0f)); Vector3 cameraOffset = cam != null ? cam.transform.position - player.position : Vector3.up * 1.6f; player.position = outside - cameraOffset + Vector3.up * 0.1f; } vehicle = null; SetMovementEnabled(player, true); } /// Szenenwechsel/Verbindungsende: ohne Teleport alles zurücksetzen. public void Reset() { vehicle = null; dwellVehicle = null; dwellStart = -1f; driverHeads.Clear(); SetMovementEnabled(null, true); } void SetMovementEnabled(Transform player, bool enabled) { if (!enabled) { disabled.Clear(); foreach (MonoBehaviour mb in player.GetComponents()) { if (mb == null || !mb.enabled) continue; if (System.Array.IndexOf(MovementScripts, mb.GetType().Name) < 0) continue; mb.enabled = false; disabled.Add(mb); } controller = player.GetComponent(); if (controller != null && controller.enabled) controller.enabled = false; else controller = null; // Der Spieler sitzt mitten im Auto: seine festen Collider würden Sitze und Teile wegdrücken // (Teile reißen ab, Sitze fliegen, bei Tempo stürzt die Physik ab). Wie beim echten Einsteigen: aus. disabledColliders.Clear(); foreach (Collider c in player.GetComponentsInChildren()) { if (c == null || !c.enabled || c.isTrigger || c is CharacterController) continue; c.enabled = false; disabledColliders.Add(c); } frozenBodies.Clear(); foreach (Rigidbody rb in player.GetComponentsInChildren()) { if (rb == null || rb.isKinematic) continue; rb.isKinematic = true; frozenBodies.Add(rb); } } else { foreach (Behaviour b in disabled) if (b != null) b.enabled = true; disabled.Clear(); if (controller != null) controller.enabled = true; controller = null; foreach (Collider c in disabledColliders) if (c != null) c.enabled = true; disabledColliders.Clear(); foreach (Rigidbody rb in frozenBodies) if (rb != null) rb.isKinematic = false; frozenBodies.Clear(); } } } }