chunk 1: core gameplay scripts scenes runtime assets

This commit is contained in:
2026-04-06 11:02:34 +03:00
parent fa0388bc79
commit 0d11a097d8
703 changed files with 2292651 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
using UnityEngine;
using System.Diagnostics;
/// <summary>
/// DevLog — Drop-in Debug.Log replacement.
/// All Log/LogWarning calls are compiled out in non-editor, non-development release builds
/// via [Conditional] attributes, eliminating ALL string allocation from logging.
///
/// Usage: Replace Debug.Log("msg") with DevLog.Log("msg")
/// Replace Debug.LogWarning("msg") with DevLog.LogWarning("msg")
///
/// Debug.LogError is NOT wrapped — errors should always log for crash reporting.
///
/// OPTIMIZATION IMPACT:
/// Before: 300+ Debug.Log calls creating string garbage every invocation
/// After: Zero string allocation in release builds (calls compiled out entirely)
///
/// RISK: Low — purely additive utility class. No existing code is changed by its existence.
/// ROLLBACK: Delete this file. Revert DevLog.Log → Debug.Log in any changed files.
/// </summary>
public static class DevLog
{
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void Log(string message)
{
UnityEngine.Debug.Log(message);
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void Log(string message, Object context)
{
UnityEngine.Debug.Log(message, context);
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void LogWarning(string message)
{
UnityEngine.Debug.LogWarning(message);
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void LogWarning(string message, Object context)
{
UnityEngine.Debug.LogWarning(message, context);
}
// LogError is intentionally NOT conditional — errors must always reach crash reporting
public static void LogError(string message)
{
UnityEngine.Debug.LogError(message);
}
public static void LogError(string message, Object context)
{
UnityEngine.Debug.LogError(message, context);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0c5db3447f63c7fcbab247b7035ae336

View File

@@ -0,0 +1,89 @@
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// EntityRegistry — Zero-allocation replacement for FindObjectsOfType.
/// Each MonoBehaviour type auto-registers in OnEnable/OnDisable via EntityRegistryMember<T>.
/// Access all instances via EntityRegistry<T>.All (read-only list, no allocation).
///
/// Usage:
/// 1) Add EntityRegistryMember<CashBag> component, or
/// 2) Call EntityRegistry<CashBag>.Register(this) in OnEnable / Unregister in OnDisable
/// 3) Query: foreach (var bag in EntityRegistry<CashBag>.All) { ... }
/// </summary>
public static class EntityRegistry<T> where T : class
{
private static readonly List<T> _instances = new List<T>(32);
private static readonly List<T> _readOnly = _instances; // Same ref, cast prevents mutation at call sites
/// <summary>All currently active instances. Do NOT modify this list.</summary>
public static List<T> All => _readOnly;
/// <summary>Alias for All — convenience method matching GetAll() convention.</summary>
public static List<T> GetAll() => _readOnly;
/// <summary>Number of active instances.</summary>
public static int Count => _instances.Count;
public static void Register(T instance)
{
if (instance != null && !_instances.Contains(instance))
_instances.Add(instance);
}
public static void Unregister(T instance)
{
_instances.Remove(instance);
}
/// <summary>
/// Find nearest instance to a world position.
/// Returns null if no instances. O(n) but n is small (≤10 for bags/characters).
/// </summary>
public static T FindNearest(Vector3 position, System.Func<T, Vector3> getPosition, System.Func<T, bool> filter = null)
{
T nearest = null;
float nearestSqrDist = float.MaxValue;
for (int i = 0; i < _instances.Count; i++)
{
T instance = _instances[i];
if (instance == null) continue;
if (filter != null && !filter(instance)) continue;
float sqrDist = (getPosition(instance) - position).sqrMagnitude;
if (sqrDist < nearestSqrDist)
{
nearestSqrDist = sqrDist;
nearest = instance;
}
}
return nearest;
}
/// <summary>
/// Find all instances within radius. Uses pre-allocated result list to avoid allocation.
/// Caller should process results immediately (list is reused).
/// </summary>
private static readonly List<T> _withinRadiusResults = new List<T>(16);
public static List<T> FindWithinRadius(Vector3 position, float radius, System.Func<T, Vector3> getPosition, System.Func<T, bool> filter = null)
{
_withinRadiusResults.Clear();
float sqrRadius = radius * radius;
for (int i = 0; i < _instances.Count; i++)
{
T instance = _instances[i];
if (instance == null) continue;
if (filter != null && !filter(instance)) continue;
float sqrDist = (getPosition(instance) - position).sqrMagnitude;
if (sqrDist <= sqrRadius)
_withinRadiusResults.Add(instance);
}
return _withinRadiusResults;
}
/// <summary>Call on scene unload to prevent stale references.</summary>
public static void Clear() => _instances.Clear();
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6e4f1892016f9c8cb85e8bfd60af9888

View File

@@ -0,0 +1,195 @@
using UnityEngine;
using System;
/// <summary>
/// GameEvents — Static event bus replacing all FindObjectOfType communication.
/// Zero-allocation pub/sub pattern. All events are static Actions.
/// Subscribe in OnEnable, unsubscribe in OnDisable.
/// </summary>
public static class GameEvents
{
// ─── Match Flow ───────────────────────────────────────────
public static event Action OnMatchStart;
public static event Action<string> OnMatchEnd; // winning teamTag
public static event Action<bool> OnMatchResult; // true = player won
public static event Action<float> OnTimerUpdated; // remaining seconds
public static event Action<string> OnCountdownTick; // "3", "2", "1", "FIGHT!"
public static event Action<bool> OnCombatEnabled; // true = enabled
public static event Action OnOvertimeStart;
// ─── Cash Bag Events ──────────────────────────────────────
public static event Action<CashBag> OnBagSpawned;
public static event Action<CashBag> OnBagCollected; // bag was picked up
public static event Action<CashBag, CashCarrier> OnBagPickedUp; // who picked it
public static event Action<CashBag, Vector3> OnBagDropped; // bag + drop position
public static event Action<CashBag> OnBagDespawned;
public static event Action<int> OnActiveBagCountChanged; // current active count
// ─── Scoring Events ──────────────────────────────────────
public static event Action<string, float, int> OnCashDeposited; // teamTag, value, comboMultiplier
public static event Action<string, float> OnVaultUpdated; // teamTag, newTotal
public static event Action<string, float> OnScoreChanged; // teamTag, totalScore
// ─── Combat Events ───────────────────────────────────────
public static event Action<GameObject, GameObject> OnKill; // killer, victim
public static event Action<GameObject, GameObject, float> OnDamageDealt; // attacker, victim, amount
public static event Action<CashCarrier> OnCarrierKilled; // carrier that was killed
public static event Action<GameObject, int> OnCarrierKillBonus; // killer, bonus points
// ─── Vault Events ────────────────────────────────────────
public static event Action<TeamVault> OnVaultUnderAttack;
public static event Action<TeamVault> OnVaultDefended;
public static event Action<TeamVault, float> OnVaultStolen; // vault, amount stolen
// ─── Extraction Events ───────────────────────────────────
public static event Action<ExtractionVault> OnExtractionVaultSpawned; // vault appeared in world
public static event Action<ExtractionVault, GameObject> OnVaultOpened; // vault, opener — $1,000 instant reward
public static event Action<ExtractionVault, GameObject> OnVaultPickedUp;// vault, carrier — starts encumbrance
public static event Action<ExtractionVault, Vector3> OnVaultDropped; // vault, drop position
public static event Action<CashoutStation, string, float> OnCashoutStarted; // station, teamTag, instantPayout (30%)
public static event Action<CashoutStation, float> OnCashoutProgress; // station, progress 0-1
public static event Action<CashoutStation, string, float> OnCashoutCompleted;// station, teamTag, totalPayout
public static event Action<CashoutStation, string> OnCashoutContested; // station, contestingTeamTag
public static event Action<CashoutStation, string, float> OnCashoutHacked; // station, hackingTeamTag, stolenAmount
public static event Action<string> OnTeamWipe; // wiped teamTag — triggers -30% penalty
public static event Action<GameObject, float> OnKillReward; // killer, reward amount ($200)
public static event Action<GameObject, float> OnRespawnTimerUpdate; // character, remainingSeconds
// ─── Character Events ────────────────────────────────────
public static event Action<GameObject> OnCharacterSpawned;
public static event Action<GameObject> OnCharacterDied;
public static event Action<GameObject> OnCharacterRespawned;
public static event Action<GameObject, GameObject> OnCharacterSwitched; // old, new
// ─── AI Events ───────────────────────────────────────────
public static event Action<AIBrain, AIAction> OnAIActionChanged;
public static event Action<string> OnTeamPing; // "Attack!", "Defend!", etc.
// ─── UI Events ───────────────────────────────────────────
public static event Action<string, string, string> OnKillFeedEntry; // killer, action, victim
public static event Action<string, float> OnPopupMessage; // message, duration
public static event Action<int> OnComboMultiplier; // multiplier value
// ─── Fire Methods (static, no allocation) ────────────────
// Match Flow
public static void FireMatchStart() => OnMatchStart?.Invoke();
public static void FireMatchEnd(string winnerTag) => OnMatchEnd?.Invoke(winnerTag);
public static void FireMatchResult(bool playerWon) => OnMatchResult?.Invoke(playerWon);
public static void FireTimerUpdated(float remaining) => OnTimerUpdated?.Invoke(remaining);
public static void FireCountdownTick(string text) => OnCountdownTick?.Invoke(text);
public static void FireCombatEnabled(bool enabled) => OnCombatEnabled?.Invoke(enabled);
public static void FireOvertimeStart() => OnOvertimeStart?.Invoke();
// Cash Bags
public static void FireBagSpawned(CashBag bag) => OnBagSpawned?.Invoke(bag);
public static void FireBagCollected(CashBag bag) => OnBagCollected?.Invoke(bag);
public static void FireBagPickedUp(CashBag bag, CashCarrier carrier) => OnBagPickedUp?.Invoke(bag, carrier);
public static void FireBagDropped(CashBag bag, Vector3 pos) => OnBagDropped?.Invoke(bag, pos);
public static void FireBagDespawned(CashBag bag) => OnBagDespawned?.Invoke(bag);
public static void FireActiveBagCountChanged(int count) => OnActiveBagCountChanged?.Invoke(count);
// Scoring
public static void FireCashDeposited(string teamTag, float value, int combo) => OnCashDeposited?.Invoke(teamTag, value, combo);
public static void FireVaultUpdated(string teamTag, float total) => OnVaultUpdated?.Invoke(teamTag, total);
public static void FireScoreChanged(string teamTag, float total) => OnScoreChanged?.Invoke(teamTag, total);
// Combat
public static void FireKill(GameObject killer, GameObject victim) => OnKill?.Invoke(killer, victim);
public static void FireDamageDealt(GameObject attacker, GameObject victim, float amount) => OnDamageDealt?.Invoke(attacker, victim, amount);
public static void FireCarrierKilled(CashCarrier carrier) => OnCarrierKilled?.Invoke(carrier);
public static void FireCarrierKillBonus(GameObject killer, int bonus) => OnCarrierKillBonus?.Invoke(killer, bonus);
// Vault
public static void FireVaultUnderAttack(TeamVault vault) => OnVaultUnderAttack?.Invoke(vault);
public static void FireVaultDefended(TeamVault vault) => OnVaultDefended?.Invoke(vault);
public static void FireVaultStolen(TeamVault vault, float amount) => OnVaultStolen?.Invoke(vault, amount);
// Extraction
public static void FireExtractionVaultSpawned(ExtractionVault vault) => OnExtractionVaultSpawned?.Invoke(vault);
public static void FireVaultOpened(ExtractionVault vault, GameObject opener) => OnVaultOpened?.Invoke(vault, opener);
public static void FireVaultPickedUp(ExtractionVault vault, GameObject carrier) => OnVaultPickedUp?.Invoke(vault, carrier);
public static void FireVaultDropped(ExtractionVault vault, Vector3 pos) => OnVaultDropped?.Invoke(vault, pos);
public static void FireCashoutStarted(CashoutStation station, string teamTag, float payout) => OnCashoutStarted?.Invoke(station, teamTag, payout);
public static void FireCashoutProgress(CashoutStation station, float progress) => OnCashoutProgress?.Invoke(station, progress);
public static void FireCashoutCompleted(CashoutStation station, string teamTag, float total) => OnCashoutCompleted?.Invoke(station, teamTag, total);
public static void FireCashoutContested(CashoutStation station, string teamTag) => OnCashoutContested?.Invoke(station, teamTag);
public static void FireCashoutHacked(CashoutStation station, string teamTag, float amount) => OnCashoutHacked?.Invoke(station, teamTag, amount);
public static void FireTeamWipe(string teamTag) => OnTeamWipe?.Invoke(teamTag);
public static void FireKillReward(GameObject killer, float reward) => OnKillReward?.Invoke(killer, reward);
public static void FireRespawnTimerUpdate(GameObject character, float remaining) => OnRespawnTimerUpdate?.Invoke(character, remaining);
// Characters
public static void FireCharacterSpawned(GameObject character) => OnCharacterSpawned?.Invoke(character);
public static void FireCharacterDied(GameObject character) => OnCharacterDied?.Invoke(character);
public static void FireCharacterRespawned(GameObject character) => OnCharacterRespawned?.Invoke(character);
public static void FireCharacterSwitched(GameObject oldChar, GameObject newChar) => OnCharacterSwitched?.Invoke(oldChar, newChar);
// AI
public static void FireAIActionChanged(AIBrain brain, AIAction action) => OnAIActionChanged?.Invoke(brain, action);
public static void FireTeamPing(string message) => OnTeamPing?.Invoke(message);
// UI
public static void FireKillFeedEntry(string killer, string action, string victim) => OnKillFeedEntry?.Invoke(killer, action, victim);
public static void FirePopupMessage(string message, float duration) => OnPopupMessage?.Invoke(message, duration);
public static void FireComboMultiplier(int multiplier) => OnComboMultiplier?.Invoke(multiplier);
/// <summary>
/// Call on scene unload to prevent stale references.
/// </summary>
public static void ClearAll()
{
OnMatchStart = null;
OnMatchEnd = null;
OnMatchResult = null;
OnTimerUpdated = null;
OnCountdownTick = null;
OnCombatEnabled = null;
OnOvertimeStart = null;
OnBagSpawned = null;
OnBagCollected = null;
OnBagPickedUp = null;
OnBagDropped = null;
OnBagDespawned = null;
OnActiveBagCountChanged = null;
OnCashDeposited = null;
OnVaultUpdated = null;
OnScoreChanged = null;
OnKill = null;
OnDamageDealt = null;
OnCarrierKilled = null;
OnCarrierKillBonus = null;
OnVaultUnderAttack = null;
OnVaultDefended = null;
OnVaultStolen = null;
OnExtractionVaultSpawned = null;
OnVaultOpened = null;
OnVaultPickedUp = null;
OnVaultDropped = null;
OnCashoutStarted = null;
OnCashoutProgress = null;
OnCashoutCompleted = null;
OnCashoutContested = null;
OnCashoutHacked = null;
OnTeamWipe = null;
OnKillReward = null;
OnRespawnTimerUpdate = null;
OnCharacterSpawned = null;
OnCharacterDied = null;
OnCharacterRespawned = null;
OnCharacterSwitched = null;
OnAIActionChanged = null;
OnTeamPing = null;
OnKillFeedEntry = null;
OnPopupMessage = null;
OnComboMultiplier = null;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: eb7212d36a725579b87e289d6aa7b3ec

View File

@@ -0,0 +1,302 @@
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// ObjectPool — Generic, lightweight object pool for mobile performance.
/// Eliminates Instantiate/Destroy overhead for frequently spawned objects
/// (VFX, money bags, projectiles, UI elements).
///
/// Usage:
/// ObjectPool pool = new ObjectPool(prefab, initialSize: 10);
/// GameObject obj = pool.Get(position, rotation);
/// pool.Return(obj); // or obj.GetComponent<PooledObject>().ReturnToPool();
/// </summary>
public class ObjectPool
{
private readonly GameObject _prefab;
private readonly Transform _container;
private readonly Queue<GameObject> _available;
private readonly HashSet<GameObject> _inUse;
private readonly int _maxSize;
private readonly bool _allowGrowth;
public int AvailableCount => _available.Count;
public int InUseCount => _inUse.Count;
public int TotalCount => _available.Count + _inUse.Count;
/// <summary>
/// Create a new object pool.
/// </summary>
/// <param name="prefab">Prefab to pool</param>
/// <param name="initialSize">Pre-warm count</param>
/// <param name="maxSize">Hard cap (0 = unlimited)</param>
/// <param name="parent">Optional parent transform for organization</param>
/// <param name="allowGrowth">Allow creating beyond initialSize if pool is empty</param>
public ObjectPool(GameObject prefab, int initialSize = 10, int maxSize = 0, Transform parent = null, bool allowGrowth = true)
{
_prefab = prefab;
_maxSize = maxSize;
_allowGrowth = allowGrowth;
_available = new Queue<GameObject>(initialSize);
_inUse = new HashSet<GameObject>();
// Create container for organization
if (parent == null)
{
GameObject containerObj = new GameObject($"Pool_{prefab.name}");
_container = containerObj.transform;
Object.DontDestroyOnLoad(containerObj);
}
else
{
_container = parent;
}
// Pre-warm
for (int i = 0; i < initialSize; i++)
{
GameObject obj = CreateNewInstance();
obj.SetActive(false);
_available.Enqueue(obj);
}
}
/// <summary>
/// Get an object from the pool. Activates it at the given position/rotation.
/// </summary>
public GameObject Get(Vector3 position, Quaternion rotation)
{
GameObject obj = null;
// Try to get from pool
while (_available.Count > 0)
{
obj = _available.Dequeue();
if (obj != null) break; // Found valid object
obj = null; // Was destroyed externally, skip
}
// Pool empty — create new if allowed
if (obj == null)
{
if (!_allowGrowth && _maxSize > 0 && TotalCount >= _maxSize)
return null; // Hard cap reached
obj = CreateNewInstance();
}
obj.transform.SetPositionAndRotation(position, rotation);
obj.SetActive(true);
_inUse.Add(obj);
// Notify pooled components
var pooled = obj.GetComponent<PooledObject>();
if (pooled != null) pooled.OnGetFromPool();
return obj;
}
/// <summary>
/// Return an object to the pool. Deactivates it.
/// </summary>
public void Return(GameObject obj)
{
if (obj == null) return;
_inUse.Remove(obj);
// Notify pooled components
var pooled = obj.GetComponent<PooledObject>();
if (pooled != null) pooled.OnReturnToPool();
obj.SetActive(false);
obj.transform.SetParent(_container);
_available.Enqueue(obj);
}
/// <summary>
/// Return all in-use objects to the pool.
/// </summary>
public void ReturnAll()
{
// Copy to avoid modification during iteration
var inUseList = new List<GameObject>(_inUse);
foreach (var obj in inUseList)
{
if (obj != null) Return(obj);
}
}
/// <summary>
/// Destroy all pooled objects and clear the pool.
/// </summary>
public void Dispose()
{
foreach (var obj in _available)
if (obj != null) Object.Destroy(obj);
foreach (var obj in _inUse)
if (obj != null) Object.Destroy(obj);
_available.Clear();
_inUse.Clear();
if (_container != null)
Object.Destroy(_container.gameObject);
}
private GameObject CreateNewInstance()
{
GameObject obj = Object.Instantiate(_prefab, _container);
obj.name = _prefab.name; // Remove "(Clone)" suffix
// Add PooledObject component if not present
var pooled = obj.GetComponent<PooledObject>();
if (pooled == null)
{
pooled = obj.AddComponent<PooledObject>();
}
pooled.OwnerPool = this;
return obj;
}
}
/// <summary>
/// PooledObject — Attach to pooled prefabs. Provides auto-return and lifecycle callbacks.
/// </summary>
public class PooledObject : MonoBehaviour
{
public ObjectPool OwnerPool { get; set; }
[Tooltip("Auto-return to pool after this many seconds (0 = manual return only)")]
[SerializeField] private float autoReturnDelay = 0f;
private float _activeTimer;
private Coroutine _autoReturnCoroutine;
/// <summary>Called when taken from pool.</summary>
public virtual void OnGetFromPool()
{
_activeTimer = 0f;
// Start coroutine-based auto-return instead of per-frame Update check
if (autoReturnDelay > 0f)
{
if (_autoReturnCoroutine != null) StopCoroutine(_autoReturnCoroutine);
_autoReturnCoroutine = StartCoroutine(AutoReturnAfterDelay());
}
}
/// <summary>Called when returned to pool.</summary>
public virtual void OnReturnToPool()
{
_activeTimer = 0f;
if (_autoReturnCoroutine != null)
{
StopCoroutine(_autoReturnCoroutine);
_autoReturnCoroutine = null;
}
}
/// <summary>Return this object to its pool.</summary>
public void ReturnToPool()
{
if (_autoReturnCoroutine != null)
{
StopCoroutine(_autoReturnCoroutine);
_autoReturnCoroutine = null;
}
if (OwnerPool != null)
OwnerPool.Return(gameObject);
else
gameObject.SetActive(false);
}
/// <summary>Coroutine-based auto-return — eliminates per-frame Update overhead.</summary>
private System.Collections.IEnumerator AutoReturnAfterDelay()
{
yield return new WaitForSeconds(autoReturnDelay);
ReturnToPool();
}
}
/// <summary>
/// PoolManager — Centralized pool registry. Access pools by prefab reference.
/// Singleton, persists across scenes.
/// </summary>
public class PoolManager : MonoBehaviour
{
private static PoolManager _instance;
public static PoolManager Instance
{
get
{
if (_instance == null)
{
var go = new GameObject("PoolManager");
_instance = go.AddComponent<PoolManager>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
private readonly Dictionary<GameObject, ObjectPool> _pools = new Dictionary<GameObject, ObjectPool>();
/// <summary>
/// Get or create a pool for a prefab.
/// </summary>
public ObjectPool GetPool(GameObject prefab, int initialSize = 10, int maxSize = 0)
{
if (_pools.TryGetValue(prefab, out ObjectPool pool))
return pool;
pool = new ObjectPool(prefab, initialSize, maxSize, transform);
_pools[prefab] = pool;
return pool;
}
/// <summary>
/// Shorthand: Get an object from a prefab's pool.
/// </summary>
public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, int poolSize = 10)
{
return GetPool(prefab, poolSize).Get(position, rotation);
}
/// <summary>
/// Shorthand: Return an object to its pool.
/// </summary>
public void Despawn(GameObject obj)
{
var pooled = obj.GetComponent<PooledObject>();
if (pooled != null && pooled.OwnerPool != null)
pooled.OwnerPool.Return(obj);
else
obj.SetActive(false);
}
/// <summary>
/// Pre-warm a pool for a prefab.
/// </summary>
public void PreWarm(GameObject prefab, int count)
{
GetPool(prefab, count);
}
/// <summary>
/// Clear all pools (call on scene transitions).
/// </summary>
public void ClearAll()
{
foreach (var pool in _pools.Values)
pool.Dispose();
_pools.Clear();
}
private void OnDestroy()
{
ClearAll();
_instance = null;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ddec7a02213d108668f26f56f71828a3