From 7d341126e8ee295afd0c583e292290e747cdcdb2 Mon Sep 17 00:00:00 2001 From: MrMaho Date: Thu, 1 Oct 2026 20:26:43 +0200 Subject: [PATCH] Crisp launcher (sizes and fonts scaled in screen pixels instead of a GUI matrix), fps command and display, fewer hitches (staggered scans, shared rigidbody scan, crash-damage mesh reads only after impacts, rarer vehicle fx lookups, cached display prefs) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 + Commands.cs | 7 + ControlSync.cs | 3 +- DamageSync.cs | 42 ++++- HingeSync.cs | 3 +- LobbyUI.cs | 425 +++++++++++++++++++++--------------------- MultiplayerManager.cs | 87 ++++++++- ObjectSync.cs | 4 +- SceneCache.cs | 21 +++ TrafficSync.cs | 5 +- VehicleFx.cs | 2 +- VehicleStateSync.cs | 3 +- WorldVarSync.cs | 3 +- 13 files changed, 381 insertions(+), 231 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86992a1..85de3c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,13 @@ Server und Mod müssen beide 2.8 sein (Server: `git pull && docker compose up -d --build`). +**Scharfe Oberfläche, FPS, Leistung** +- Launcher wird in voller Auflösung gezeichnet (Schrift und Kanten scharf statt hochgezogen/verpixelt) +- `fps` in der F7-Konsole: FPS-Anzeige oben rechts (grün/gelb/rot), Konsole zeigt auch das längste Bild (Ruckler) +- Weniger Ruckler: Sync-Teile suchen nach dem Laden nicht mehr alle im selben Frame, gemeinsame Suche nach Rigidbodys, + Unfallschäden werden nur noch nach einem Aufprall geprüft (vorher jede Sekunde große Mesh-Kopien), Fahrzeug-Effekte + schlagen ihre Bauteile seltener nach, Ping-/FPS-Anzeige ohne Registry-Zugriff pro Bild + **Launcher im Vollbild** - Der MSCO-Launcher füllt das ganze Hauptmenü und **skaliert automatisch** mit der Auflösung; Listen wachsen mit dem Platz (keine abgeschnittenen Knöpfe mehr). Das Spielmenü dahinter (Logo, Farbauswahl, MP3 …) ist komplett verdeckt diff --git a/Commands.cs b/Commands.cs index 942ab69..74cd3bc 100644 --- a/Commands.cs +++ b/Commands.cs @@ -18,6 +18,7 @@ namespace MSCO "host [port] / join [port] - direct LAN game (no Steam)\n" + "save - save the game (host / singleplayer)\n" + "ping [on|off] - show your ping to the server (toggle)\n" + + "fps [on|off] - show the frames per second (toggle)\n" + "/admin - admin menu (kick / ban / teleport / cheats)\n" + "vehicles | rig - debug info about vehicles / player models\n" + "debug - toggle sync debug output\n" + @@ -112,6 +113,12 @@ namespace MSCO else mp.ShowPing = !mp.ShowPing; print("Ping: " + mp.PingText + " (display " + (mp.ShowPing ? "on" : "off") + ")"); break; + case "fps": + // "fps" schaltet die Anzeige um, "fps on/off" setzt sie + if (args.Length > 1) mp.ShowFps = args[1].ToLower() == "on" || args[1] == "1"; + else mp.ShowFps = !mp.ShowFps; + print("FPS: " + mp.FpsText + " (display " + (mp.ShowFps ? "on" : "off") + ")"); + break; case "admin": case "/admin": mp.OpenAdminMenu(); diff --git a/ControlSync.cs b/ControlSync.cs index 97334df..06a868c 100644 --- a/ControlSync.cs +++ b/ControlSync.cs @@ -60,11 +60,12 @@ namespace MSCO { controls.Clear(); scanned.Clear(); - nextScan = 0f; + nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(ObjectSync objects, int localId, Vector3 playerPos, Action send) { + if (nextScan < 0f) nextScan = Time.time + 2.4f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; diff --git a/DamageSync.cs b/DamageSync.cs index 9d69222..b15dde4 100644 --- a/DamageSync.cs +++ b/DamageSync.cs @@ -14,8 +14,10 @@ namespace MSCO public class DamageSync { const float ScanInterval = 10f; - const float CheckInterval = 1f; // Fahrzeuge, die wir fahren - const float FreeCheckInterval = 5f; // geparkte Fahrzeuge (Unfall nur durch Anstoßen) seltener + const float CheckInterval = 0.5f; // so oft nach einem Aufprall prüfen + const float ImpactWindow = 4f; // so lange nach einem Aufprall prüfen (Verformung läuft ein paar Frames) + const float ImpactDeltaV = 2.5f; // plötzliche Geschwindigkeitsänderung (m/s pro Frame) = Aufprall + const float SafetyCheckInterval = 30f; // sonst nur selten (z.B. Reparatur in der Werkstatt) const float MinMove = 0.002f; const int MaxPerMessage = 4000; @@ -26,13 +28,18 @@ namespace MSCO public int Key; public string Root; public MeshFilter Filter; + public Rigidbody Body; // Fahrzeug, zu dem das Mesh gehört public Vector3[] Known; // zuletzt gesendet/empfangen } readonly Dictionary meshes = new Dictionary(); readonly HashSet seen = new HashSet(); readonly List changed = new List(); - float nextScan, nextCheck, nextFreeCheck; + float nextScan, nextCheck, nextSafetyCheck; + // Mesh-Daten lesen ist teuer (große Arrays, Speicher-Müll) - nur nach einem Aufprall + readonly Dictionary lastVelocity = new Dictionary(); + readonly Dictionary lastImpact = new Dictionary(); + readonly List bodies = new List(); static List types; public int Count { get { return meshes.Count; } } @@ -41,27 +48,44 @@ namespace MSCO { meshes.Clear(); seen.Clear(); - nextScan = 0f; + lastVelocity.Clear(); + lastImpact.Clear(); + bodies.Clear(); + nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(ObjectSync objects, int localId, Action send) { + if (nextScan < 0f) nextScan = Time.time + 1.0f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; Scan(); } + // Aufprall erkennen: Geschwindigkeit ändert sich schlagartig (billig, jedes Frame) + for (int i = 0; i < bodies.Count; i++) + { + Rigidbody rb = bodies[i]; + if (rb == null || rb.isKinematic) continue; + Vector3 v = rb.velocity, before; + if (lastVelocity.TryGetValue(rb, out before) && (v - before).sqrMagnitude > ImpactDeltaV * ImpactDeltaV) + lastImpact[rb] = Time.time; + lastVelocity[rb] = v; + } + if (Time.time < nextCheck) return; nextCheck = Time.time + CheckInterval; - bool checkFree = Time.time >= nextFreeCheck; - if (checkFree) nextFreeCheck = Time.time + FreeCheckInterval; + bool safety = Time.time >= nextSafetyCheck; + if (safety) nextSafetyCheck = Time.time + SafetyCheckInterval; foreach (DamageMesh m in meshes.Values) { if (m.Filter == null) continue; int owner = objects.VehicleOwner(m.Root); if (owner != SyncedBody.NoOwner && owner != localId) continue; // der Fahrer schickt die Schäden - if (owner == SyncedBody.NoOwner && !checkFree) continue; + float impact; + bool recentImpact = m.Body != null && lastImpact.TryGetValue(m.Body, out impact) && Time.time - impact < ImpactWindow; + if (!recentImpact && !safety) continue; Mesh mesh = m.Filter.sharedMesh; if (mesh == null) continue; @@ -164,7 +188,9 @@ namespace MSCO if (mesh == null || mesh.vertexCount == 0 || mesh.vertexCount > 65000) return; int key = Hash(FsmSync.PathOf(mf.transform)); if (meshes.ContainsKey(key)) return; // nicht eindeutig - meshes[key] = new DamageMesh { Key = key, Root = mf.transform.root.name, Filter = mf, Known = mesh.vertices }; + Rigidbody body = mf.transform.root.GetComponent(); + meshes[key] = new DamageMesh { Key = key, Root = mf.transform.root.name, Filter = mf, Body = body, Known = mesh.vertices }; + if (body != null && !bodies.Contains(body)) bodies.Add(body); } static bool IsVehicle(string root) diff --git a/HingeSync.cs b/HingeSync.cs index 4a68575..284756b 100644 --- a/HingeSync.cs +++ b/HingeSync.cs @@ -50,11 +50,12 @@ namespace MSCO foreach (Hinge h in hinges.Values) StopDriving(h); hinges.Clear(); seen.Clear(); - nextScan = 0f; + nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(ObjectSync objects, int localId, Vector3 playerPos, float dt, Action send) { + if (nextScan < 0f) nextScan = Time.time + 1.7f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; diff --git a/LobbyUI.cs b/LobbyUI.cs index a26c45e..3cdddc1 100644 --- a/LobbyUI.cs +++ b/LobbyUI.cs @@ -19,9 +19,16 @@ namespace MSCO const float SidebarWidth = 240f; const float StatusHeight = 40f; const float MaxContentWidth = 1100f; - float vw, vh, uiScale = 1f; // virtuelle Bildschirmgröße (Einheiten vor der Skalierung) + float vw, vh; // Bildschirmgröße in Pixeln + static float uiScale = 1f; // Skalierung passend zur Auflösung + float stylesScale = -1f; // für diese Skalierung wurden die Stile gebaut float contentHeight = 400f; // Höhe des Inhaltsbereichs - Listen wachsen damit + // Maße (S) und Schriftgrößen/Abstände (P) werden direkt in Bildschirmpixeln berechnet - Schrift und Kanten + // werden so in voller Auflösung gezeichnet (scharf), statt ein kleines Bild hochzuziehen + static float S(float v) { return Mathf.Round(v * uiScale); } + static int P(int v) { return Mathf.Max(1, Mathf.RoundToInt(v * uiScale)); } + /// Höhe für eine Liste: was nach "reserved" für Überschriften/Knöpfe übrig bleibt. float ListHeight(float reserved, float min) { return Mathf.Max(min, contentHeight - reserved); } @@ -84,15 +91,12 @@ namespace MSCO public void Draw(MultiplayerManager mp) { - EnsureStyles(); uiScale = Mathf.Clamp(Mathf.Min(Screen.height / RefHeight, Screen.width / RefWidth), MinScale, MaxScale); - vw = Screen.width / uiScale; - vh = Screen.height / uiScale; + EnsureStyles(); // baut die Stile neu, wenn sich die Skalierung ändert (Auflösung gewechselt) + vw = Screen.width; + vh = Screen.height; ScreenRect = new Rect(0, 0, Screen.width, Screen.height); - Matrix4x4 previous = GUI.matrix; - GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(uiScale, uiScale, 1f)); - try { DrawScaled(mp); } - finally { GUI.matrix = previous; } + DrawScaled(mp); } void DrawScaled(MultiplayerManager mp) @@ -109,12 +113,12 @@ namespace MSCO var window = new Rect(0, 0, vw, vh); GUI.Box(window, GUIContent.none, windowBox); - DrawSidebar(new Rect(0, 0, SidebarWidth, vh - StatusHeight)); + DrawSidebar(new Rect(0, 0, S(SidebarWidth), vh - S(StatusHeight))); // Inhalt mittig im freien Bereich, nicht beliebig breit (lesbar bleiben) - float free = vw - SidebarWidth; - float width = Mathf.Min(MaxContentWidth, free - 96f); - var content = new Rect(SidebarWidth + (free - width) / 2f, 40f, width, vh - StatusHeight - 72f); + float free = vw - S(SidebarWidth); + float width = Mathf.Min(S(MaxContentWidth), free - S(96f)); + var content = new Rect(S(SidebarWidth) + Mathf.Round((free - width) / 2f), S(40f), width, vh - S(StatusHeight) - S(72f)); contentHeight = content.height; GUILayout.BeginArea(content); if (versionWarning) DrawVersionWarning(mp); @@ -125,7 +129,7 @@ namespace MSCO else DrawServerBrowser(mp); GUILayout.EndArea(); - DrawStatusBar(mp, new Rect(0, vh - StatusHeight, vw, StatusHeight)); + DrawStatusBar(mp, new Rect(0, vh - S(StatusHeight), vw, S(StatusHeight))); } // ---------------------------------------------------------------- Bereiche @@ -135,38 +139,38 @@ namespace MSCO GUI.Box(r, GUIContent.none, sidebarBox); // Logo + Schriftzug Texture2D logo = Branding.LogoTexture; - float textX = r.x + 22; + float textX = r.x + S(22); if (logo != null) { - GUI.DrawTexture(new Rect(r.x + 20, r.y + 20, 44, 44), logo); - textX = r.x + 72; + GUI.DrawTexture(new Rect(r.x + S(20), r.y + S(20), S(44), S(44)), logo); + textX = r.x + S(72); } - GUI.Label(new Rect(textX, r.y + 24, r.xMax - textX, 40), "MSCO", logoStyle); - GUI.Label(new Rect(r.x + 22, r.y + 70, r.width - 30, 20), "MY SUMMER CAR ONLINE", taglineStyle); - GUI.DrawTexture(new Rect(r.x + 20, r.y + 98, r.width - 40, 1), texDivider); + GUI.Label(new Rect(textX, r.y + S(24), r.xMax - textX, S(40)), "MSCO", logoStyle); + GUI.Label(new Rect(r.x + S(22), r.y + S(70), r.width - S(30), S(20)), "MY SUMMER CAR ONLINE", taglineStyle); + GUI.DrawTexture(new Rect(r.x + S(20), r.y + S(98), r.width - S(40), S(1)), texDivider); - float y = r.y + 110; - if (NavButton(new Rect(r.x, y, r.width, 42), "P2P", page == Page.P2P)) page = Page.P2P; - y += 44; - if (NavButton(new Rect(r.x, y, r.width, 42), "Server Browser", page == Page.ServerBrowser)) page = Page.ServerBrowser; - y += 44; + float y = r.y + S(110); + if (NavButton(new Rect(r.x, y, r.width, S(42)), "P2P", page == Page.P2P)) page = Page.P2P; + y += S(44); + if (NavButton(new Rect(r.x, y, r.width, S(42)), "Server Browser", page == Page.ServerBrowser)) page = Page.ServerBrowser; + y += S(44); int favCount = Favorites.All.Count; - if (NavButton(new Rect(r.x, y, r.width, 42), "Favorites" + (favCount > 0 ? " (" + favCount + ")" : ""), page == Page.Favorites)) page = Page.Favorites; + if (NavButton(new Rect(r.x, y, r.width, S(42)), "Favorites" + (favCount > 0 ? " (" + favCount + ")" : ""), page == Page.Favorites)) page = Page.Favorites; // Eigenes Hauptmenü: Spiel beenden (die Knöpfe des Spiel-Menüs sind ausgeblendet) if (quitConfirm) { - if (GUI.Button(new Rect(r.x + 16, r.yMax - 118, r.width - 32, 36), "QUIT GAME?", dangerStyle)) Application.Quit(); - if (GUI.Button(new Rect(r.x + 16, r.yMax - 76, r.width - 32, 30), "CANCEL", tabStyle)) quitConfirm = false; + if (GUI.Button(new Rect(r.x + S(16), r.yMax - S(118), r.width - S(32), S(36)), "QUIT GAME?", dangerStyle)) Application.Quit(); + if (GUI.Button(new Rect(r.x + S(16), r.yMax - S(76), r.width - S(32), S(30)), "CANCEL", tabStyle)) quitConfirm = false; } - else if (GUI.Button(new Rect(r.x + 16, r.yMax - 76, r.width - 32, 36), "QUIT", tabStyle)) quitConfirm = true; + else if (GUI.Button(new Rect(r.x + S(16), r.yMax - S(76), r.width - S(32), S(36)), "QUIT", tabStyle)) quitConfirm = true; - GUI.Label(new Rect(r.x + 24, r.yMax - 30, r.width - 48, 20), "v" + MSCO.ModVersion, taglineStyle); + GUI.Label(new Rect(r.x + S(24), r.yMax - S(30), r.width - S(48), S(20)), "v" + MSCO.ModVersion, taglineStyle); } bool NavButton(Rect r, string text, bool active) { - if (active) GUI.Box(new Rect(r.x + 4, r.y + 8, 4, r.height - 16), GUIContent.none, accentBox); // Akzentbalken links + if (active) GUI.Box(new Rect(r.x + S(4), r.y + S(8), S(4), r.height - S(16)), GUIContent.none, accentBox); // Akzentbalken links return GUI.Button(r, text, active ? navActiveStyle : navStyle); } @@ -175,13 +179,13 @@ namespace MSCO GUILayout.Label("P2P", titleStyle); GUILayout.Label("Host a game and invite up to " + (MultiplayerManager.MaxPlayers - 1) + " friends through Steam - continue your own save or start a new game.", textStyle); - GUILayout.Space(18); + GUILayout.Space(S(18)); if (mp.SteamAvailable) { - if (GUILayout.Button("HOST GAME", primaryStyle, GUILayout.Height(46), GUILayout.Width(250))) + if (GUILayout.Button("HOST GAME", primaryStyle, GUILayout.Height(S(46)), GUILayout.Width(S(250)))) mp.HostSteam(); - GUILayout.Space(14); + GUILayout.Space(S(14)); GUILayout.Label("To join a friend: accept their invite in Steam, or right-click them in your " + "Steam friends list and choose \"Join Game\".", textStyle); } @@ -219,26 +223,26 @@ namespace MSCO GUILayout.BeginHorizontal(); // Titel darf nicht vom REFRESH-Button zusammengedrückt werden (sonst Zeilenumbruch) GUILayout.Label("Server Browser", titleStyle, GUILayout.ExpandWidth(true)); - if (GUILayout.Button(browser.Loading ? "LOADING..." : "REFRESH", tabStyle, GUILayout.Height(28), GUILayout.Width(110))) + if (GUILayout.Button(browser.Loading ? "LOADING..." : "REFRESH", tabStyle, GUILayout.Height(S(28)), GUILayout.Width(S(110)))) browser.Refresh(mp); GUILayout.EndHorizontal(); // Suche + Filter GUILayout.BeginHorizontal(); - searchInput = GUILayout.TextField(searchInput, 40, fieldStyle, GUILayout.Height(26), GUILayout.Width(170)); + searchInput = GUILayout.TextField(searchInput, 40, fieldStyle, GUILayout.Height(S(26)), GUILayout.Width(S(170))); if (string.IsNullOrEmpty(searchInput)) { Rect field = GUILayoutUtility.GetLastRect(); - GUI.Label(new Rect(field.x + 10, field.y, field.width, field.height), "Search...", placeholderStyle); + GUI.Label(new Rect(field.x + S(10), field.y, field.width, field.height), "Search...", placeholderStyle); } - GUILayout.Space(12); + GUILayout.Space(S(12)); showSteam = Chip(showSteam, "STEAM"); - GUILayout.Space(6); + GUILayout.Space(S(6)); showNonSteam = Chip(showNonSteam, "NON-STEAM"); - GUILayout.Space(6); + GUILayout.Space(S(6)); hideFull = Chip(hideFull, "HIDE FULL"); GUILayout.EndHorizontal(); - GUILayout.Space(6); + GUILayout.Space(S(6)); string search = (searchInput ?? "").Trim().ToLower(); if (shownServers == null || Event.current.type == EventType.Layout) @@ -251,35 +255,35 @@ namespace MSCO string shownError = Event.current.type == EventType.Layout ? (layoutError = browser.Error) : layoutError; // Tabellenkopf - Rect header = GUILayoutUtility.GetRect(10, 26, GUILayout.ExpandWidth(true)); + Rect header = GUILayoutUtility.GetRect(S(10), S(26), GUILayout.ExpandWidth(true)); GUI.Box(header, GUIContent.none, rowBox); - GUI.Label(new Rect(header.x + 12, header.y, header.width * 0.48f, header.height), "SERVER NAME", headerCellStyle); - GUI.Label(new Rect(header.x + header.width * 0.5f, header.y, 70, header.height), "PLAYERS", headerCellStyle); - GUI.Label(new Rect(header.x + header.width * 0.62f, header.y, 70, header.height), "TYPE", headerCellStyle); - GUI.Label(new Rect(header.x + header.width * 0.76f, header.y, 50, header.height), "PING", headerCellStyle); + GUI.Label(new Rect(header.x + S(12), header.y, header.width * 0.48f, header.height), "SERVER NAME", headerCellStyle); + GUI.Label(new Rect(header.x + header.width * 0.5f, header.y, S(70), header.height), "PLAYERS", headerCellStyle); + GUI.Label(new Rect(header.x + header.width * 0.62f, header.y, S(70), header.height), "TYPE", headerCellStyle); + GUI.Label(new Rect(header.x + header.width * 0.76f, header.y, S(50), header.height), "PING", headerCellStyle); - serverScroll = GUILayout.BeginScrollView(serverScroll, GUILayout.Height(ListHeight(230f, 140f))); + serverScroll = GUILayout.BeginScrollView(serverScroll, GUILayout.Height(ListHeight(S(230f), S(140f)))); if (shownError != null) GUILayout.Label(shownError, textStyle); if (servers.Count == 0) GUILayout.Label(browser.Loading ? "Loading servers..." : all.Count > 0 ? "No servers match your filters." : "No servers online right now.", textStyle); foreach (ServerListEntry s in servers) { - Rect row = GUILayoutUtility.GetRect(10, 34, GUILayout.ExpandWidth(true)); + Rect row = GUILayoutUtility.GetRect(S(10), S(34), GUILayout.ExpandWidth(true)); // Hervorgehobene Server (Keymaster-Admin) stehen oben - der Master liefert sie zuerst - var rowRect = new Rect(row.x, row.y + 2, row.width, row.height - 4); + var rowRect = new Rect(row.x, row.y + S(2), row.width, row.height - S(4)); bool hover = rowRect.Contains(Event.current.mousePosition); GUI.Box(rowRect, GUIContent.none, s.Featured ? featuredRowBox : hover ? rowHoverBox : rowBox); - if (s.Featured) GUI.DrawTexture(new Rect(row.x, row.y + 8, 3, row.height - 16), texGold); + if (s.Featured) GUI.DrawTexture(new Rect(row.x, row.y + S(8), S(3), row.height - S(16)), texGold); // Herz: als Favorit merken / entfernen (vor dem Zeilen-Knopf, damit es den Klick bekommt) - Heart(new Rect(row.xMax - 58, row.y + 7, 20, 20), s); + Heart(new Rect(row.xMax - S(58), row.y + S(7), S(20), S(20)), s); // Klick auf die Zeile öffnet das Server-Profil (dort wird verbunden) if (GUI.Button(new Rect(row.x, row.y, row.width, row.height), GUIContent.none, GUIStyle.none)) selectedServer = s.Address + ":" + s.Port; - GUI.DrawTexture(new Rect(row.x + 10, row.y + 13, 8, 8), s.Ping < 0 ? texPending : texOnline); - float nameX = row.x + 26; + GUI.DrawTexture(new Rect(row.x + S(10), row.y + S(13), S(8), S(8)), s.Ping < 0 ? texPending : texOnline); + float nameX = row.x + S(26); Favorites.Favorite fav = Favorites.FindByAddress(s.Address, s.Port); if (s.Featured) nameX += Pill(nameX, row, "FEATURED", pillFeaturedStyle); if (fav != null) nameX += Pill(nameX, row, fav.Nickname, pillMutedStyle); @@ -295,13 +299,13 @@ namespace MSCO GUI.Label(new Rect(nameX, row.y, row.x + row.width * 0.49f - nameX, row.height), s.Name, rowNameStyle); // Spielerzahl mit Füllbalken (wie auf der Website) float px = row.x + row.width * 0.5f; - GUI.Label(new Rect(px, row.y - 3, 70, row.height), s.Players + "/" + s.MaxPlayers, rowNameStyle); + GUI.Label(new Rect(px, row.y - S(3), S(70), row.height), s.Players + "/" + s.MaxPlayers, rowNameStyle); float fill = s.MaxPlayers > 0 ? Mathf.Clamp01((float)s.Players / s.MaxPlayers) : 0f; - GUI.DrawTexture(new Rect(px, row.y + 24, 48, 3), texBarBack); - GUI.DrawTexture(new Rect(px, row.y + 24, Mathf.Max(2f, 48 * fill), 3), fill >= 1f ? texBarFull : texBarFill); + GUI.DrawTexture(new Rect(px, row.y + S(24), S(48), S(3)), texBarBack); + GUI.DrawTexture(new Rect(px, row.y + S(24), Mathf.Max(S(2f), S(48) * fill), S(3)), fill >= 1f ? texBarFull : texBarFill); Pill(row.x + row.width * 0.62f, row, s.RequireSteam ? "STEAM" : "NO STEAM", s.RequireSteam ? pillStyle : pillMutedStyle); - GUI.Label(new Rect(row.x + row.width * 0.76f, row.y, 60, row.height), s.Ping < 0 ? "..." : s.Ping + " ms", PingStyle(s.Ping)); - GUI.Label(new Rect(row.xMax - 30, row.y, 20, row.height), "›", headerCellStyle); // Hinweis: Profil öffnen + GUI.Label(new Rect(row.x + row.width * 0.76f, row.y, S(60), row.height), s.Ping < 0 ? "..." : s.Ping + " ms", PingStyle(s.Ping)); + GUI.Label(new Rect(row.xMax - S(30), row.y, S(20), row.height), "›", headerCellStyle); // Hinweis: Profil öffnen } GUILayout.EndScrollView(); @@ -310,18 +314,18 @@ namespace MSCO if (!mp.SteamAvailable) { // Ohne Steam: eigener Name (nur für Non-Steam-Server) - GUILayout.BeginVertical(GUILayout.Width(150)); + GUILayout.BeginVertical(GUILayout.Width(S(150))); GUILayout.Label("YOUR NAME", captionStyle); DrawNameField(mp, 150); GUILayout.EndVertical(); - GUILayout.Space(10); + GUILayout.Space(S(10)); } GUILayout.BeginVertical(); GUILayout.Label("DIRECT CONNECT", captionStyle); GUILayout.BeginHorizontal(); - directInput = GUILayout.TextField(directInput, fieldStyle, GUILayout.Height(34)); - GUILayout.Space(10); - if (GUILayout.Button("CONNECT", primaryStyle, GUILayout.Height(34), GUILayout.Width(110))) + directInput = GUILayout.TextField(directInput, fieldStyle, GUILayout.Height(S(34))); + GUILayout.Space(S(10)); + if (GUILayout.Button("CONNECT", primaryStyle, GUILayout.Height(S(34)), GUILayout.Width(S(110)))) { string host; int port; @@ -344,7 +348,7 @@ namespace MSCO void DrawNameField(MultiplayerManager mp, float width) { if (nameInput == null) nameInput = mp.PlayerName; - string edited = GUILayout.TextField(nameInput, 24, fieldStyle, GUILayout.Height(34), GUILayout.Width(width)); + string edited = GUILayout.TextField(nameInput, 24, fieldStyle, GUILayout.Height(S(34)), GUILayout.Width(width)); if (edited != nameInput) { nameInput = edited; @@ -383,40 +387,40 @@ namespace MSCO EnsureHearts(); GUILayout.Label("Favorites", titleStyle); GUILayout.Label("Give each server a nickname - switch in the game with /switch in the chat or F7 console.", textStyle); - GUILayout.Space(8); + GUILayout.Space(S(8)); List favs = Favorites.All; - favoritesScroll = GUILayout.BeginScrollView(favoritesScroll, GUILayout.Height(ListHeight(190f, 160f))); + favoritesScroll = GUILayout.BeginScrollView(favoritesScroll, GUILayout.Height(ListHeight(S(190f), S(160f)))); if (favs.Count == 0) GUILayout.Label("No favorites yet. Click the heart next to a server in the Server Browser, or add one by address below.", textStyle); Favorites.Favorite remove = null; foreach (Favorites.Favorite f in favs) { ServerListEntry live = mp.Browser.Servers.Find(x => x.Port == f.Port && string.Equals(x.Address, f.Host, System.StringComparison.OrdinalIgnoreCase)); - GUILayout.BeginHorizontal(rowBox, GUILayout.Height(44)); - GUILayout.Space(6); + GUILayout.BeginHorizontal(rowBox, GUILayout.Height(S(44))); + GUILayout.Space(S(6)); // Herz (entfernen) - Rect heart = GUILayoutUtility.GetRect(22, 22, GUILayout.Width(22), GUILayout.Height(40)); - Rect heartIcon = new Rect(heart.x, heart.y + 10, 20, 20); + Rect heart = GUILayoutUtility.GetRect(S(22), S(22), GUILayout.Width(S(22)), GUILayout.Height(S(40))); + Rect heartIcon = new Rect(heart.x, heart.y + S(10), S(20), S(20)); GUI.DrawTexture(heartIcon, texHeartOn); if (GUI.Button(heartIcon, GUIContent.none, GUIStyle.none)) remove = f; - GUILayout.Space(8); + GUILayout.Space(S(8)); // Kurzname bearbeiten string edit; if (!nicknameEdits.TryGetValue(f.Address, out edit)) edit = f.Nickname; - GUILayout.BeginVertical(GUILayout.Width(150)); - GUILayout.Space(7); - edit = GUILayout.TextField(edit, 20, fieldStyle, GUILayout.Height(28), GUILayout.Width(150)); + GUILayout.BeginVertical(GUILayout.Width(S(150))); + GUILayout.Space(S(7)); + edit = GUILayout.TextField(edit, 20, fieldStyle, GUILayout.Height(S(28)), GUILayout.Width(S(150))); GUILayout.EndVertical(); nicknameEdits[f.Address] = edit; - GUILayout.Space(6); + GUILayout.Space(S(6)); bool changed = Favorites.CleanNickname(edit) != f.Nickname; - GUILayout.BeginVertical(GUILayout.Width(64)); - GUILayout.Space(7); + GUILayout.BeginVertical(GUILayout.Width(S(64))); + GUILayout.Space(S(7)); GUI.enabled = changed; - if (GUILayout.Button("SAVE", tabStyle, GUILayout.Height(28), GUILayout.Width(64))) + if (GUILayout.Button("SAVE", tabStyle, GUILayout.Height(S(28)), GUILayout.Width(S(64)))) { string error = Favorites.Set(edit, f.Host, f.Port, f.ServerName); if (error != null) MscoLog.Warning(error); @@ -424,24 +428,24 @@ namespace MSCO } GUI.enabled = true; GUILayout.EndVertical(); - GUILayout.Space(10); + GUILayout.Space(S(10)); // Server + Status GUILayout.BeginVertical(); - GUILayout.Space(4); + GUILayout.Space(S(4)); GUILayout.Label(string.IsNullOrEmpty(f.ServerName) ? f.Address : f.ServerName, rowNameStyle); GUILayout.Label(live == null ? f.Address + " · not listed / offline" : f.Address + " · " + live.Players + "/" + live.MaxPlayers + " · " + (live.Ping < 0 ? "..." : live.Ping + " ms"), textStyle); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); - GUILayout.BeginVertical(GUILayout.Width(90)); - GUILayout.Space(7); - if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(30), GUILayout.Width(90))) mp.SwitchServer(f.Nickname); + GUILayout.BeginVertical(GUILayout.Width(S(90))); + GUILayout.Space(S(7)); + if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(S(30)), GUILayout.Width(S(90)))) mp.SwitchServer(f.Nickname); GUILayout.EndVertical(); - GUILayout.Space(6); + GUILayout.Space(S(6)); GUILayout.EndHorizontal(); - GUILayout.Space(4); + GUILayout.Space(S(4)); } if (remove != null) { Favorites.Remove(remove); nicknameEdits.Remove(remove.Address); } GUILayout.EndScrollView(); @@ -449,14 +453,14 @@ namespace MSCO GUILayout.FlexibleSpace(); GUILayout.Label("ADD BY ADDRESS", captionStyle); GUILayout.BeginHorizontal(); - favoriteAddInput = GUILayout.TextField(favoriteAddInput ?? "", 60, fieldStyle, GUILayout.Height(34)); + favoriteAddInput = GUILayout.TextField(favoriteAddInput ?? "", 60, fieldStyle, GUILayout.Height(S(34))); if (string.IsNullOrEmpty(favoriteAddInput)) { Rect field = GUILayoutUtility.GetLastRect(); - GUI.Label(new Rect(field.x + 10, field.y, field.width, field.height), "host:port", placeholderStyle); + GUI.Label(new Rect(field.x + S(10), field.y, field.width, field.height), "host:port", placeholderStyle); } - GUILayout.Space(10); - if (GUILayout.Button("ADD", secondaryStyle, GUILayout.Height(34), GUILayout.Width(110))) + GUILayout.Space(S(10)); + if (GUILayout.Button("ADD", secondaryStyle, GUILayout.Height(S(34)), GUILayout.Width(S(110)))) { string host; int port; @@ -517,12 +521,12 @@ namespace MSCO /// Server-Profil: wem der Server gehört, Version, Spieler, Laufzeit ... void DrawServerProfile(MultiplayerManager mp, ServerListEntry s) { - if (GUILayout.Button("◂ BACK", tabStyle, GUILayout.Height(26), GUILayout.Width(90))) + if (GUILayout.Button("◂ BACK", tabStyle, GUILayout.Height(S(26)), GUILayout.Width(S(90)))) { selectedServer = null; return; } - GUILayout.Space(8); + GUILayout.Space(S(8)); if (s == null) { @@ -536,7 +540,7 @@ namespace MSCO GUILayout.BeginVertical(); GUILayout.Label(s.Name, titleStyle); GUILayout.BeginHorizontal(); - Rect badgeRow = GUILayoutUtility.GetRect(10, 20, GUILayout.ExpandWidth(true)); + Rect badgeRow = GUILayoutUtility.GetRect(S(10), S(20), GUILayout.ExpandWidth(true)); float bx = badgeRow.x; if (s.Featured) bx += Pill(bx, badgeRow, "FEATURED", pillFeaturedStyle); bx += Pill(bx, badgeRow, s.RequireSteam ? "STEAM" : "NON-STEAM", s.RequireSteam ? pillStyle : pillMutedStyle); @@ -547,49 +551,49 @@ namespace MSCO if (!string.IsNullOrEmpty(tag) && tag != "featured") bx += Pill(bx, badgeRow, tag.ToUpper(), pillMutedStyle); GUILayout.EndHorizontal(); GUILayout.EndVertical(); - GUILayout.Space(12); - Rect heartSlot = GUILayoutUtility.GetRect(44, 44, GUILayout.Width(44), GUILayout.Height(44)); + GUILayout.Space(S(12)); + Rect heartSlot = GUILayoutUtility.GetRect(S(44), S(44), GUILayout.Width(S(44)), GUILayout.Height(S(44))); GUI.Box(heartSlot, GUIContent.none, rowBox); - Heart(new Rect(heartSlot.x + 11, heartSlot.y + 11, 22, 22), s); - GUILayout.Space(8); + Heart(new Rect(heartSlot.x + S(11), heartSlot.y + S(11), S(22), S(22)), s); + GUILayout.Space(S(8)); GUI.enabled = CanJoin(mp, s); - if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(44), GUILayout.Width(180))) + if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(S(44)), GUILayout.Width(S(180)))) mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers); GUI.enabled = true; GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(s.Description)) GUILayout.Label(s.Description, textStyle); - GUILayout.Space(10); + GUILayout.Space(S(10)); // Ohne Steam: Name wählen (sauber in einem eigenen Kasten) if (s.RequireSteam && !mp.SteamAvailable) { - GUILayout.BeginHorizontal(rowBox, GUILayout.Height(40)); + GUILayout.BeginHorizontal(rowBox, GUILayout.Height(S(40))); GUILayout.Label("This server requires Steam - start the game through Steam to join.", textStyle); GUILayout.EndHorizontal(); - GUILayout.Space(10); + GUILayout.Space(S(10)); } else if (!mp.SteamAvailable) { - GUILayout.BeginHorizontal(rowBox, GUILayout.Height(44)); - GUILayout.Space(6); - GUILayout.BeginVertical(GUILayout.Width(110)); - GUILayout.Space(4); + GUILayout.BeginHorizontal(rowBox, GUILayout.Height(S(44))); + GUILayout.Space(S(6)); + GUILayout.BeginVertical(GUILayout.Width(S(110))); + GUILayout.Space(S(4)); GUILayout.Label("PLAYING AS", captionStyle); GUILayout.Label("no Steam", textStyle); GUILayout.EndVertical(); GUILayout.BeginVertical(); - GUILayout.Space(5); + GUILayout.Space(S(5)); DrawNameField(mp, 220); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - GUILayout.Space(10); + GUILayout.Space(S(10)); } // Infos links, Spielerliste rechts bool versionOk = s.Version == MSCO.ModVersion; GUILayout.BeginHorizontal(); - GUILayout.BeginVertical(GUILayout.Width(250)); + GUILayout.BeginVertical(GUILayout.Width(S(250))); InfoLine("HOSTED BY", string.IsNullOrEmpty(s.Owner) ? "unknown" : s.Owner, rowNameStyle); InfoLine("VERSION", "v" + s.Version + (versionOk ? "" : " (you: v" + MSCO.ModVersion + ")"), versionOk ? rowNameStyle : badgeStyle); InfoLine("STEAM", s.RequireSteam ? "Required" : "Not required", rowNameStyle); @@ -597,20 +601,20 @@ namespace MSCO InfoLine("PING", s.Ping < 0 ? "-" : s.Ping + " ms", PingStyle(s.Ping)); InfoLine("ADDRESS", s.Address + ":" + s.Port, rowNameStyle); GUILayout.EndVertical(); - GUILayout.Space(16); + GUILayout.Space(S(16)); - GUILayout.BeginVertical(rowBox, GUILayout.ExpandWidth(true), GUILayout.MinHeight(150)); - GUILayout.Space(6); + GUILayout.BeginVertical(rowBox, GUILayout.ExpandWidth(true), GUILayout.MinHeight(S(150))); + GUILayout.Space(S(6)); GUILayout.Label("PLAYERS " + s.Players + " / " + s.MaxPlayers, captionStyle); - profileScroll = GUILayout.BeginScrollView(profileScroll, GUILayout.Height(ListHeight(330f, 130f))); + profileScroll = GUILayout.BeginScrollView(profileScroll, GUILayout.Height(ListHeight(S(330f), S(130f)))); if (s.PlayerNames == null || s.PlayerNames.Count == 0) GUILayout.Label("Nobody online right now.", textStyle); else foreach (string name in s.PlayerNames) { - Rect line = GUILayoutUtility.GetRect(10, 24, GUILayout.ExpandWidth(true)); - GUI.DrawTexture(new Rect(line.x + 2, line.y + 8, 8, 8), texOnline); - GUI.Label(new Rect(line.x + 18, line.y, line.width - 18, line.height), name, rowNameStyle); + Rect line = GUILayoutUtility.GetRect(S(10), S(24), GUILayout.ExpandWidth(true)); + GUI.DrawTexture(new Rect(line.x + S(2), line.y + S(8), S(8), S(8)), texOnline); + GUI.Label(new Rect(line.x + S(18), line.y, line.width - S(18), line.height), name, rowNameStyle); } GUILayout.EndScrollView(); GUILayout.EndVertical(); @@ -621,8 +625,8 @@ namespace MSCO /// Eine Info-Zeile: Bezeichnung links (grau), Wert rechts. void InfoLine(string caption, string value, GUIStyle valueStyle) { - GUILayout.BeginHorizontal(GUILayout.Height(26)); - GUILayout.Label(caption, captionStyle, GUILayout.Width(100)); + GUILayout.BeginHorizontal(GUILayout.Height(S(26))); + GUILayout.Label(caption, captionStyle, GUILayout.Width(S(100))); GUILayout.Label(value, valueStyle); GUILayout.EndHorizontal(); } @@ -631,14 +635,14 @@ namespace MSCO static float Pill(float x, Rect row, string text, GUIStyle style) { Vector2 size = style.CalcSize(new GUIContent(text)); - GUI.Label(new Rect(x, row.y + (row.height - 18) / 2f, size.x, 18), text, style); - return size.x + 8; + GUI.Label(new Rect(x, row.y + (row.height - S(18)) / 2f, size.x, S(18)), text, style); + return size.x + S(8); } /// Filter-Chip (an = orange umrandet), ersetzt die Unity-Häkchen. bool Chip(bool on, string text) { - if (GUILayout.Button(text, on ? chipOnStyle : chipOffStyle, GUILayout.Height(26), GUILayout.ExpandWidth(false))) on = !on; + if (GUILayout.Button(text, on ? chipOnStyle : chipOffStyle, GUILayout.Height(S(26)), GUILayout.ExpandWidth(false))) on = !on; return on; } @@ -646,7 +650,7 @@ namespace MSCO { GUILayout.Label(caption, captionStyle); GUILayout.Label(value, valueStyle); - GUILayout.Space(2); + GUILayout.Space(S(2)); } /// Ping farbig: grün gut, gelb geht so, rot schlecht. @@ -671,20 +675,20 @@ namespace MSCO GUILayout.Label("Version mismatch", titleStyle); GUILayout.Label("This server runs a different MSCO version than you. You can still join, but things may not " + "sync correctly (players, items, cars). For the best experience use the same version.", textStyle); - GUILayout.Space(16); + GUILayout.Space(S(16)); GUILayout.Label("SERVER", captionStyle); GUILayout.Label(w.Name + " (" + w.Host + ":" + w.Port + ")", rowNameStyle); - GUILayout.Space(8); + GUILayout.Space(S(8)); GUILayout.Label("VERSIONS", captionStyle); GUILayout.Label("Server v" + w.ServerVersion + " - You v" + MSCO.ModVersion, rowTagStyle); - GUILayout.Space(22); + GUILayout.Space(S(22)); GUILayout.BeginHorizontal(); - if (GUILayout.Button("JOIN ANYWAY", primaryStyle, GUILayout.Height(42), GUILayout.Width(170))) + if (GUILayout.Button("JOIN ANYWAY", primaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(170)))) mp.ConfirmVersionWarning(); - GUILayout.Space(10); - if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(42), GUILayout.Width(170))) + GUILayout.Space(S(10)); + if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(170)))) mp.CancelVersionWarning(); GUILayout.EndHorizontal(); } @@ -694,22 +698,22 @@ namespace MSCO { GUILayout.Label("Join server?", titleStyle); GUILayout.Label("You opened a join link from the server list.", textStyle); - GUILayout.Space(18); + GUILayout.Space(S(18)); GUILayout.Label("SERVER ADDRESS", captionStyle); GUILayout.Label(mp.PendingJoinHost + ":" + mp.PendingJoinPort, rowNameStyle); - GUILayout.Space(24); + GUILayout.Space(S(24)); if (!mp.SteamAvailable) GUILayout.Label("Steam is required to join servers - start the game through Steam.", textStyle); GUILayout.BeginHorizontal(); GUI.enabled = mp.SteamAvailable; - if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(42), GUILayout.Width(170))) + if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(170)))) mp.ConfirmPendingJoin(); GUI.enabled = true; - GUILayout.Space(10); - if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(42), GUILayout.Width(170))) + GUILayout.Space(S(10)); + if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(170)))) mp.CancelPendingJoin(); GUILayout.EndHorizontal(); } @@ -727,43 +731,43 @@ namespace MSCO { if (texDim == null) texDim = Solid(new Color(0f, 0f, 0f, 0.6f)); if (texDanger == null) texDanger = Solid(Hex(0xe5, 0x48, 0x4d)); - if (promptTextStyle == null) + if (promptTextStyle == null || promptTextStyle.fontSize != P(15)) { promptTextStyle = new GUIStyle(rowNameStyle); promptTextStyle.wordWrap = true; - promptTextStyle.fontSize = 15; + promptTextStyle.fontSize = P(15); } GUI.DrawTexture(new Rect(0, 0, vw, vh), texDim); - var r = new Rect((vw - PromptWidth) / 2f, (vh - PromptHeight) / 2f, PromptWidth, PromptHeight); + var r = new Rect(Mathf.Round((vw - S(PromptWidth)) / 2f), Mathf.Round((vh - S(PromptHeight)) / 2f), S(PromptWidth), S(PromptHeight)); UiTheme.DrawShadow(r); GUI.Box(r, GUIContent.none, windowBox); string title = reconnecting ? ((mp.ReconnectMessage ?? "").StartsWith("Switching") ? "SWITCHING SERVER" : "RECONNECTING") : mp.DisconnectTitle; bool bad = !reconnecting && title != "DISCONNECTED"; - GUI.DrawTexture(new Rect(r.x, r.y, r.width, 4), bad ? texDanger : texAccent); + GUI.DrawTexture(new Rect(r.x, r.y, r.width, S(4)), bad ? texDanger : texAccent); - float x = r.x + 32, w = r.width - 64, y = r.y + 28; - GUI.Label(new Rect(x, y, w, 34), title, titleStyle); - y += 50; - GUI.Label(new Rect(x, y, w, 20), "REASON", captionStyle); + float x = r.x + S(32), w = r.width - S(64), y = r.y + S(28); + GUI.Label(new Rect(x, y, w, S(34)), title, titleStyle); + y += S(50); + GUI.Label(new Rect(x, y, w, S(20)), "REASON", captionStyle); if (reconnecting) { - GUI.Label(new Rect(x, y + 22, w, 44), mp.ReconnectMessage ?? "", promptTextStyle); + GUI.Label(new Rect(x, y + S(22), w, S(44)), mp.ReconnectMessage ?? "", promptTextStyle); string attempt = mp.ReconnectAttempt == 0 ? "Connecting again in a moment..." : "Attempt " + mp.ReconnectAttempt + " of " + mp.ReconnectMaxAttempts + (string.IsNullOrEmpty(mp.Status) ? "" : " - " + mp.Status); - GUI.Label(new Rect(x, y + 74, w, 40), attempt, textStyle); - if (GUI.Button(new Rect(r.xMax - 32 - 170, r.yMax - 28 - 42, 170, 42), "CANCEL", secondaryStyle)) mp.CancelReconnect(); + GUI.Label(new Rect(x, y + S(74), w, S(40)), attempt, textStyle); + if (GUI.Button(new Rect(r.xMax - S(32) - S(170), r.yMax - S(28) - S(42), S(170), S(42)), "CANCEL", secondaryStyle)) mp.CancelReconnect(); return; } - GUI.Label(new Rect(x, y + 22, w, 60), mp.DisconnectNotice ?? "", promptTextStyle); - GUI.Label(new Rect(x, y + 88, w, 20), "Your own save game has been restored.", textStyle); - if (GUI.Button(new Rect(r.xMax - 32 - 170, r.yMax - 28 - 42, 170, 42), "OK", primaryStyle)) mp.ClearDisconnectNotice(); + GUI.Label(new Rect(x, y + S(22), w, S(60)), mp.DisconnectNotice ?? "", promptTextStyle); + GUI.Label(new Rect(x, y + S(88), w, S(20)), "Your own save game has been restored.", textStyle); + if (GUI.Button(new Rect(r.xMax - S(32) - S(170), r.yMax - S(28) - S(42), S(170), S(42)), "OK", primaryStyle)) mp.ClearDisconnectNotice(); } bool newGameConfirm; @@ -775,38 +779,38 @@ namespace MSCO if (newGameConfirm) { GUILayout.BeginVertical(rowBox); - GUILayout.Space(6); + GUILayout.Space(S(6)); GUILayout.Label("START A NEW GAME?", captionStyle); GUILayout.Label(saved != null ? "Your current save game is kept as a backup in MSCO_Saves - everyone starts in a fresh world." : "Everyone starts in a fresh world.", textStyle); - GUILayout.Space(6); + GUILayout.Space(S(6)); GUILayout.BeginHorizontal(); - if (GUILayout.Button("YES, NEW GAME", dangerStyle, GUILayout.Height(38), GUILayout.Width(170))) + if (GUILayout.Button("YES, NEW GAME", dangerStyle, GUILayout.Height(S(38)), GUILayout.Width(S(170)))) { newGameConfirm = false; mp.StartHostNewGame(); } - GUILayout.Space(8); - if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(38), GUILayout.Width(110))) newGameConfirm = false; + GUILayout.Space(S(8)); + if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(S(38)), GUILayout.Width(S(110)))) newGameConfirm = false; GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - GUILayout.Space(6); + GUILayout.Space(S(6)); GUILayout.EndVertical(); return; } GUILayout.BeginHorizontal(); GUI.enabled = saved != null; - GUILayout.BeginVertical(GUILayout.Width(190)); - if (GUILayout.Button("CONTINUE", primaryStyle, GUILayout.Height(42), GUILayout.Width(190))) + GUILayout.BeginVertical(GUILayout.Width(S(190))); + if (GUILayout.Button("CONTINUE", primaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(190)))) mp.StartHostGame(); GUILayout.Label(saved != null ? "saved " + saved.Value.ToString("dd.MM.yyyy HH:mm") : "no save game yet", captionStyle); GUILayout.EndVertical(); GUI.enabled = true; - GUILayout.Space(10); - GUILayout.BeginVertical(GUILayout.Width(190)); - if (GUILayout.Button("NEW GAME", secondaryStyle, GUILayout.Height(42), GUILayout.Width(190))) + GUILayout.Space(S(10)); + GUILayout.BeginVertical(GUILayout.Width(S(190))); + if (GUILayout.Button("NEW GAME", secondaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(190)))) { if (saved != null) newGameConfirm = true; else mp.StartHostNewGame(); @@ -825,39 +829,39 @@ namespace MSCO : mp.IsDedicated || mp.IsConnectingToServer ? "Connected to the server - the world is loading" + (string.IsNullOrEmpty(mp.Status) ? "." : ": " + mp.Status) : "Waiting for the host to start the game.", textStyle); - GUILayout.Space(16); + GUILayout.Space(S(16)); GUILayout.Label("PLAYERS " + mp.Players.Count + "/" + mp.SessionMaxPlayers, captionStyle); - playerScroll = GUILayout.BeginScrollView(playerScroll, GUILayout.Height(ListHeight(mp.IsHost ? 330f : 170f, 120f))); + playerScroll = GUILayout.BeginScrollView(playerScroll, GUILayout.Height(ListHeight(mp.IsHost ? S(330f) : S(170f), S(120f)))); foreach (PlayerInfo p in mp.Players) { - Rect row = GUILayoutUtility.GetRect(10, 32, GUILayout.ExpandWidth(true)); - GUI.Box(new Rect(row.x, row.y, row.width, row.height - 4), GUIContent.none, rowBox); + Rect row = GUILayoutUtility.GetRect(S(10), S(32), GUILayout.ExpandWidth(true)); + GUI.Box(new Rect(row.x, row.y, row.width, row.height - S(4)), GUIContent.none, rowBox); // Eigener Punkt gelb, solange man noch lädt/synchronisiert; grün, wenn man wirklich im Spiel ist - GUI.DrawTexture(new Rect(row.x + 12, row.y + 11, 8, 8), p.IsLocal && !mp.IsFullyConnected ? texPending : texOnline); - GUI.Label(new Rect(row.x + 30, row.y, row.width - 150, row.height - 2), p.Name, rowNameStyle); + GUI.DrawTexture(new Rect(row.x + S(12), row.y + S(11), S(8), S(8)), p.IsLocal && !mp.IsFullyConnected ? texPending : texOnline); + GUI.Label(new Rect(row.x + S(30), row.y, row.width - S(150), row.height - S(2)), p.Name, rowNameStyle); string tag = p.Id == NetSession.HostId ? "HOST" : ""; if (p.IsLocal) tag = tag.Length > 0 ? tag + " · YOU" : "YOU"; - GUI.Label(new Rect(row.xMax - 120, row.y, 110, row.height - 2), tag, rowTagStyle); + GUI.Label(new Rect(row.xMax - S(120), row.y, S(110), row.height - S(2)), tag, rowTagStyle); } GUILayout.EndScrollView(); - GUILayout.Space(14); + GUILayout.Space(S(14)); if (mp.IsHost) { DrawHostStart(mp); - GUILayout.Space(10); + GUILayout.Space(S(10)); } GUILayout.BeginHorizontal(); if (mp.IsHost) { if (mp.IsSteamSession && mp.Players.Count < MultiplayerManager.MaxPlayers && - GUILayout.Button("INVITE FRIENDS", secondaryStyle, GUILayout.Height(42), GUILayout.Width(150))) + GUILayout.Button("INVITE FRIENDS", secondaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(150)))) mp.InviteFriends(); - GUILayout.Space(10); + GUILayout.Space(S(10)); } - if (GUILayout.Button("DISCONNECT", secondaryStyle, GUILayout.Height(42), GUILayout.Width(150))) + if (GUILayout.Button("DISCONNECT", secondaryStyle, GUILayout.Height(S(42)), GUILayout.Width(S(150)))) mp.Disconnect(); GUILayout.EndHorizontal(); } @@ -866,8 +870,8 @@ namespace MSCO { GUI.Box(r, GUIContent.none, sidebarBox); Texture2D dot = mp.IsFullyConnected ? texOnline : mp.IsInSession || mp.IsReconnecting ? texPending : texMuted; - GUI.DrawTexture(new Rect(r.x + 18, r.y + (r.height - 8) / 2f, 8, 8), dot); - GUI.Label(new Rect(r.x + 36, r.y, r.width - 72, r.height), mp.Status, statusStyle); + GUI.DrawTexture(new Rect(r.x + S(18), r.y + (r.height - S(8)) / 2f, S(8), S(8)), dot); + GUI.Label(new Rect(r.x + S(36), r.y, r.width - S(72), r.height), mp.Status, statusStyle); } @@ -875,61 +879,62 @@ namespace MSCO void EnsureStyles() { - if (stylesReady && texBackground != null) return; + if (stylesReady && texBackground != null && Mathf.Abs(stylesScale - uiScale) < 0.01f) return; + stylesScale = uiScale; // PlayerPrefs erst hier lesen (nicht im Konstruktor erlaubt) - directInput = PlayerPrefs.GetString(PrefDirect, ""); + if (directInput == null) directInput = PlayerPrefs.GetString(PrefDirect, ""); texBackground = Solid(Background); texSidebar = Solid(Sidebar); texAccent = Solid(Accent); - texOnline = UiTheme.Rounded(Online, 4); // runde Status-Punkte - texPending = UiTheme.Rounded(Pending, 4); - texMuted = UiTheme.Rounded(Muted, 4); + texOnline = UiTheme.Rounded(Online, P(4)); // runde Status-Punkte + texPending = UiTheme.Rounded(Pending, P(4)); + texMuted = UiTheme.Rounded(Muted, P(4)); texRow = Solid(Field); // Fenster und Seitenleiste mit feinem Rand (Seitenleiste: ihr Rand trennt sie vom Inhalt) - windowBox = UiTheme.Card(Background, 10); - sidebarBox = UiTheme.Card(Sidebar, 10); - rowBox = UiTheme.Panel9(Field, UiTheme.Radius); - rowHoverBox = UiTheme.Card(FieldFocus, UiTheme.Radius); + windowBox = UiTheme.Card(Background, P(10)); + sidebarBox = UiTheme.Card(Sidebar, P(10)); + rowBox = UiTheme.Panel9(Field, P(UiTheme.Radius)); + rowHoverBox = UiTheme.Card(FieldFocus, P(UiTheme.Radius)); texDivider = Solid(new Color(1f, 1f, 1f, 0.07f)); texGold = Solid(Gold); texBarBack = Solid(new Color(1f, 1f, 1f, 0.1f)); texBarFill = Solid(Online); texBarFull = Solid(UiTheme.Danger); - accentBox = UiTheme.Panel9(Accent, 1); + accentBox = UiTheme.Panel9(Accent, P(1)); - pillStyle = UiTheme.Panel9(new Color(Accent.r, Accent.g, Accent.b, 0.18f), 4); + pillStyle = UiTheme.Panel9(new Color(Accent.r, Accent.g, Accent.b, 0.18f), P(4)); pillStyle.normal.textColor = Accent; - pillStyle.fontSize = 10; + pillStyle.fontSize = P(10); pillStyle.fontStyle = FontStyle.Bold; pillStyle.alignment = TextAnchor.MiddleCenter; - pillStyle.padding = new RectOffset(7, 7, 0, 0); + pillStyle.padding = new RectOffset(P(7), P(7), P(0), P(0)); pillStyle.wordWrap = false; pillMutedStyle = new GUIStyle(pillStyle); - pillMutedStyle.normal.background = UiTheme.Rounded(new Color(1f, 1f, 1f, 0.08f), 4); + pillMutedStyle.normal.background = UiTheme.Rounded(new Color(1f, 1f, 1f, 0.08f), P(4)); pillMutedStyle.normal.textColor = Muted; pillFeaturedStyle = new GUIStyle(pillStyle); - pillFeaturedStyle.normal.background = UiTheme.Rounded(Gold, 4); + pillFeaturedStyle.normal.background = UiTheme.Rounded(Gold, P(4)); pillFeaturedStyle.normal.textColor = Hex(0x1a, 0x12, 0x06); - featuredRowBox = UiTheme.Panel9(Color.Lerp(Field, Gold, 0.16f), UiTheme.Radius); + featuredRowBox = UiTheme.Panel9(Color.Lerp(Field, Gold, 0.16f), P(UiTheme.Radius)); - chipOnStyle = Button(new Color(Accent.r, Accent.g, Accent.b, 0.22f), new Color(Accent.r, Accent.g, Accent.b, 0.32f), AccentHover, 11, FontStyle.Bold); - chipOffStyle = Button(Field, FieldFocus, Muted, 11, FontStyle.Bold); - chipOnStyle.padding = chipOffStyle.padding = new RectOffset(12, 12, 0, 0); + chipOnStyle = Button(new Color(Accent.r, Accent.g, Accent.b, 0.22f), new Color(Accent.r, Accent.g, Accent.b, 0.32f), AccentHover, P(11), FontStyle.Bold); + chipOffStyle = Button(Field, FieldFocus, Muted, P(11), FontStyle.Bold); + chipOnStyle.padding = chipOffStyle.padding = new RectOffset(P(12), P(12), P(0), P(0)); - logoStyle = Label(28, Color.white, FontStyle.Bold); - taglineStyle = Label(10, Muted, FontStyle.Bold); - titleStyle = Label(24, Color.white, FontStyle.Bold); - textStyle = Label(13, Muted, FontStyle.Normal); + logoStyle = Label(P(28), Color.white, FontStyle.Bold); + taglineStyle = Label(P(10), Muted, FontStyle.Bold); + titleStyle = Label(P(24), Color.white, FontStyle.Bold); + textStyle = Label(P(13), Muted, FontStyle.Normal); textStyle.wordWrap = true; - captionStyle = Label(11, Muted, FontStyle.Bold); - captionStyle.margin.bottom = 6; - statusStyle = Label(12, Color.white, FontStyle.Normal); + captionStyle = Label(P(11), Muted, FontStyle.Bold); + captionStyle.margin.bottom = P(6); + statusStyle = Label(P(12), Color.white, FontStyle.Normal); statusStyle.alignment = TextAnchor.MiddleLeft; - headerCellStyle = Label(11, Muted, FontStyle.Bold); + headerCellStyle = Label(P(11), Muted, FontStyle.Bold); headerCellStyle.alignment = TextAnchor.MiddleLeft; - rowNameStyle = Label(14, Color.white, FontStyle.Normal); + rowNameStyle = Label(P(14), Color.white, FontStyle.Normal); rowNameStyle.alignment = TextAnchor.MiddleLeft; pingGoodStyle = new GUIStyle(rowNameStyle); pingGoodStyle.normal.textColor = Online; @@ -937,34 +942,34 @@ namespace MSCO pingMidStyle.normal.textColor = Pending; pingBadStyle = new GUIStyle(rowNameStyle); pingBadStyle.normal.textColor = UiTheme.DangerHover; - rowTagStyle = Label(11, Accent, FontStyle.Bold); + rowTagStyle = Label(P(11), Accent, FontStyle.Bold); rowTagStyle.alignment = TextAnchor.MiddleRight; badgeStyle = new GUIStyle(rowTagStyle); badgeStyle.alignment = TextAnchor.MiddleLeft; - badgeStyle.padding = new RectOffset(6, 6, 2, 2); - placeholderStyle = Label(13, Muted, FontStyle.Italic); + badgeStyle.padding = new RectOffset(P(6), P(6), P(2), P(2)); + placeholderStyle = Label(P(13), Muted, FontStyle.Italic); placeholderStyle.alignment = TextAnchor.MiddleLeft; toggleStyle = new GUIStyle(GUI.skin.toggle); toggleStyle.normal.textColor = toggleStyle.onNormal.textColor = toggleStyle.hover.textColor = toggleStyle.onHover.textColor = Color.white; - toggleStyle.fontSize = 11; + toggleStyle.fontSize = P(11); toggleStyle.fontStyle = FontStyle.Bold; - navStyle = Button(Sidebar, Secondary, Muted, 14, FontStyle.Normal); + navStyle = Button(Sidebar, Secondary, Muted, P(14), FontStyle.Normal); navStyle.hover.textColor = Color.white; // Hover: Text hell (vorher blieb er grau) navStyle.alignment = TextAnchor.MiddleLeft; - navStyle.padding = new RectOffset(26, 10, 0, 0); - navActiveStyle = Button(Secondary, Secondary, Color.white, 14, FontStyle.Bold); + navStyle.padding = new RectOffset(P(26), P(10), P(0), P(0)); + navActiveStyle = Button(Secondary, Secondary, Color.white, P(14), FontStyle.Bold); navActiveStyle.alignment = TextAnchor.MiddleLeft; - navActiveStyle.padding = new RectOffset(26, 10, 0, 0); + navActiveStyle.padding = new RectOffset(P(26), P(10), P(0), P(0)); - primaryStyle = Button(Accent, AccentHover, Color.white, 14, FontStyle.Bold); + primaryStyle = Button(Accent, AccentHover, Color.white, P(14), FontStyle.Bold); UiTheme.ApplyPrimaryGradient(primaryStyle); - dangerStyle = Button(UiTheme.Danger, UiTheme.DangerHover, Color.white, 13, FontStyle.Bold); - secondaryStyle = Button(Secondary, SecondaryHover, Color.white, 14, FontStyle.Bold); - tabStyle = Button(Sidebar, Secondary, Muted, 11, FontStyle.Bold); + dangerStyle = Button(UiTheme.Danger, UiTheme.DangerHover, Color.white, P(13), FontStyle.Bold); + secondaryStyle = Button(Secondary, SecondaryHover, Color.white, P(14), FontStyle.Bold); + tabStyle = Button(Sidebar, Secondary, Muted, P(11), FontStyle.Bold); tabStyle.hover.textColor = Color.white; - fieldStyle = UiTheme.Field(14); + fieldStyle = UiTheme.Field(P(14)); stylesReady = true; } diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs index 4a64efc..408f726 100644 --- a/MultiplayerManager.cs +++ b/MultiplayerManager.cs @@ -279,8 +279,84 @@ namespace MSCO /// Ping-Anzeige oben rechts (Konsole: "ping"), bleibt gespeichert. public bool ShowPing { - get { return PlayerPrefs.GetInt(PrefShowPing, 0) == 1; } - set { PlayerPrefs.SetInt(PrefShowPing, value ? 1 : 0); } + get + { + if (showPing < 0) showPing = PlayerPrefs.GetInt(PrefShowPing, 0); // einmal lesen, nicht bei jedem Zeichnen + return showPing == 1; + } + set + { + showPing = value ? 1 : 0; + PlayerPrefs.SetInt(PrefShowPing, showPing); + } + } + int showPing = -1, showFps = -1; + + // ---------------------------------------------------------------- FPS-Anzeige (Konsole: "fps") + const string PrefShowFps = "MSCO_ShowFps"; + float fpsTimer, fpsValue = -1f, frameMsWorst; + int fpsFrames; + float frameMsWorstWindow; + + /// FPS-Anzeige oben rechts, bleibt gespeichert. + public bool ShowFps + { + get + { + if (showFps < 0) showFps = PlayerPrefs.GetInt(PrefShowFps, 0); + return showFps == 1; + } + set + { + showFps = value ? 1 : 0; + PlayerPrefs.SetInt(PrefShowFps, showFps); + } + } + + /// Bilder pro Sekunde (Mittel über eine halbe Sekunde) und längstes Bild darin (Ruckler). + public string FpsText + { + get + { + if (fpsValue < 0f) return "measuring..."; + return Mathf.RoundToInt(fpsValue) + " FPS (" + (1000f / Mathf.Max(1f, fpsValue)).ToString("0.0") + " ms, worst " + + frameMsWorst.ToString("0") + " ms)"; + } + } + + void MeasureFps() + { + float dt = Time.unscaledDeltaTime; + fpsFrames++; + fpsTimer += dt; + frameMsWorstWindow = Mathf.Max(frameMsWorstWindow, dt * 1000f); + if (fpsTimer < 0.5f) return; + fpsValue = fpsFrames / fpsTimer; + frameMsWorst = frameMsWorstWindow; + fpsFrames = 0; + fpsTimer = 0f; + frameMsWorstWindow = 0f; + } + + GUIStyle fpsStyle; + + /// Oben rechts, links neben dem Ping. + void DrawFps(float rightEdge) + { + if (!ShowFps) return; + if (fpsStyle == null) + { + fpsStyle = UiTheme.Panel9(new Color(0.06f, 0.06f, 0.07f, 0.85f), 6); + fpsStyle.fontSize = 12; + fpsStyle.fontStyle = FontStyle.Bold; + fpsStyle.alignment = TextAnchor.MiddleCenter; + fpsStyle.padding = new RectOffset(10, 10, 0, 0); + } + // grün ab 50, gelb ab 30, sonst rot + fpsStyle.normal.textColor = fpsValue < 0f ? UiTheme.Muted : fpsValue >= 50f ? UiTheme.Online : fpsValue >= 30f ? UiTheme.Pending : UiTheme.Danger; + var content = new GUIContent(fpsValue < 0f ? "FPS ..." : "FPS " + Mathf.RoundToInt(fpsValue)); + float width = fpsStyle.CalcSize(content).x; + GUI.Box(new Rect(rightEdge - width, 8, width, 24), content, fpsStyle); } /// Text für Anzeige und Konsole. @@ -311,7 +387,9 @@ namespace MSCO var content = new GUIContent("PING " + PingText); float width = pingStyle.CalcSize(content).x; GUI.Box(new Rect(Screen.width - width - 12, 8, width, 24), content, pingStyle); + pingWidth = width + 8; } + float pingWidth; // Platz, den die Ping-Anzeige belegt (FPS steht links daneben) // ---------------------------------------------------------------- Cheats (Admin-Menü) @@ -871,6 +949,7 @@ namespace MSCO void Update() { + MeasureFps(); // Menü-Buttons des Spiels nicht durch unser Fenster hindurch auslösen (z.B. Changelog) // Solange das Multiplayer-Fenster offen ist, reagiert das Spielmenü (CONTINUE, QUIT ...) gar nicht; // erst nach MINIMIZE wieder. Über der eingeklappten Leiste ebenfalls nicht (sonst öffnet sich z.B. der Changelog). @@ -2179,7 +2258,7 @@ namespace MSCO if (MenuHelper.InMenu) { lobby.Draw(this); - DrawPing(); toasts.Draw(); + pingWidth = 0f; DrawPing(); DrawFps(Screen.width - 12 - pingWidth); toasts.Draw(); overlays.Draw(this, false); return; } @@ -2196,7 +2275,7 @@ namespace MSCO } if (session != null) DrawHud(); - DrawPing(); toasts.Draw(); + pingWidth = 0f; DrawPing(); DrawFps(Screen.width - 12 - pingWidth); toasts.Draw(); overlays.Draw(this, session != null); } diff --git a/ObjectSync.cs b/ObjectSync.cs index 0ed1cf4..1908b7e 100644 --- a/ObjectSync.cs +++ b/ObjectSync.cs @@ -900,9 +900,9 @@ namespace MSCO var candidates = new List>(); var rootsByName = new Dictionary>(); - foreach (UnityEngine.Object o in UnityEngine.Object.FindObjectsOfType(typeof(Rigidbody))) + foreach (Rigidbody rb in SceneCache.Rigidbodies(1f)) { - var rb = (Rigidbody)o; + if (rb == null) continue; Transform root = rb.transform.root; List roots; if (!rootsByName.TryGetValue(root.name, out roots)) rootsByName[root.name] = roots = new List(); diff --git a/SceneCache.cs b/SceneCache.cs index 5e19e20..773fa3a 100644 --- a/SceneCache.cs +++ b/SceneCache.cs @@ -14,6 +14,27 @@ namespace MSCO static float builtAt = -1000f; static int builtForLevel = -1; + static Rigidbody[] bodies = new Rigidbody[0]; + static float bodiesAt = -1000f; + static int bodiesForLevel = -1; + + /// + /// Alle Rigidbodies; höchstens maxAge Sekunden alt (Objekt-Sync und Verkehr teilen sich die teure Suche). + /// Einträge können inzwischen zerstört sein (== null prüfen). + /// + public static Rigidbody[] Rigidbodies(float maxAge) + { + if (Time.realtimeSinceStartup - bodiesAt > maxAge || bodiesForLevel != Application.loadedLevel) + { + Object[] found = Object.FindObjectsOfType(typeof(Rigidbody)); + bodies = new Rigidbody[found.Length]; + for (int i = 0; i < found.Length; i++) bodies[i] = (Rigidbody)found[i]; + bodiesAt = Time.realtimeSinceStartup; + bodiesForLevel = Application.loadedLevel; + } + return bodies; + } + /// Alle FSMs; höchstens maxAge Sekunden alt. Einträge können inzwischen zerstört sein (== null prüfen). public static PlayMakerFSM[] Fsms(float maxAge) { diff --git a/TrafficSync.cs b/TrafficSync.cs index 8a28726..2f44f4c 100644 --- a/TrafficSync.cs +++ b/TrafficSync.cs @@ -42,11 +42,12 @@ namespace MSCO foreach (Car c in cars.Values) StopFollowing(c); cars.Clear(); seen.Clear(); - nextScan = 0f; + nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(bool authority, float dt, Action send) { + if (nextScan < 0f) nextScan = Time.time + 4.5f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; @@ -136,7 +137,7 @@ namespace MSCO void Scan() { int before = cars.Count; - foreach (Rigidbody rb in UnityEngine.Object.FindObjectsOfType()) + foreach (Rigidbody rb in SceneCache.Rigidbodies(5f)) // teilt sich die Suche mit dem Objekt-Sync { if (rb == null || !seen.Add(rb)) continue; if (Array.IndexOf(Roots, rb.transform.root.name) < 0) continue; diff --git a/VehicleFx.cs b/VehicleFx.cs index ec9030b..fe384ac 100644 --- a/VehicleFx.cs +++ b/VehicleFx.cs @@ -23,7 +23,7 @@ namespace MSCO string n = name.ToLower(); return n.Contains("needle") || n.Contains("steeringwheel"); } - const float CacheRefresh = 5f; // Teile können an-/abgebaut werden + const float CacheRefresh = 15f; // Teile können an-/abgebaut werden (Nachschlagen ist teuer: ganzes Fahrzeug) const float ApplyTimeout = 2f; // ohne neue Daten nichts mehr erzwingen struct AudioState diff --git a/VehicleStateSync.cs b/VehicleStateSync.cs index 6384457..1cf9ef4 100644 --- a/VehicleStateSync.cs +++ b/VehicleStateSync.cs @@ -58,11 +58,12 @@ namespace MSCO vars.Clear(); pending.Clear(); scanned.Clear(); - nextScan = 0f; + nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(ObjectSync objects, int localId, Action send) { + if (nextScan < 0f) nextScan = Time.time + 3.1f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval; diff --git a/WorldVarSync.cs b/WorldVarSync.cs index f84ec36..9b8534f 100644 --- a/WorldVarSync.cs +++ b/WorldVarSync.cs @@ -49,11 +49,12 @@ namespace MSCO { vars.Clear(); scanned.Clear(); - nextScan = 0f; + nextScan = -1f; // erster Suchlauf verteilt, siehe Update } public void Update(Action send) { + if (nextScan < 0f) nextScan = Time.time + 3.8f; // nicht alle Sync-Teile im selben Frame suchen lassen if (Time.time >= nextScan) { nextScan = Time.time + ScanInterval;