using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; namespace MSCO { /// /// Lichter und Dauergeräusche (Motor, Blinker, Hupe, Radio ...) von Fahrzeugen. /// Wer ein Fahrzeug besitzt (fährt/schiebt), schickt regelmäßig deren Zustand; /// bei allen anderen wird dieser Zustand jedes Frame übernommen. /// public class VehicleFx { const float SendInterval = 0.2f; const float CacheRefresh = 5f; // Teile können an-/abgebaut werden const float ApplyTimeout = 2f; // ohne neue Daten nichts mehr erzwingen struct AudioState { public bool Playing; public float Pitch; public float Volume; } class Components { public float BuiltAt; public Transform Root; public readonly Dictionary Lights = new Dictionary(); public readonly Dictionary Audio = new Dictionary(); } class Snapshot { public float ReceivedAt; public readonly Dictionary Lights = new Dictionary(); public readonly Dictionary Audio = new Dictionary(); } readonly Dictionary components = new Dictionary(); readonly Dictionary incoming = new Dictionary(); float sendTimer; public void Reset() { components.Clear(); incoming.Clear(); } // ---------------------------------------------------------------- Senden public void Update(ObjectSync objects, Action send, float dt) { sendTimer += dt; if (sendTimer < SendInterval) return; sendTimer = 0f; foreach (SyncedBody b in objects.LocalVehicles()) { Components c = GetComponents(b); if (c == null) continue; int id = b.Id; send(MsgType.VehicleFx, NetSession.Build(w => { w.Write(id); w.Write(c.Lights.Count); foreach (KeyValuePair l in c.Lights) { w.Write(l.Key); w.Write(l.Value != null && l.Value.enabled && l.Value.gameObject.activeInHierarchy); } int audioCount = 0; foreach (AudioSource a in c.Audio.Values) if (a != null) audioCount++; w.Write(audioCount); foreach (KeyValuePair a in c.Audio) { if (a.Value == null) continue; w.Write(a.Key); w.Write(a.Value.isPlaying); w.Write(a.Value.pitch); w.Write(a.Value.volume); } })); } } // ---------------------------------------------------------------- Empfangen public void Handle(BinaryReader r) { int id = r.ReadInt32(); var s = new Snapshot { ReceivedAt = Time.time }; int lights = r.ReadInt32(); for (int i = 0; i < lights; i++) s.Lights[r.ReadInt32()] = r.ReadBoolean(); int audio = r.ReadInt32(); for (int i = 0; i < audio; i++) { int key = r.ReadInt32(); s.Audio[key] = new AudioState { Playing = r.ReadBoolean(), Pitch = r.ReadSingle(), Volume = r.ReadSingle() }; } incoming[id] = s; } /// Jedes Frame nach den Spiel-FSMs (LateUpdate), damit unsere Werte gewinnen. public void Apply(ObjectSync objects) { foreach (KeyValuePair kv in incoming) { if (Time.time - kv.Value.ReceivedAt > ApplyTimeout) continue; SyncedBody b = objects.Get(kv.Key); if (b == null || !objects.IsRemoteOwnedBody(b)) continue; Components c = GetComponents(b); if (c == null) continue; foreach (KeyValuePair l in kv.Value.Lights) { Light light; if (c.Lights.TryGetValue(l.Key, out light) && light != null) SetLight(light, l.Value, c.Root); } foreach (KeyValuePair a in kv.Value.Audio) { AudioSource src; if (c.Audio.TryGetValue(a.Key, out src) && src != null) SetAudio(src, a.Value); } } } static void SetLight(Light light, bool on, Transform root) { if (!on) { if (light.enabled) light.enabled = false; return; } if (!light.enabled) light.enabled = true; // Lichter werden im Spiel oft über ein übergeordnetes Objekt ein-/ausgeschaltet for (Transform t = light.transform; t != null && t != root; t = t.parent) if (!t.gameObject.activeSelf) t.gameObject.SetActive(true); } static void SetAudio(AudioSource src, AudioState state) { if (!src.gameObject.activeInHierarchy) return; if (state.Playing && !src.isPlaying) src.Play(); else if (!state.Playing && src.isPlaying) src.Stop(); src.pitch = state.Pitch; src.volume = state.Volume; } // ---------------------------------------------------------------- Komponenten Components GetComponents(SyncedBody b) { if (b.Rb == null) return null; Components c; if (components.TryGetValue(b.Id, out c) && Time.time - c.BuiltAt < CacheRefresh) return c; c = new Components { BuiltAt = Time.time, Root = b.Rb.transform }; var seen = new Dictionary(); foreach (Light l in b.Rb.GetComponentsInChildren(true)) c.Lights[Key(l.transform, c.Root, seen)] = l; seen.Clear(); foreach (AudioSource a in b.Rb.GetComponentsInChildren(true)) if (a.loop) c.Audio[Key(a.transform, c.Root, seen)] = a; // nur Dauergeräusche components[b.Id] = c; return c; } /// Schlüssel aus dem Pfad unterhalb des Fahrzeugs (+ Nummer bei gleichen Pfaden). static int Key(Transform t, Transform root, Dictionary seen) { var sb = new StringBuilder(); for (; t != null && t != root; t = t.parent) sb.Insert(0, "/" + t.name); string path = sb.ToString(); int n; seen.TryGetValue(path, out n); seen[path] = n + 1; path += "#" + n; unchecked { uint h = 2166136261; foreach (byte c in Encoding.UTF8.GetBytes(path)) { h ^= c; h *= 16777619; } return (int)h; } } } }