Подробные изменения: - Добавлены и доработаны модальные окна главного меню, сервисные настройки, шаблоны 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 без ошибок
244 lines
8.8 KiB
C#
244 lines
8.8 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using Zenject;
|
||
|
||
namespace DanroJump.Gameplay
|
||
{
|
||
/// <summary>
|
||
/// Создает collectibles, equipment и enemies на точках спавна платформы.
|
||
/// </summary>
|
||
public sealed class PlatformContentSpawner
|
||
{
|
||
private readonly DiContainer container;
|
||
private readonly Dictionary<PlatformController, List<GameObject>> spawnedContentByPlatform = new();
|
||
private readonly List<PlatformSpawnedContent> orphanMarkerBuffer = new();
|
||
private readonly List<PlatformSpawnPoint> spawnPointCandidates = new();
|
||
|
||
/// <summary>
|
||
/// Получает DI-контейнер для создания prefab с injection.
|
||
/// </summary>
|
||
public PlatformContentSpawner(DiContainer container)
|
||
{
|
||
this.container = container;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Очищает старый контент платформы и спавнит новый по правилам источника.
|
||
/// </summary>
|
||
public void SpawnContent(
|
||
PlatformController platform,
|
||
IPlatformContentSource catalog,
|
||
System.Random random)
|
||
{
|
||
ClearSpawnedContent(platform);
|
||
|
||
if (platform == null || catalog == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
SpawnIntoPoints(platform, catalog, PlatformContentKind.Collectible, platform.CollectibleSpawnPoints, random);
|
||
SpawnIntoPoints(platform, catalog, PlatformContentKind.Equipment, platform.EquipmentSpawnPoints, random);
|
||
SpawnIntoPoints(platform, catalog, PlatformContentKind.Enemy, platform.EnemySpawnPoints, random);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Удаляет весь контент, созданный для указанной платформы.
|
||
/// </summary>
|
||
public void ClearSpawnedContent(PlatformController platform)
|
||
{
|
||
if (platform == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!ClearTrackedContent(platform))
|
||
{
|
||
ClearOrphanSpawnedContent(platform);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Очищает контент всех отслеживаемых платформ.
|
||
/// </summary>
|
||
public void ClearAll()
|
||
{
|
||
foreach (var pair in spawnedContentByPlatform)
|
||
{
|
||
ClearContentList(pair.Value);
|
||
}
|
||
|
||
spawnedContentByPlatform.Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Спавнит одну категорию контента в одну выбранную точку платформы.
|
||
/// </summary>
|
||
private void SpawnIntoPoints(
|
||
PlatformController platform,
|
||
IPlatformContentSource catalog,
|
||
PlatformContentKind kind,
|
||
IReadOnlyList<PlatformSpawnPoint> spawnPoints,
|
||
System.Random random)
|
||
{
|
||
if (spawnPoints == null || !catalog.ShouldSpawn(kind, random) || !catalog.TryGetPrefab(kind, random, out var prefab))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var selectedSpawnPoint = SelectSpawnPoint(spawnPoints, random);
|
||
if (selectedSpawnPoint != null)
|
||
{
|
||
Spawn(prefab, selectedSpawnPoint.Anchor, platform);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Создает конкретный prefab в точке спавна и привязывает оборудование к платформе.
|
||
/// </summary>
|
||
private void Spawn(GameObject prefab, Transform parent, PlatformController platform)
|
||
{
|
||
var targetParent = parent != null ? parent : platform.transform;
|
||
var instance = container.InstantiatePrefab(prefab, targetParent.position, targetParent.rotation, targetParent);
|
||
|
||
// Маркер нужен, чтобы при аварийной очистке найти даже неотслеженный дочерний контент.
|
||
if (!instance.TryGetComponent(out PlatformSpawnedContent _))
|
||
{
|
||
instance.AddComponent<PlatformSpawnedContent>();
|
||
}
|
||
|
||
if (instance.TryGetComponent(out IPlatformEquipment equipment))
|
||
{
|
||
equipment.Install(platform);
|
||
}
|
||
|
||
if (!spawnedContentByPlatform.TryGetValue(platform, out var spawnedContent))
|
||
{
|
||
spawnedContent = new List<GameObject>();
|
||
spawnedContentByPlatform.Add(platform, spawnedContent);
|
||
}
|
||
|
||
spawnedContent.Add(instance);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Удаляет контент, который был записан в словарь отслеживания.
|
||
/// </summary>
|
||
private bool ClearTrackedContent(PlatformController platform)
|
||
{
|
||
if (!spawnedContentByPlatform.TryGetValue(platform, out var spawnedContent))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
ClearContentList(spawnedContent);
|
||
spawnedContent.Clear();
|
||
spawnedContentByPlatform.Remove(platform);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Уничтожает объекты из уже выделенного списка без освобождения самого списка.
|
||
/// </summary>
|
||
private static void ClearContentList(List<GameObject> spawnedContent)
|
||
{
|
||
for (var index = spawnedContent.Count - 1; index >= 0; index--)
|
||
{
|
||
var content = spawnedContent[index];
|
||
if (content != null)
|
||
{
|
||
content.SetActive(false);
|
||
Object.Destroy(content);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fallback-очистка дочерних объектов, если словарь не содержит платформу.
|
||
/// </summary>
|
||
private void ClearOrphanSpawnedContent(PlatformController platform)
|
||
{
|
||
platform.GetComponentsInChildren(true, orphanMarkerBuffer);
|
||
|
||
for (var index = orphanMarkerBuffer.Count - 1; index >= 0; index--)
|
||
{
|
||
var marker = orphanMarkerBuffer[index];
|
||
if (marker != null)
|
||
{
|
||
marker.gameObject.SetActive(false);
|
||
Object.Destroy(marker.gameObject);
|
||
}
|
||
}
|
||
|
||
orphanMarkerBuffer.Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, нужно ли пропустить optional-точку спавна по ее весу.
|
||
/// </summary>
|
||
private static bool ShouldSkipOptionalPoint(PlatformSpawnPoint spawnPoint, System.Random random)
|
||
{
|
||
if (!spawnPoint.Optional)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (spawnPoint.Weight <= 0f)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (spawnPoint.Weight >= 1f)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var roll = random != null ? random.NextDouble() : 0d;
|
||
return roll > spawnPoint.Weight;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Выбирает одну подходящую точку по весам, чтобы плотность контента не зависела от числа маркеров на prefab.
|
||
/// </summary>
|
||
private PlatformSpawnPoint SelectSpawnPoint(
|
||
IReadOnlyList<PlatformSpawnPoint> spawnPoints,
|
||
System.Random random)
|
||
{
|
||
spawnPointCandidates.Clear();
|
||
var totalWeight = 0f;
|
||
for (var index = 0; index < spawnPoints.Count; index++)
|
||
{
|
||
var spawnPoint = spawnPoints[index];
|
||
if (spawnPoint == null || ShouldSkipOptionalPoint(spawnPoint, random))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
spawnPointCandidates.Add(spawnPoint);
|
||
totalWeight += Mathf.Max(0f, spawnPoint.Weight);
|
||
}
|
||
|
||
if (totalWeight <= 0f)
|
||
{
|
||
spawnPointCandidates.Clear();
|
||
return null;
|
||
}
|
||
|
||
var roll = (float)((random != null ? random.NextDouble() : 0d) * totalWeight);
|
||
for (var index = 0; index < spawnPointCandidates.Count; index++)
|
||
{
|
||
var spawnPoint = spawnPointCandidates[index];
|
||
roll -= Mathf.Max(0f, spawnPoint.Weight);
|
||
if (roll <= 0f)
|
||
{
|
||
spawnPointCandidates.Clear();
|
||
return spawnPoint;
|
||
}
|
||
}
|
||
|
||
spawnPointCandidates.Clear();
|
||
return null;
|
||
}
|
||
}
|
||
}
|