Files
MSCO/WheelSpin.cs

104 lines
4.2 KiB
C#

using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace MSCO
{
/// <summary>
/// Räder an Fahrzeugen, die ein anderer Spieler fährt, drehen sich mit. Bei uns wird so ein Auto nur verschoben
/// (kinematisch), die Rad-Physik des Spiels (CarDynamics "Wheel") dreht die Räder dann nicht. Wir drehen das
/// Rad-Modell passend zur Geschwindigkeit um seine Achse - nach dem Spiel (LateUpdate), ohne dessen Werte zu ändern.
/// </summary>
public class WheelSpin
{
const float CacheRefresh = 5f; // Räder können ab-/angebaut werden
class WheelInfo
{
public Transform Model;
public Transform Hub; // Transform der Wheel-Komponente (Fahrtrichtung)
public float Radius;
public float Angle;
public Quaternion Base; // Drehung ohne unsere Rollbewegung
public Quaternion Written; // zuletzt von uns gesetzt
public bool HasWritten;
}
class Vehicle
{
public float BuiltAt;
public readonly List<WheelInfo> Wheels = new List<WheelInfo>();
}
readonly Dictionary<int, Vehicle> vehicles = new Dictionary<int, Vehicle>();
static System.Type wheelType;
static FieldInfo modelField, radiusField;
static bool lookedUp;
public void Reset() { vehicles.Clear(); }
public void Apply(ObjectSync objects, float dt)
{
if (!Lookup()) return;
List<SyncedBody> driven = objects.RemoteDrivenVehicles();
if (driven.Count == 0) { if (vehicles.Count > 0) vehicles.Clear(); return; }
foreach (SyncedBody b in driven)
{
Vehicle v;
if (!vehicles.TryGetValue(b.Id, out v) || Time.time - v.BuiltAt > CacheRefresh)
{
v = Build(b.Rb.transform);
vehicles[b.Id] = v;
}
Vector3 vel = b.TargetVel;
foreach (WheelInfo w in v.Wheels)
{
if (w.Model == null || w.Hub == null) continue;
float speed = Vector3.Dot(vel, w.Hub.forward);
w.Angle = Mathf.Repeat(w.Angle + speed / w.Radius * Mathf.Rad2Deg * dt, 360f);
if (float.IsNaN(w.Angle) || float.IsInfinity(w.Angle)) w.Angle = 0f;
// Hat das Spiel das Modell in diesem Frame selbst gedreht (z.B. Lenkung), ist das die neue Basis
Quaternion current = w.Model.localRotation;
if (!w.HasWritten || current != w.Written) w.Base = current;
w.Written = w.Base * Quaternion.Euler(w.Angle, 0f, 0f);
w.Model.localRotation = w.Written;
w.HasWritten = true;
}
}
}
static Vehicle Build(Transform root)
{
var v = new Vehicle { BuiltAt = Time.time };
foreach (Component c in root.GetComponentsInChildren(wheelType, true))
{
var model = modelField.GetValue(c) as GameObject;
float radius = radiusField != null ? (float)radiusField.GetValue(c) : 0.3f;
if (model == null || radius < 0.05f || radius > 2f) continue;
v.Wheels.Add(new WheelInfo { Model = model.transform, Hub = c.transform, Radius = radius });
}
return v;
}
static bool Lookup()
{
if (lookedUp) return wheelType != null;
lookedUp = true;
foreach (Assembly a in System.AppDomain.CurrentDomain.GetAssemblies())
{
if (a.GetName().Name != "Assembly-CSharp") continue;
wheelType = a.GetType("Wheel");
break;
}
if (wheelType == null) { MscoLog.Warning("[MSCO] Wheel type not found - remote wheels will not spin"); return false; }
modelField = wheelType.GetField("model", BindingFlags.Public | BindingFlags.Instance);
radiusField = wheelType.GetField("radius", BindingFlags.Public | BindingFlags.Instance);
if (modelField == null) { wheelType = null; return false; }
return true;
}
}
}