153 lines
5.6 KiB
C#
153 lines
5.6 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.InputSystem;
|
||
|
|
|
||
|
|
namespace Ironspore
|
||
|
|
{
|
||
|
|
public enum SettingsApplyResult { Saved, ConfirmDisplay, Failed }
|
||
|
|
|
||
|
|
// Runtime settings orchestration; UI owns its draft, while the store owns durable snapshots.
|
||
|
|
public sealed class SettingsService : IDisposable
|
||
|
|
{
|
||
|
|
readonly SettingsStore store;
|
||
|
|
readonly GameSettingsData defaults;
|
||
|
|
readonly float initialVolume;
|
||
|
|
readonly int initialQuality;
|
||
|
|
readonly Func<float> clock;
|
||
|
|
GameSettingsData current, preview;
|
||
|
|
float displayDeadline;
|
||
|
|
public InputBindingService Bindings { get; }
|
||
|
|
public GameSettingsData Current => current.Copy();
|
||
|
|
public GameSettingsData Defaults => defaults.Copy();
|
||
|
|
public bool ConfirmingDisplay => preview != null;
|
||
|
|
public int ConfirmationSeconds => Mathf.Max(0, Mathf.CeilToInt(displayDeadline - clock()));
|
||
|
|
public string Error => store.Error;
|
||
|
|
public float LookSensitivity => current.mouseSensitivity;
|
||
|
|
public float AimSensitivity => current.aimSensitivity;
|
||
|
|
|
||
|
|
public SettingsService(SettingsStore store, InputActionAsset input, GameSettingsData defaults, Func<float> clock = null)
|
||
|
|
{
|
||
|
|
this.store = store;
|
||
|
|
this.defaults = defaults.Copy();
|
||
|
|
this.clock = clock ?? (() => Time.realtimeSinceStartup);
|
||
|
|
initialVolume = AudioListener.volume;
|
||
|
|
initialQuality = QualitySettings.GetQualityLevel();
|
||
|
|
Bindings = new InputBindingService(input);
|
||
|
|
current = store.Data.Normalize(QualitySettings.names.Length);
|
||
|
|
if (!Bindings.TryLoad(current.bindingOverrides))
|
||
|
|
Debug.LogWarning("Invalid input overrides were ignored; using default controls.");
|
||
|
|
current.bindingOverrides = Bindings.Save();
|
||
|
|
ApplyRuntime(current, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static GameSettingsData RuntimeDefaults() => new GameSettingsData
|
||
|
|
{
|
||
|
|
qualityLevel = Mathf.Min(2, QualitySettings.names.Length - 1),
|
||
|
|
resolutionWidth = Application.isEditor ? 1280 : Mathf.Max(640, Screen.width),
|
||
|
|
resolutionHeight = Application.isEditor ? 720 : Mathf.Max(360, Screen.height),
|
||
|
|
fullscreen = !Application.isEditor && Screen.fullScreen
|
||
|
|
};
|
||
|
|
|
||
|
|
public List<Vector2Int> Resolutions()
|
||
|
|
{
|
||
|
|
var sizes = new List<Vector2Int>();
|
||
|
|
foreach (var resolution in Screen.resolutions)
|
||
|
|
AddSize(sizes, resolution.width, resolution.height);
|
||
|
|
AddSize(sizes, 1280, 720);
|
||
|
|
AddSize(sizes, 1600, 900);
|
||
|
|
AddSize(sizes, 1920, 1080);
|
||
|
|
AddSize(sizes, current.resolutionWidth, current.resolutionHeight);
|
||
|
|
sizes.Sort((a, b) => a.x == b.x ? a.y.CompareTo(b.y) : a.x.CompareTo(b.x));
|
||
|
|
return sizes;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void AddSize(List<Vector2Int> sizes, int width, int height)
|
||
|
|
{
|
||
|
|
var size = new Vector2Int(width, height);
|
||
|
|
if (width >= 640 && height >= 360 && !sizes.Contains(size))
|
||
|
|
sizes.Add(size);
|
||
|
|
}
|
||
|
|
|
||
|
|
public SettingsApplyResult Apply(GameSettingsData draft)
|
||
|
|
{
|
||
|
|
CancelDisplay();
|
||
|
|
var candidate = draft.Normalize(QualitySettings.names.Length);
|
||
|
|
candidate.bindingOverrides = Bindings.Save();
|
||
|
|
if (!candidate.SameDisplay(current))
|
||
|
|
{
|
||
|
|
preview = candidate;
|
||
|
|
displayDeadline = clock() + 15;
|
||
|
|
ApplyRuntime(preview, true);
|
||
|
|
return SettingsApplyResult.ConfirmDisplay;
|
||
|
|
}
|
||
|
|
return Save(candidate) ? SettingsApplyResult.Saved : SettingsApplyResult.Failed;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool ConfirmDisplay()
|
||
|
|
{
|
||
|
|
if (preview == null)
|
||
|
|
return false;
|
||
|
|
var candidate = preview;
|
||
|
|
preview = null;
|
||
|
|
if (Save(candidate))
|
||
|
|
return true;
|
||
|
|
ApplyRuntime(current, true);
|
||
|
|
Bindings.TryLoad(current.bindingOverrides);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Save(GameSettingsData candidate)
|
||
|
|
{
|
||
|
|
if (!store.Save(candidate))
|
||
|
|
return false;
|
||
|
|
current = candidate.Copy();
|
||
|
|
ApplyRuntime(current, false);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool Tick()
|
||
|
|
{
|
||
|
|
if (!ConfirmingDisplay || clock() < displayDeadline)
|
||
|
|
return false;
|
||
|
|
CancelDisplay();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void CancelDisplay()
|
||
|
|
{
|
||
|
|
if (preview == null)
|
||
|
|
return;
|
||
|
|
preview = null;
|
||
|
|
ApplyRuntime(current, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void DiscardDraft()
|
||
|
|
{
|
||
|
|
Bindings.Cancel();
|
||
|
|
CancelDisplay();
|
||
|
|
Bindings.TryLoad(current.bindingOverrides);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ApplyRuntime(GameSettingsData data, bool display)
|
||
|
|
{
|
||
|
|
AudioListener.volume = data.masterVolume;
|
||
|
|
if (QualitySettings.GetQualityLevel() != data.qualityLevel)
|
||
|
|
QualitySettings.SetQualityLevel(data.qualityLevel, true);
|
||
|
|
if (display && !Application.isEditor)
|
||
|
|
Screen.SetResolution(data.resolutionWidth, data.resolutionHeight,
|
||
|
|
data.fullscreen ? FullScreenMode.ExclusiveFullScreen : FullScreenMode.Windowed);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose()
|
||
|
|
{
|
||
|
|
DiscardDraft();
|
||
|
|
Bindings.Dispose();
|
||
|
|
#if UNITY_EDITOR
|
||
|
|
AudioListener.volume = initialVolume;
|
||
|
|
QualitySettings.SetQualityLevel(initialQuality, true);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|