Danro-Jump/Assets/Scripts/Runtime/Settings/Core/ServiceSettingsService.cs
Danil Iarosh 1f6884c5ac Build gameplay systems and service settings
Подробные изменения:
- Добавлены и доработаны модальные окна главного меню, сервисные настройки, шаблоны UI Toolkit и единый scene-flow сервис для переходов между сценами.
- Расширена gameplay-архитектура: сессия забега, HUD, спавн контента платформ, collectible-предметы, оборудование, хрупкие и движущиеся платформы.
- Добавлена база противников: типы спавна, патрулирование, процедурный спавнер, правила размещения через ScriptableObject-сложности и убийство врагов прыжком сверху.
- Исправлены смерть и respawn игрока, финальный GameOver, отскоки от врагов, пружин и boost-предметов, а также проход сквозь врагов при усиленном подъеме.
- Проведен рефакторинг взаимодействия систем: общий landing context, единые правила player-contact, NonLazy gameplay session, fail-fast installer и централизованный выбор точек спавна.
- Обновлены сцены, prefab-ассеты, difficulty stages, ресурсы главного меню, настройки проекта, roadmap и аппаратная COM-интеграция.
- Добавлены ассеты летучей мыши, врагов, collectibles, TextMesh Pro, ProjectContext и gameplay UI.

Проверки:
- dotnet build .\\Assembly-CSharp.csproj --no-restore
- Unity validation ключевых gameplay/UI/scene-flow скриптов
- Unity Console после force refresh без ошибок
2026-05-27 16:34:51 +03:00

361 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using UnityEngine;
namespace DanroJump.Settings
{
/// <summary>
/// Runtime-сервис операторских настроек: хранит значения, валидирует их по базе и сохраняет во внешний файл.
/// </summary>
public sealed class ServiceSettingsService : IServiceSettingsService
{
private readonly ServiceSettingsDatabase database;
private readonly IServiceSettingsRepository repository;
private readonly IServiceSettingApplier applier;
private readonly Dictionary<string, ServiceSettingValue> values = new(StringComparer.Ordinal);
/// <summary>
/// Создает сервис поверх базы описаний, репозитория сохранения и опционального applier.
/// </summary>
public ServiceSettingsService(
ServiceSettingsDatabase database,
IServiceSettingsRepository repository,
IServiceSettingApplier applier = null)
{
this.database = database;
this.repository = repository;
this.applier = applier ?? new NullServiceSettingApplier();
}
/// <summary>
/// База описаний настроек, используемая сервисом.
/// </summary>
public ServiceSettingsDatabase Database => database;
public event Action SettingsLoaded;
public event Action SettingsSaved;
public event Action<string, ServiceSettingValue> SettingChanged;
/// <summary>
/// Загружает сохраненные значения, дополняет дефолтами и применяет их.
/// </summary>
public void Load()
{
values.Clear();
if (repository != null && repository.TryLoad(out var loadedValues))
{
foreach (var pair in loadedValues)
{
if (TryGetDefinition(pair.Key, out var definition) && IsStoredValueDefinition(definition))
{
values[pair.Key] = definition.Sanitize(pair.Value);
}
}
}
EnsureDefaults();
ApplyAll();
SettingsLoaded?.Invoke();
}
/// <summary>
/// Сохраняет текущий snapshot настроек в репозиторий.
/// </summary>
public void Save()
{
if (repository == null || !repository.Save(values))
{
return;
}
SettingsSaved?.Invoke();
}
/// <summary>
/// Перечитывает только значения, разрешенные для внешних изменений.
/// </summary>
public void ReloadExternalChanges()
{
if (repository == null || !repository.TryLoad(out var loadedValues))
{
return;
}
foreach (var pair in loadedValues)
{
SetExternal(pair.Key, pair.Value);
}
}
/// <summary>
/// Повторно применяет все значения через applier.
/// </summary>
public void ApplyAll()
{
if (database == null)
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition))
{
continue;
}
applier.Apply(definition, Get(definition.Key));
}
}
/// <summary>
/// Сбрасывает все настройки к дефолтам из базы и сохраняет результат.
/// </summary>
public void ResetAllToDefaults()
{
if (database == null)
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition))
{
continue;
}
Set(definition.Key, definition.DefaultValue);
}
Save();
}
/// <summary>
/// Сбрасывает одну группу настроек к дефолтам.
/// </summary>
public void ResetGroupToDefaults(string groupName)
{
if (database == null || string.IsNullOrWhiteSpace(groupName))
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition) ||
!string.Equals(definition.Group, groupName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
Set(definition.Key, definition.DefaultValue);
}
Save();
}
/// <summary>
/// Устанавливает значение настройки после проверки и sanitization.
/// </summary>
public bool Set(string key, ServiceSettingValue value)
{
if (!TryGetDefinition(key, out var definition))
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Unknown setting key '{key}'.");
return false;
}
if (definition.ValueType == ServiceSettingValueType.Button)
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Button setting '{key}' does not store a value.");
return false;
}
var sanitized = definition.Sanitize(value);
if (values.TryGetValue(key, out var current) && current.Equals(sanitized))
{
return true;
}
// Значение применяем сразу, а Save отвечает только за запись на диск.
values[key] = sanitized;
applier.Apply(definition, sanitized);
SettingChanged?.Invoke(key, sanitized);
return true;
}
/// <summary>
/// Устанавливает значение из внешнего файла только для разрешенных настроек.
/// </summary>
public bool SetExternal(string key, ServiceSettingValue value)
{
if (!TryGetDefinition(key, out var definition))
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Unknown external setting key '{key}'.");
return false;
}
if (!definition.ExposedForExternalChanges)
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Setting '{key}' is not exposed for external changes.");
return false;
}
return Set(key, value);
}
/// <summary>
/// Устанавливает bool-настройку по ключу.
/// </summary>
public bool SetBool(string key, bool value)
{
return Set(key, ServiceSettingValue.Bool(value));
}
/// <summary>
/// Устанавливает int-настройку по ключу.
/// </summary>
public bool SetInt(string key, int value)
{
return Set(key, ServiceSettingValue.Int(value));
}
/// <summary>
/// Устанавливает float-настройку по ключу.
/// </summary>
public bool SetFloat(string key, float value)
{
return Set(key, ServiceSettingValue.Float(value));
}
/// <summary>
/// Устанавливает string-настройку по ключу.
/// </summary>
public bool SetString(string key, string value)
{
return Set(key, ServiceSettingValue.String(value));
}
/// <summary>
/// Устанавливает option-настройку по индексу варианта.
/// </summary>
public bool SetOptionIndex(string key, int optionIndex)
{
return Set(key, ServiceSettingValue.Option(optionIndex));
}
/// <summary>
/// Пытается найти описание настройки по ключу.
/// </summary>
public bool TryGetDefinition(string key, out ServiceSettingDefinition definition)
{
definition = null;
return !string.IsNullOrWhiteSpace(key) &&
database != null &&
database.TryFind(key, out definition);
}
/// <summary>
/// Возвращает копию текущих значений для безопасного чтения снаружи.
/// </summary>
public IReadOnlyDictionary<string, ServiceSettingValue> Snapshot()
{
return new Dictionary<string, ServiceSettingValue>(values, StringComparer.Ordinal);
}
/// <summary>
/// Возвращает текущее значение или дефолт из базы.
/// </summary>
public ServiceSettingValue Get(string key)
{
if (values.TryGetValue(key, out var value))
{
return value;
}
return TryGetDefinition(key, out var definition) ? definition.DefaultValue : default;
}
/// <summary>
/// Возвращает bool-значение настройки.
/// </summary>
public bool GetBool(string key)
{
return Get(key).boolValue;
}
/// <summary>
/// Возвращает int-значение настройки.
/// </summary>
public int GetInt(string key)
{
return Get(key).intValue;
}
/// <summary>
/// Возвращает float-значение настройки.
/// </summary>
public float GetFloat(string key)
{
return Get(key).floatValue;
}
/// <summary>
/// Возвращает string-значение настройки.
/// </summary>
public string GetString(string key)
{
return Get(key).stringValue ?? string.Empty;
}
/// <summary>
/// Возвращает индекс выбранного option-варианта.
/// </summary>
public int GetOptionIndex(string key)
{
return Get(key).intValue;
}
/// <summary>
/// Заполняет отсутствующие значения дефолтами и заново sanitizes загруженные данные.
/// </summary>
private void EnsureDefaults()
{
if (database == null)
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition))
{
continue;
}
values[definition.Key] = values.TryGetValue(definition.Key, out var existingValue)
? definition.Sanitize(existingValue)
: definition.Sanitize(definition.DefaultValue);
}
}
/// <summary>
/// Проверяет, что описание настройки пригодно для работы сервиса.
/// </summary>
private static bool IsValidDefinition(ServiceSettingDefinition definition)
{
return definition != null && !string.IsNullOrWhiteSpace(definition.Key);
}
/// <summary>
/// Проверяет, что настройка хранит значение. Кнопки являются действиями и не попадают в JSON.
/// </summary>
private static bool IsStoredValueDefinition(ServiceSettingDefinition definition)
{
return IsValidDefinition(definition) && definition.ValueType != ServiceSettingValueType.Button;
}
}
}