Danro-Jump/Assets/Scripts/Runtime/Gameplay/Spawning/PlatformContentSpawner.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

244 lines
8.8 KiB
C#
Raw 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.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;
}
}
}