using System.Collections.Generic; using UnityEngine; namespace MSCO { /// /// Beifahrer: Neben einem Fahrzeug, das ein anderer Spieler fährt, per Taste einsteigen. /// Der Sitz ist die Fahrerposition, 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; // so nah muss man an der Karosserie stehen 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(); public bool IsRiding { get { return vehicle != null; } } /// 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) return null; SyncedBody best = null; RemotePlayer driver = null; float bestDist = EnterRange * EnterRange; List driven = objects.RemoteDrivenVehicles(); float nearest = float.MaxValue; foreach (SyncedBody b in driven) { float d = DistanceToVehicleSq(b, player.position); nearest = Mathf.Min(nearest, d); PlayerInfo owner; if (!players.TryGetValue(b.OwnerId, out owner) || owner.Avatar == null) continue; if (d < bestDist) { bestDist = d; best = b; driver = owner.Avatar; } } if (best == null) { // Genau sagen, woran es liegt if (driven.Count == 0) return "Passenger: nobody is driving a vehicle right now (the driver must sit in the driver's seat)"; return "Passenger: get closer to the car (" + Mathf.Sqrt(nearest).ToString("0") + " m away, max " + EnterRange.ToString("0") + " m)"; } Transform vt = best.Rb.transform; Vector3 driverHead = vt.InverseTransformPoint(driver.HeadPosition); seatHeadLocal = Mathf.Abs(driverHead.x) < CenterSeatThreshold ? driverHead - new Vector3(0f, 0f, BehindCenterSeat) // Sitz in der Mitte: dahinter : new Vector3(-driverHead.x, driverHead.y, driverHead.z); // gegenüber vom Fahrer vehicle = best; lastYaw = vt.eulerAngles.y; SetMovementEnabled(player, false); return "Riding as passenger (press again to get out)"; } /// /// 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; // Fahrer ausgestiegen oder Fahrzeug weg: automatisch aussteigen if (vehicle.Rb == null || cam == null || !objects.IsRemoteOwnedBody(vehicle)) { Exit(player, cam); 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) { // 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; 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(); } } } }