Beta Release
This commit is contained in:
+190
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<int, Light> Lights = new Dictionary<int, Light>();
|
||||
public readonly Dictionary<int, AudioSource> Audio = new Dictionary<int, AudioSource>();
|
||||
}
|
||||
|
||||
class Snapshot
|
||||
{
|
||||
public float ReceivedAt;
|
||||
public readonly Dictionary<int, bool> Lights = new Dictionary<int, bool>();
|
||||
public readonly Dictionary<int, AudioState> Audio = new Dictionary<int, AudioState>();
|
||||
}
|
||||
|
||||
readonly Dictionary<int, Components> components = new Dictionary<int, Components>();
|
||||
readonly Dictionary<int, Snapshot> incoming = new Dictionary<int, Snapshot>();
|
||||
float sendTimer;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
components.Clear();
|
||||
incoming.Clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Senden
|
||||
|
||||
public void Update(ObjectSync objects, Action<MsgType, byte[]> 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<int, Light> 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<int, AudioSource> 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;
|
||||
}
|
||||
|
||||
/// <summary>Jedes Frame nach den Spiel-FSMs (LateUpdate), damit unsere Werte gewinnen.</summary>
|
||||
public void Apply(ObjectSync objects)
|
||||
{
|
||||
foreach (KeyValuePair<int, Snapshot> 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<int, bool> 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<int, AudioState> 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<string, int>();
|
||||
foreach (Light l in b.Rb.GetComponentsInChildren<Light>(true))
|
||||
c.Lights[Key(l.transform, c.Root, seen)] = l;
|
||||
seen.Clear();
|
||||
foreach (AudioSource a in b.Rb.GetComponentsInChildren<AudioSource>(true))
|
||||
if (a.loop) c.Audio[Key(a.transform, c.Root, seen)] = a; // nur Dauergeräusche
|
||||
components[b.Id] = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>Schlüssel aus dem Pfad unterhalb des Fahrzeugs (+ Nummer bei gleichen Pfaden).</summary>
|
||||
static int Key(Transform t, Transform root, Dictionary<string, int> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user