- GameSettings: playerName field persisted in Settings.json - NetworkPlayerSetup: owner-written DisplayName NetworkVariable (seeded from settings on spawn, sanitized/truncated, live rename push) - PlayerNameField: binds the session panel name input to settings - PlayerNameplates: screen-space overlay labels above players with distance-based font scaling (clamped min/max), pooling, FP self-hide Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
94 lines
2.7 KiB
C#
94 lines
2.7 KiB
C#
using System;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
|
|
/// Shared JSON-backed store for player-facing game settings (<persistentDataPath>/Settings.json).
|
|
/// Loaded lazily and cached in memory, so per-frame reads never touch the disk; each change is
|
|
/// written immediately (settings edits are rare), so a hard-killed build keeps them. The typed
|
|
/// facades (MouseSensitivity, MasterVolume) own the ranges/clamping; this class only persists raw values.
|
|
public static class GameSettings
|
|
{
|
|
private const string FileName = "Settings.json";
|
|
private const int FileVersion = 1;
|
|
|
|
[Serializable]
|
|
private class Data
|
|
{
|
|
public int version = FileVersion;
|
|
public float mouseSensitivity = global::MouseSensitivity.Default;
|
|
public float masterVolume = global::MasterVolume.Default;
|
|
public string playerName = "";
|
|
}
|
|
|
|
private static Data _data;
|
|
private static Data Store => _data ??= Load();
|
|
|
|
private static string SavePath() => Path.Combine(Application.persistentDataPath, FileName);
|
|
|
|
public static float MouseSensitivity
|
|
{
|
|
get => Store.mouseSensitivity;
|
|
set
|
|
{
|
|
if (Mathf.Approximately(Store.mouseSensitivity, value)) return;
|
|
Store.mouseSensitivity = value;
|
|
Save();
|
|
}
|
|
}
|
|
|
|
public static float MasterVolume
|
|
{
|
|
get => Store.masterVolume;
|
|
set
|
|
{
|
|
if (Mathf.Approximately(Store.masterVolume, value)) return;
|
|
Store.masterVolume = value;
|
|
Save();
|
|
}
|
|
}
|
|
|
|
/// <summary>Display name shown above the avatar. Empty = per-spawn fallback ("Player <clientId>").</summary>
|
|
public static string PlayerName
|
|
{
|
|
get => Store.playerName ?? "";
|
|
set
|
|
{
|
|
string v = value == null ? "" : value.Trim();
|
|
if (Store.playerName == v) return;
|
|
Store.playerName = v;
|
|
Save();
|
|
}
|
|
}
|
|
|
|
private static Data Load()
|
|
{
|
|
string path = SavePath();
|
|
if (File.Exists(path))
|
|
{
|
|
try
|
|
{
|
|
var data = JsonUtility.FromJson<Data>(File.ReadAllText(path));
|
|
if (data != null) return data;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarning($"[GameSettings] Loading {FileName} failed, using defaults: {e.Message}");
|
|
}
|
|
}
|
|
return new Data();
|
|
}
|
|
|
|
private static void Save()
|
|
{
|
|
try
|
|
{
|
|
Directory.CreateDirectory(Application.persistentDataPath);
|
|
File.WriteAllText(SavePath(), JsonUtility.ToJson(Store, true));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarning($"[GameSettings] Saving {FileName} failed: {e.Message}");
|
|
}
|
|
}
|
|
}
|