Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Убран пулинг из `EnvironmentalCollectibleBase` и `EnvironmentalCollectibleSpawner`: при подборе и деспауне частицы теперь уничтожаются через `Destroy()`, что устраняет проблему переиспользования объектов старого типа при смене частиц. - Удалены поля `usePooling`, `_ownerPool`, `_originPrefab` из `EnvironmentalCollectibleBase`; упрощены методы `Collect()` и `Despawn()`. - Добавлено время жизни частиц: поле `defaultLifetime` в `EnvironmentalCollectibleBase`, таймер `_age` с автоматическим деспауном по истечении; виртуальный `OnDespawned()` для наследников. - В `SpawnEntry` добавлены `overrideLifetime` и `lifetimeOverride` для переопределения времени жизни. - Добавлена структура `NamedSpawnEntry` — именованные записи с ключом (`string key`), полным `SpawnEntry` и флагом `hardReplace`, настраиваемые в инспекторе. - Спаунер получает игрока из `GameManager.Instance.PlayerObj` вместо `FindGameObjectWithTag`; подписка на `GameManager.PlayerSpawned` для автообновления ссылки. - Добавлен постепенный спаун: `MaintainFixedCount()` запускает корутину `SpawnRoutine()` с рандомной задержкой `spawnDelay` между объектами. - Добавлены диапазоны случайного масштаба (`scaleRange`) и угла поворота (`rotationRange`) при спауне. - Добавлены публичные методы переключения типа частиц для `UnityEvent`: `SetForcedPrefab`, `SetForcedPrefabHard`, `SetForcedPrefabByObject`, `SetForcedPrefabByObjectHard`, `SetForcedPrefabByIndex`, `SetForcedPrefabHardByIndex`, `SetForcedEntry`, `SetForcedEntryByName`, `SetEntryByName` (мягкая постепенная замена через `GradualReplaceRoutine`), `ClearForcedPrefab`, `SetOverrideArea`. - Исправлено сравнение prefab по ссылкам на сравнение по `name`, устраняя проблему когда один и тот же ассет из разных массивов (`spawnEntries` / `namedEntries`) не совпадал. - Удалён `EnvironmentalZoneTrigger.cs` (триггер-зона не нужна как отдельный скрипт). - Удалены `EnvironmentalPool` из использования в спаунере и базовом классе. - Префабы `Coin.prefab` и `Coin_Robotail.prefab` перемещены в `Prefabs/Props/Collectibles/`; добавлены `LeafCollectible.prefab` и `BatCollectible.prefab`.
592 lines
22 KiB
C#
592 lines
22 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using RR.Common.Entities.Props;
|
||
using RR.Game2D.Core.Managers;
|
||
using UnityEngine;
|
||
|
||
namespace RR.Game2D.Behaviour
|
||
{
|
||
public sealed class EnvironmentalCollectibleSpawner : MonoBehaviour
|
||
{
|
||
[Serializable]
|
||
public struct SpawnEntry
|
||
{
|
||
public EnvironmentalCollectibleBase prefab;
|
||
public int weight;
|
||
public bool overridePoints;
|
||
public int pointsOverride;
|
||
public bool overrideLifetime;
|
||
[Tooltip("Время жизни в секундах. 0 или меньше — бесконечно.")]
|
||
public float lifetimeOverride;
|
||
public int limit;
|
||
}
|
||
|
||
[Serializable]
|
||
public struct NamedSpawnEntry
|
||
{
|
||
[Tooltip("Имя, по которому триггер выбирает этот entry (например \"leaves\", \"bats\").")]
|
||
public string key;
|
||
public SpawnEntry entry;
|
||
[Tooltip("Мгновенно убрать все чужие объекты при активации.")]
|
||
public bool hardReplace;
|
||
}
|
||
|
||
[Header("Spawn Count")]
|
||
[SerializeField] private int fixedCount = 8;
|
||
[SerializeField] private int maxSpawnAttempts = 12;
|
||
[Tooltip("Задержка между спауном каждого объекта (секунды). 0 — все сразу.")]
|
||
[SerializeField] private Vector2 spawnDelay = new(0.15f, 0.4f);
|
||
|
||
[Header("Spawn Area")]
|
||
[SerializeField] private BoxCollider2D spawnArea;
|
||
[SerializeField] private Vector2 areaCenter;
|
||
[SerializeField] private Vector2 areaSize = new(8f, 4f);
|
||
|
||
[Header("Random Scale & Rotation")]
|
||
[Tooltip("Минимальный и максимальный uniform-масштаб при спауне.")]
|
||
[SerializeField] private Vector2 scaleRange = new(0.8f, 1.2f);
|
||
[Tooltip("Минимальный и максимальный угол поворота по Z (градусы) при спауне.")]
|
||
[SerializeField] private Vector2 rotationRange = new(0f, 0f);
|
||
|
||
[Header("Player")]
|
||
[SerializeField] private float minDistanceToPlayer = 1.5f;
|
||
[SerializeField] private float maxDistanceToPlayer = 25f;
|
||
|
||
private Transform _player;
|
||
|
||
[Header("Prefabs")]
|
||
[SerializeField] private SpawnEntry[] spawnEntries;
|
||
|
||
[Header("Trigger Entries")]
|
||
[Tooltip("Именованные SpawnEntry для вызова из триггеров. Используйте SetForcedEntryByName(string) в UnityEvent.")]
|
||
[SerializeField] private NamedSpawnEntry[] namedEntries;
|
||
|
||
private readonly HashSet<EnvironmentalCollectibleBase> _activeCollectibles = new();
|
||
private readonly Dictionary<EnvironmentalCollectibleBase, GameObject> _prefabByCollectible = new();
|
||
private readonly Dictionary<GameObject, int> _activeCountByPrefab = new();
|
||
private readonly List<EnvironmentalCollectibleBase> _refreshBuffer = new();
|
||
|
||
private GameObject _forcedPrefab;
|
||
private SpawnEntry? _forcedEntry;
|
||
private BoxCollider2D _overrideArea;
|
||
private Coroutine _spawnCoroutine;
|
||
private Coroutine _gradualReplaceCoroutine;
|
||
|
||
private void Start()
|
||
{
|
||
ResolvePlayer();
|
||
if (GameManager.Instance != null)
|
||
GameManager.Instance.PlayerSpawned += OnPlayerSpawned;
|
||
MaintainFixedCount();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
foreach (EnvironmentalCollectibleBase collectible in _activeCollectibles)
|
||
{
|
||
if (collectible != null)
|
||
collectible.Collected -= HandleCollected;
|
||
}
|
||
|
||
if (GameManager.Instance != null)
|
||
GameManager.Instance.PlayerSpawned -= OnPlayerSpawned;
|
||
}
|
||
|
||
#region Public API — смена типа спауна
|
||
|
||
/// <summary>
|
||
/// Мягко переключает спаунер на prefab (компонент). Старые объекты доживают.
|
||
/// </summary>
|
||
public void SetForcedPrefab(EnvironmentalCollectibleBase prefab)
|
||
{
|
||
ApplyForced(prefab != null ? prefab.gameObject : null, null, false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Жёстко переключает на prefab (компонент). Все «чужие» объекты убираются сразу.
|
||
/// </summary>
|
||
public void SetForcedPrefabHard(EnvironmentalCollectibleBase prefab)
|
||
{
|
||
ApplyForced(prefab != null ? prefab.gameObject : null, null, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Мягко переключает спаунер на prefab (GameObject).
|
||
/// Удобно вызывать из UnityEvent — просто перетащить prefab в инспекторе.
|
||
/// </summary>
|
||
public void SetForcedPrefabByObject(GameObject prefabObject)
|
||
{
|
||
ApplyForced(prefabObject, null, false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Жёстко переключает на prefab (GameObject). Все «чужие» объекты убираются сразу.
|
||
/// </summary>
|
||
public void SetForcedPrefabByObjectHard(GameObject prefabObject)
|
||
{
|
||
ApplyForced(prefabObject, null, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Мягко переключает на prefab по индексу в массиве spawnEntries.
|
||
/// </summary>
|
||
public void SetForcedPrefabByIndex(int index)
|
||
{
|
||
if (!TryGetEntry(index, out SpawnEntry entry)) return;
|
||
ApplyForced(entry.prefab.gameObject, entry, false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Жёстко переключает на prefab по индексу — все «чужие» убираются сразу.
|
||
/// </summary>
|
||
public void SetForcedPrefabHardByIndex(int index)
|
||
{
|
||
if (!TryGetEntry(index, out SpawnEntry entry)) return;
|
||
ApplyForced(entry.prefab.gameObject, entry, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Переключает спаунер на полностью кастомный SpawnEntry (из кода).
|
||
/// </summary>
|
||
public void SetForcedEntry(SpawnEntry entry, bool hardReplace = false)
|
||
{
|
||
if (entry.prefab == null) return;
|
||
ApplyForced(entry.prefab.gameObject, entry, hardReplace);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Переключает спаунер по строковому ключу из массива namedEntries.
|
||
/// Вызывайте из UnityEvent (EventTrigger) — Unity покажет поле ввода строки.
|
||
/// </summary>
|
||
public void SetForcedEntryByName(string key)
|
||
{
|
||
if (namedEntries == null || string.IsNullOrEmpty(key)) return;
|
||
|
||
for (int i = 0; i < namedEntries.Length; i++)
|
||
{
|
||
if (string.Equals(namedEntries[i].key, key, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
NamedSpawnEntry named = namedEntries[i];
|
||
if (named.entry.prefab == null) return;
|
||
ApplyForced(named.entry.prefab.gameObject, named.entry, named.hardReplace);
|
||
return;
|
||
}
|
||
}
|
||
|
||
Debug.LogWarning($"[{nameof(EnvironmentalCollectibleSpawner)}] Named entry '{key}' not found.", this);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Мягко переключает тип спауна по ключу из namedEntries.
|
||
/// Существующие объекты другого типа убираются постепенно (по одному с задержкой)
|
||
/// и заменяются новыми.
|
||
/// </summary>
|
||
public void SetEntryByName(string key)
|
||
{
|
||
if (namedEntries == null || string.IsNullOrEmpty(key)) return;
|
||
|
||
for (int i = 0; i < namedEntries.Length; i++)
|
||
{
|
||
if (string.Equals(namedEntries[i].key, key, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
NamedSpawnEntry named = namedEntries[i];
|
||
if (named.entry.prefab == null) return;
|
||
|
||
_forcedPrefab = named.entry.prefab.gameObject;
|
||
_forcedEntry = named.entry;
|
||
|
||
StartGradualReplace();
|
||
return;
|
||
}
|
||
}
|
||
|
||
Debug.LogWarning($"[{nameof(EnvironmentalCollectibleSpawner)}] Named entry '{key}' not found.", this);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возвращает спаунер в смешанный режим (все записи из spawnEntries).
|
||
/// </summary>
|
||
public void ClearForcedPrefab()
|
||
{
|
||
_forcedPrefab = null;
|
||
_forcedEntry = null;
|
||
MaintainFixedCount();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Временно подменяет область спауна (например, из триггер-зоны).
|
||
/// Передайте null чтобы вернуть оригинальную область.
|
||
/// </summary>
|
||
public void SetOverrideArea(BoxCollider2D area)
|
||
{
|
||
_overrideArea = area;
|
||
}
|
||
|
||
#endregion
|
||
|
||
private void ApplyForced(GameObject prefabObj, SpawnEntry? entry, bool hardReplace)
|
||
{
|
||
_forcedPrefab = prefabObj;
|
||
_forcedEntry = entry;
|
||
|
||
RefreshActiveCollectibles();
|
||
MaintainFixedCount();
|
||
}
|
||
|
||
private bool TryGetEntry(int index, out SpawnEntry entry)
|
||
{
|
||
entry = default;
|
||
if (spawnEntries == null || index < 0 || index >= spawnEntries.Length)
|
||
return false;
|
||
|
||
entry = spawnEntries[index];
|
||
return entry.prefab != null;
|
||
}
|
||
|
||
private void MaintainFixedCount()
|
||
{
|
||
if (_spawnCoroutine != null)
|
||
StopCoroutine(_spawnCoroutine);
|
||
|
||
if (!gameObject.activeInHierarchy) return;
|
||
|
||
_spawnCoroutine = StartCoroutine(SpawnRoutine());
|
||
}
|
||
|
||
private IEnumerator SpawnRoutine()
|
||
{
|
||
int safety = fixedCount * Mathf.Max(1, maxSpawnAttempts);
|
||
bool isFirst = true;
|
||
|
||
while (_activeCollectibles.Count < fixedCount && safety > 0)
|
||
{
|
||
// Задержка перед каждым спауном (кроме первого — чтобы был хоть один сразу)
|
||
if (!isFirst && spawnDelay.y > 0f)
|
||
yield return new WaitForSeconds(UnityEngine.Random.Range(spawnDelay.x, spawnDelay.y));
|
||
|
||
isFirst = false;
|
||
|
||
if (!TrySpawnOne())
|
||
break;
|
||
|
||
safety--;
|
||
}
|
||
|
||
_spawnCoroutine = null;
|
||
}
|
||
|
||
private void StartGradualReplace()
|
||
{
|
||
if (_gradualReplaceCoroutine != null)
|
||
StopCoroutine(_gradualReplaceCoroutine);
|
||
|
||
if (!gameObject.activeInHierarchy) return;
|
||
|
||
_gradualReplaceCoroutine = StartCoroutine(GradualReplaceRoutine());
|
||
}
|
||
|
||
private IEnumerator GradualReplaceRoutine()
|
||
{
|
||
if (_forcedPrefab == null)
|
||
{
|
||
_gradualReplaceCoroutine = null;
|
||
yield break;
|
||
}
|
||
|
||
string forcedName = _forcedPrefab.name;
|
||
|
||
while (true)
|
||
{
|
||
EnvironmentalCollectibleBase toRemove = null;
|
||
foreach (EnvironmentalCollectibleBase c in _activeCollectibles)
|
||
{
|
||
if (c == null) continue;
|
||
if (_prefabByCollectible.TryGetValue(c, out GameObject p) && p.name != forcedName)
|
||
{
|
||
toRemove = c;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (toRemove == null) break;
|
||
|
||
DespawnCollectible(toRemove);
|
||
MaintainFixedCount();
|
||
|
||
if (spawnDelay.y > 0f)
|
||
yield return new WaitForSeconds(UnityEngine.Random.Range(spawnDelay.x, spawnDelay.y));
|
||
else
|
||
yield return null;
|
||
}
|
||
|
||
_gradualReplaceCoroutine = null;
|
||
}
|
||
|
||
private bool TrySpawnOne()
|
||
{
|
||
ResolvePlayer();
|
||
|
||
if (!TryPickSpawnEntry(out SpawnEntry entry))
|
||
return false;
|
||
|
||
if (!TryFindSpawnPosition(out Vector3 spawnPosition))
|
||
return false;
|
||
|
||
GameObject prefab = entry.prefab.gameObject;
|
||
GameObject instance = Instantiate(prefab, spawnPosition, Quaternion.identity, transform);
|
||
|
||
if (instance == null) return false;
|
||
|
||
// Случайный масштаб и поворот
|
||
float scale = UnityEngine.Random.Range(scaleRange.x, scaleRange.y);
|
||
instance.transform.localScale = Vector3.one * scale;
|
||
|
||
float angle = UnityEngine.Random.Range(rotationRange.x, rotationRange.y);
|
||
instance.transform.rotation = Quaternion.Euler(0f, 0f, angle);
|
||
|
||
EnvironmentalCollectibleBase collectible = instance.GetComponent<EnvironmentalCollectibleBase>();
|
||
if (collectible == null)
|
||
{
|
||
Debug.LogError($"[{nameof(EnvironmentalCollectibleSpawner)}] Prefab {prefab.name} is missing EnvironmentalCollectibleBase.", prefab);
|
||
Destroy(instance);
|
||
return false;
|
||
}
|
||
|
||
collectible.Collected -= HandleCollected;
|
||
collectible.Collected += HandleCollected;
|
||
collectible.InitializeForSpawn(
|
||
spawnPosition,
|
||
entry.overridePoints ? entry.pointsOverride : null,
|
||
entry.overrideLifetime ? entry.lifetimeOverride : null);
|
||
|
||
_activeCollectibles.Add(collectible);
|
||
_prefabByCollectible[collectible] = prefab;
|
||
|
||
if (_activeCountByPrefab.TryGetValue(prefab, out int currentCount))
|
||
_activeCountByPrefab[prefab] = currentCount + 1;
|
||
else
|
||
_activeCountByPrefab[prefab] = 1;
|
||
|
||
return true;
|
||
}
|
||
|
||
private void HandleCollected(EnvironmentalCollectibleBase collectible)
|
||
{
|
||
if (collectible == null) return;
|
||
|
||
collectible.Collected -= HandleCollected;
|
||
|
||
if (_prefabByCollectible.TryGetValue(collectible, out GameObject prefab) &&
|
||
_activeCountByPrefab.TryGetValue(prefab, out int currentCount))
|
||
{
|
||
_activeCountByPrefab[prefab] = Mathf.Max(0, currentCount - 1);
|
||
}
|
||
|
||
_activeCollectibles.Remove(collectible);
|
||
_prefabByCollectible.Remove(collectible);
|
||
|
||
MaintainFixedCount();
|
||
}
|
||
|
||
private bool TryPickSpawnEntry(out SpawnEntry entry)
|
||
{
|
||
// Если задан полный SpawnEntry — используем его напрямую
|
||
if (_forcedEntry.HasValue)
|
||
{
|
||
SpawnEntry forced = _forcedEntry.Value;
|
||
if (forced.prefab == null)
|
||
{
|
||
entry = default;
|
||
return false;
|
||
}
|
||
|
||
// Проверяем лимит
|
||
if (forced.limit > 0 &&
|
||
_activeCountByPrefab.TryGetValue(forced.prefab.gameObject, out int fc) &&
|
||
fc >= forced.limit)
|
||
{
|
||
entry = default;
|
||
return false;
|
||
}
|
||
|
||
entry = forced;
|
||
return true;
|
||
}
|
||
|
||
entry = default;
|
||
if (spawnEntries == null || spawnEntries.Length == 0)
|
||
return false;
|
||
|
||
string forcedName = _forcedPrefab != null ? _forcedPrefab.name : null;
|
||
|
||
int totalWeight = 0;
|
||
for (int i = 0; i < spawnEntries.Length; i++)
|
||
{
|
||
SpawnEntry current = spawnEntries[i];
|
||
if (current.prefab == null || current.weight <= 0) continue;
|
||
|
||
GameObject prefab = current.prefab.gameObject;
|
||
if (forcedName != null && prefab.name != forcedName) continue;
|
||
|
||
if (current.limit > 0 &&
|
||
_activeCountByPrefab.TryGetValue(prefab, out int activeCount) &&
|
||
activeCount >= current.limit)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
totalWeight += current.weight;
|
||
}
|
||
|
||
// Фильтрация по _forcedPrefab — если prefab не найден в spawnEntries, создаём временный entry
|
||
if (totalWeight <= 0 && _forcedPrefab != null)
|
||
{
|
||
EnvironmentalCollectibleBase comp = _forcedPrefab.GetComponent<EnvironmentalCollectibleBase>();
|
||
if (comp != null)
|
||
{
|
||
entry = new SpawnEntry { prefab = comp, weight = 1 };
|
||
return true;
|
||
}
|
||
}
|
||
|
||
if (totalWeight <= 0)
|
||
return false;
|
||
|
||
int pick = UnityEngine.Random.Range(0, totalWeight);
|
||
for (int i = 0; i < spawnEntries.Length; i++)
|
||
{
|
||
SpawnEntry current = spawnEntries[i];
|
||
if (current.prefab == null || current.weight <= 0) continue;
|
||
|
||
GameObject prefab = current.prefab.gameObject;
|
||
if (forcedName != null && prefab.name != forcedName) continue;
|
||
|
||
if (current.limit > 0 &&
|
||
_activeCountByPrefab.TryGetValue(prefab, out int activeCount) &&
|
||
activeCount >= current.limit)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
pick -= current.weight;
|
||
if (pick < 0)
|
||
{
|
||
entry = current;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool TryFindSpawnPosition(out Vector3 spawnPosition)
|
||
{
|
||
Bounds bounds = GetSpawnBounds();
|
||
|
||
for (int i = 0; i < maxSpawnAttempts; i++)
|
||
{
|
||
float x = UnityEngine.Random.Range(bounds.min.x, bounds.max.x);
|
||
float y = UnityEngine.Random.Range(bounds.min.y, bounds.max.y);
|
||
spawnPosition = new Vector3(x, y, 0f);
|
||
|
||
if (_player == null)
|
||
return true;
|
||
|
||
float distance = Vector2.Distance(_player.position, spawnPosition);
|
||
if (distance < minDistanceToPlayer) continue;
|
||
if (maxDistanceToPlayer > 0f && distance > maxDistanceToPlayer) continue;
|
||
|
||
return true;
|
||
}
|
||
|
||
spawnPosition = transform.position;
|
||
return false;
|
||
}
|
||
|
||
private Bounds GetSpawnBounds()
|
||
{
|
||
if (_overrideArea != null)
|
||
return _overrideArea.bounds;
|
||
|
||
if (spawnArea != null)
|
||
return spawnArea.bounds;
|
||
|
||
Vector3 center = transform.TransformPoint(areaCenter);
|
||
Vector3 size = new(areaSize.x, areaSize.y, 0f);
|
||
return new Bounds(center, size);
|
||
}
|
||
|
||
private void ResolvePlayer()
|
||
{
|
||
if (GameManager.Instance?.PlayerObj != null)
|
||
{
|
||
_player = GameManager.Instance.PlayerObj.transform;
|
||
return;
|
||
}
|
||
|
||
// Fallback на случай, если GameManager ещё не проинициализирован
|
||
GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
|
||
if (playerObject != null)
|
||
_player = playerObject.transform;
|
||
}
|
||
|
||
private void OnPlayerSpawned(GameObject playerObj)
|
||
{
|
||
_player = playerObj != null ? playerObj.transform : null;
|
||
}
|
||
|
||
|
||
private void RefreshActiveCollectibles()
|
||
{
|
||
if (_forcedPrefab == null)
|
||
return;
|
||
|
||
string forcedName = _forcedPrefab.name;
|
||
|
||
_refreshBuffer.Clear();
|
||
foreach (EnvironmentalCollectibleBase collectible in _activeCollectibles)
|
||
{
|
||
if (collectible == null) continue;
|
||
|
||
if (_prefabByCollectible.TryGetValue(collectible, out GameObject prefab) && prefab.name != forcedName)
|
||
_refreshBuffer.Add(collectible);
|
||
}
|
||
|
||
for (int i = 0; i < _refreshBuffer.Count; i++)
|
||
{
|
||
DespawnCollectible(_refreshBuffer[i]);
|
||
}
|
||
|
||
_refreshBuffer.Clear();
|
||
}
|
||
|
||
private void DespawnCollectible(EnvironmentalCollectibleBase collectible)
|
||
{
|
||
if (collectible == null) return;
|
||
|
||
collectible.Collected -= HandleCollected;
|
||
|
||
if (_prefabByCollectible.TryGetValue(collectible, out GameObject prefab))
|
||
{
|
||
if (_activeCountByPrefab.TryGetValue(prefab, out int currentCount))
|
||
_activeCountByPrefab[prefab] = Mathf.Max(0, currentCount - 1);
|
||
}
|
||
|
||
_activeCollectibles.Remove(collectible);
|
||
_prefabByCollectible.Remove(collectible);
|
||
|
||
Destroy(collectible.gameObject);
|
||
}
|
||
|
||
private void OnDrawGizmosSelected()
|
||
{
|
||
Bounds bounds = GetSpawnBounds();
|
||
Gizmos.color = new Color(0.15f, 0.8f, 0.25f, 0.35f);
|
||
Gizmos.DrawCube(bounds.center, bounds.size);
|
||
Gizmos.color = new Color(0.15f, 0.8f, 0.25f, 1f);
|
||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||
}
|
||
}
|
||
}
|