mirror of
https://github.com/Earth-Genesis-Games/Carrasco.git
synced 2026-09-11 09:52:19 +00:00
actualizacion simple de ui
This commit is contained in:
Vendored
+1
-1
@@ -56,5 +56,5 @@
|
||||
"temp/": true,
|
||||
"Temp/": true
|
||||
},
|
||||
"dotnet.defaultSolution": "Carrasco.sln"
|
||||
"dotnet.defaultSolution": "Carrasco.slnx"
|
||||
}
|
||||
@@ -2,415 +2,420 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[DefaultExecutionOrder(-5)]
|
||||
/// <summary>
|
||||
/// Spawner dinámico basado en LevelConfig.dynamic.
|
||||
/// - Spawnea aviones fuera de cámara (distancia en píxeles exacta).
|
||||
/// - Respeta límite simultáneo.
|
||||
/// - Soporta modo simple (intervalo fijo) y modo avanzado (legacy).
|
||||
/// </summary>
|
||||
public class DynamicSpawnerSystem : MonoBehaviour
|
||||
{
|
||||
[Header("Estado")]
|
||||
// ====================== RUNTIME STATE ======================
|
||||
[Header("Runtime debug")]
|
||||
[SerializeField] private bool running = false;
|
||||
[SerializeField] private bool usingCfg = false;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool verbose = false;
|
||||
[SerializeField] private float statusLogEvery = 10f;
|
||||
private LevelConfig _cfg; // config activa del nivel
|
||||
private float _levelStartTime; // Time.time cuando empezamos
|
||||
private float _lastSpawnTime = -999f; // última vez que instanciamos
|
||||
private int _spawnedSoFar = 0; // contador total (debug)
|
||||
|
||||
[Header("Spawn fuera de cámara")]
|
||||
[Tooltip("Distancia en PÍXELES por fuera del borde de la cámara donde aparece el avión.")]
|
||||
public int spawnMarginPixels = 100;
|
||||
private readonly List<DynamicSpawnToken> _activeTokens = new List<DynamicSpawnToken>();
|
||||
|
||||
// ----- Config cargada -----
|
||||
private LevelConfig _cfg;
|
||||
private LevelConfig.DynamicSettings _dyn;
|
||||
private List<LevelConfig.DynamicPool> _pools;
|
||||
private bool _pausedByTutorial = false; // si el tutorial inicial está activo
|
||||
private bool _didFirstForcedBlue = false; // para forceBlueFirstPlane
|
||||
|
||||
// ----- Conteo activo por tipo -----
|
||||
private int _activePlane = 0;
|
||||
private int _activeHeli = 0;
|
||||
|
||||
// tokens vivos
|
||||
private readonly HashSet<DynamicSpawnToken> _tokens = new HashSet<DynamicSpawnToken>();
|
||||
|
||||
// control de ritmo
|
||||
private float _nextSpawnAtRealtime = 0f;
|
||||
private float _lastStatusLog = -999f;
|
||||
|
||||
// ramp (dificultad, NO velocidad)
|
||||
private float _rampStartRealtime = 0f;
|
||||
private int _rampSteps = 0;
|
||||
|
||||
// ===== NUEVO: tiempo para normalizar curvas 0..1 =====
|
||||
private float _difficultyStartRealtime = 0f;
|
||||
|
||||
// pools cache
|
||||
private readonly List<GameObject> _planePrefabs = new List<GameObject>();
|
||||
private readonly List<float> _planeWeights = new List<float>();
|
||||
private readonly List<GameObject> _heliPrefabs = new List<GameObject>();
|
||||
private readonly List<float> _heliWeights = new List<float>();
|
||||
|
||||
// ===== NUEVO: mapear cada prefab a su pool para leer overrides =====
|
||||
private readonly List<LevelConfig.DynamicPool> _planePoolRef = new List<LevelConfig.DynamicPool>();
|
||||
private readonly List<LevelConfig.DynamicPool> _heliPoolRef = new List<LevelConfig.DynamicPool>();
|
||||
|
||||
private Coroutine _runner;
|
||||
|
||||
// =====================================================================
|
||||
// API
|
||||
// =====================================================================
|
||||
|
||||
public void StartWithLevel(LevelConfig cfg)
|
||||
// cache pools organizados por tipo
|
||||
private struct PoolEntry
|
||||
{
|
||||
StopAllSpawning();
|
||||
|
||||
_cfg = cfg;
|
||||
usingCfg = (_cfg != null);
|
||||
if (!usingCfg) { if (verbose) Debug.LogWarning("[DynamicSpawnerSystem] StartWithLevel sin LevelConfig."); return; }
|
||||
|
||||
_dyn = _cfg.dynamic;
|
||||
_pools = _cfg.dynamicPools ?? new List<LevelConfig.DynamicPool>();
|
||||
|
||||
BuildTypePools();
|
||||
|
||||
_activePlane = 0;
|
||||
_activeHeli = 0;
|
||||
_tokens.Clear();
|
||||
|
||||
_rampStartRealtime = Time.realtimeSinceStartup;
|
||||
_difficultyStartRealtime = _rampStartRealtime; // punto 0 para curvas 0..1
|
||||
_rampSteps = 0;
|
||||
|
||||
running = (_cfg.useDynamicSpawning && _dyn != null && _dyn.enabled);
|
||||
if (!running) { if (verbose) Debug.Log("[DynamicSpawnerSystem] Dynamic desactivado por config."); return; }
|
||||
|
||||
_nextSpawnAtRealtime = Time.realtimeSinceStartup + Mathf.Max(0f, _dyn.startDelay);
|
||||
_runner = StartCoroutine(CoRunRealtime());
|
||||
if (verbose) Debug.Log("[DynamicSpawnerSystem] Iniciado.");
|
||||
public PlaneType type;
|
||||
public GameObject prefab;
|
||||
public float weight;
|
||||
}
|
||||
private List<PoolEntry> _flatPool = new List<PoolEntry>();
|
||||
|
||||
public void StopStreaming() => running = false;
|
||||
|
||||
public void StopAllSpawning()
|
||||
// ====================== UNITY ======================
|
||||
private void OnEnable() { }
|
||||
private void OnDisable()
|
||||
{
|
||||
running = false;
|
||||
if (_runner != null) StopCoroutine(_runner);
|
||||
_runner = null;
|
||||
_tokens.Clear();
|
||||
_activeTokens.Clear();
|
||||
}
|
||||
|
||||
// Compat.
|
||||
private void Stop() => StopAllSpawning();
|
||||
// ====================== API PRINCIPAL ======================
|
||||
|
||||
// Callbacks desde DynamicSpawnToken
|
||||
public void OnTokenEnteredViewport(DynamicSpawnToken token) { /* opcional */ }
|
||||
|
||||
public void OnTokenDestroyed(DynamicSpawnToken token)
|
||||
/// <summary>
|
||||
/// Llamado por GameManager cuando carga un nuevo LevelConfig.
|
||||
/// </summary>
|
||||
public void StartWithLevel(LevelConfig cfg)
|
||||
{
|
||||
if (token == null) return;
|
||||
if (_tokens.Remove(token))
|
||||
if (cfg == null)
|
||||
{
|
||||
if (token.Type == PlaneType.Plane) _activePlane = Mathf.Max(0, _activePlane - 1);
|
||||
else if (token.Type == PlaneType.Helicopter) _activeHeli = Mathf.Max(0, _activeHeli - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// LOOP
|
||||
// =====================================================================
|
||||
|
||||
private IEnumerator CoRunRealtime()
|
||||
{
|
||||
var wait = new WaitForSecondsRealtime(0.05f);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!running || !usingCfg || _dyn == null)
|
||||
{
|
||||
yield return wait;
|
||||
continue;
|
||||
}
|
||||
|
||||
TryRampUp(); // solo ritmo/caps
|
||||
|
||||
if (verbose && Time.realtimeSinceStartup - _lastStatusLog >= statusLogEvery)
|
||||
{
|
||||
_lastStatusLog = Time.realtimeSinceStartup;
|
||||
Debug.Log($"[DynamicSpawnerSystem] running={running} usingCfg={usingCfg} activeTot={_activePlane + _activeHeli} plane={_activePlane} heli={_activeHeli} pools P/H={_planePrefabs.Count}/{_heliPrefabs.Count}");
|
||||
}
|
||||
|
||||
if (Time.realtimeSinceStartup >= _nextSpawnAtRealtime)
|
||||
{
|
||||
TrySpawnTick();
|
||||
ProgramNextSpawn();
|
||||
}
|
||||
|
||||
yield return wait;
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// SPAWN
|
||||
// =====================================================================
|
||||
|
||||
private void TrySpawnTick()
|
||||
{
|
||||
bool canPlane = _planePrefabs.Count > 0 && _activePlane < _dyn.maxActivePlane;
|
||||
bool canHeli = _heliPrefabs.Count > 0 && _activeHeli < _dyn.maxActiveHelicopter;
|
||||
|
||||
if (!canPlane && !canHeli) return;
|
||||
|
||||
int roomPlane = Mathf.Max(0, _dyn.maxActivePlane - _activePlane);
|
||||
int roomHeli = Mathf.Max(0, _dyn.maxActiveHelicopter - _activeHeli);
|
||||
|
||||
if (canPlane && (!canHeli || roomPlane >= roomHeli)) TrySpawnOne(PlaneType.Plane);
|
||||
else if (canHeli) TrySpawnOne(PlaneType.Helicopter);
|
||||
}
|
||||
|
||||
private void TrySpawnOne(PlaneType t)
|
||||
{
|
||||
GameObject prefab = null;
|
||||
LevelConfig.DynamicPool poolRef = null;
|
||||
|
||||
if (t == PlaneType.Plane)
|
||||
{
|
||||
if (_planePrefabs.Count == 0) return;
|
||||
int idx = WeightedPickIndex(_planeWeights);
|
||||
prefab = _planePrefabs[idx];
|
||||
poolRef = _planePoolRef[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_heliPrefabs.Count == 0) return;
|
||||
int idx = WeightedPickIndex(_heliWeights);
|
||||
prefab = _heliPrefabs[idx];
|
||||
poolRef = _heliPoolRef[idx];
|
||||
}
|
||||
|
||||
if (prefab == null || poolRef == null) return;
|
||||
|
||||
// Cap por tipo (opcional)
|
||||
int aliveByType = CountAlive(t);
|
||||
if (poolRef.maxAlive > 0 && aliveByType >= poolRef.maxAlive)
|
||||
return;
|
||||
|
||||
if (!TryGetSpawnOutsideViewportPixels(out Vector3 pos, out Vector3 inwardDir))
|
||||
{
|
||||
// fallback: si no hay cámara, no spawneamos
|
||||
Debug.LogWarning("[DynamicSpawnerSystem] StartWithLevel(null)");
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var go = Instantiate(prefab, pos, Quaternion.identity);
|
||||
go.name = prefab.name + "_Dyn";
|
||||
_cfg = cfg;
|
||||
_levelStartTime = Time.time;
|
||||
_lastSpawnTime = Time.time - 999f;
|
||||
_spawnedSoFar = 0;
|
||||
_didFirstForcedBlue = false;
|
||||
_activeTokens.Clear();
|
||||
|
||||
// Token de housekeeping
|
||||
var token = go.GetComponent<DynamicSpawnToken>();
|
||||
if (token == null) token = go.AddComponent<DynamicSpawnToken>();
|
||||
token.Initialize(this, t);
|
||||
_tokens.Add(token);
|
||||
BuildFlatPoolFromConfig(_cfg);
|
||||
|
||||
// Dirección hacia adentro
|
||||
var plane = go.GetComponent<PlaneController>();
|
||||
if (plane) plane.SetInitialDirection(inwardDir);
|
||||
running = true;
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(CoRunRealtime());
|
||||
|
||||
// ===== NUEVO: velocidad final por tipo con curvas =====
|
||||
float t01 = EvalDifficultyT01();
|
||||
float globalSpeedMul = SafeEval(_dyn.globalSpeedMultiplier, t01, 1f);
|
||||
float perTypeSpeedMul = SafeEval(poolRef.speedMultiplierOverTime, t01, 1f);
|
||||
|
||||
float finalSpeed = plane != null ? Mathf.Max(0f, plane.speed) : 0f;
|
||||
|
||||
// Si el pool define un rango, lo usamos como base. Si no, respetamos el speed del prefab/controller.
|
||||
if (poolRef.speedRange != Vector2.zero)
|
||||
{
|
||||
float baseSpeed = Random.Range(
|
||||
Mathf.Min(poolRef.speedRange.x, poolRef.speedRange.y),
|
||||
Mathf.Max(poolRef.speedRange.x, poolRef.speedRange.y)
|
||||
);
|
||||
finalSpeed = baseSpeed;
|
||||
}
|
||||
|
||||
// Multiplicadores globales y por tipo + tuning del nivel
|
||||
finalSpeed *= globalSpeedMul;
|
||||
finalSpeed *= perTypeSpeedMul;
|
||||
finalSpeed *= Mathf.Max(0.01f, _cfg != null ? _cfg.speedMultiplier : 1f);
|
||||
|
||||
if (plane) plane.speed = finalSpeed;
|
||||
|
||||
if (t == PlaneType.Plane) _activePlane++;
|
||||
else _activeHeli++;
|
||||
Debug.Log("[DynamicSpawnerSystem] Iniciado con LevelConfig '" + cfg.levelId + "'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula un punto de spawn exactamente a 'spawnMarginPixels' FUERA del rectángulo visible.
|
||||
/// No modifica velocidad de los aviones: solo su posición inicial y la dirección hacia adentro.
|
||||
/// GameManager puede avisar que el tutorial inicial (overlay) está mostrando pausa.
|
||||
/// Mientras esto esté en true, NO instanciamos (ni siquiera offscreen).
|
||||
/// </summary>
|
||||
private bool TryGetSpawnOutsideViewportPixels(out Vector3 worldPos, out Vector3 inwardDir)
|
||||
public void SetTutorialPause(bool paused) { _pausedByTutorial = paused; }
|
||||
|
||||
/// <summary>
|
||||
/// Llamado por tokens cuando el avión entra en viewport por primera vez.
|
||||
/// </summary>
|
||||
public void OnTokenEnteredViewport(DynamicSpawnToken tok) { }
|
||||
|
||||
/// <summary>
|
||||
/// Llamado por tokens en OnDestroy para que dejemos de contarlos.
|
||||
/// </summary>
|
||||
public void OnTokenDestroyed(DynamicSpawnToken tok)
|
||||
{
|
||||
worldPos = Vector3.zero;
|
||||
inwardDir = Vector3.right;
|
||||
if (tok == null) return;
|
||||
_activeTokens.Remove(tok);
|
||||
}
|
||||
|
||||
var cam = Camera.main;
|
||||
if (cam == null) return false;
|
||||
// ====================== LOOP ======================
|
||||
|
||||
int W = Mathf.Max(1, Screen.width);
|
||||
int H = Mathf.Max(1, Screen.height);
|
||||
|
||||
float mx = (float)spawnMarginPixels / W;
|
||||
float my = (float)spawnMarginPixels / H;
|
||||
|
||||
int edge = Random.Range(0, 4); // 0=L,1=R,2=B,3=T
|
||||
Vector3 innerVP = new Vector3(0.5f, 0.5f, 0f); // centro
|
||||
|
||||
Vector3 spawnVP;
|
||||
switch (edge)
|
||||
private IEnumerator CoRunRealtime()
|
||||
{
|
||||
// delay inicial definido en config (sirve tanto simple como avanzado)
|
||||
float startDelay = Mathf.Max(0f, _cfg.dynamic.startDelay);
|
||||
if (startDelay > 0f)
|
||||
{
|
||||
case 0: spawnVP = new Vector3(-mx, Random.value, 0f); break;
|
||||
case 1: spawnVP = new Vector3(1f + mx, Random.value, 0f); break;
|
||||
case 2: spawnVP = new Vector3(Random.value, -my, 0f); break;
|
||||
default: spawnVP = new Vector3(Random.value, 1f + my, 0f); break;
|
||||
float t0 = Time.realtimeSinceStartup;
|
||||
while (Time.realtimeSinceStartup - t0 < startDelay)
|
||||
yield return null;
|
||||
}
|
||||
|
||||
float depthToZ0 = Mathf.Abs(cam.transform.position.z);
|
||||
|
||||
Vector3 spawnWorld = cam.ViewportToWorldPoint(new Vector3(spawnVP.x, spawnVP.y, depthToZ0));
|
||||
Vector3 innerWorld = cam.ViewportToWorldPoint(new Vector3(innerVP.x, innerVP.y, depthToZ0));
|
||||
|
||||
spawnWorld.z = 0f;
|
||||
innerWorld.z = 0f;
|
||||
|
||||
Vector3 dir = innerWorld - spawnWorld;
|
||||
if (dir.sqrMagnitude < 1e-6f)
|
||||
while (running)
|
||||
{
|
||||
var camCenter = cam.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, depthToZ0));
|
||||
camCenter.z = 0f;
|
||||
dir = camCenter - spawnWorld;
|
||||
// cortar todo si GameOver definitivo o Win
|
||||
if (GameManager.Instance != null)
|
||||
{
|
||||
if (GameManager.Instance.isPausedByGameOver || GameManager.Instance.IsWin())
|
||||
{
|
||||
yield return null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// detener spawns mientras el overlay/tutorial inicial está activo
|
||||
if (_pausedByTutorial)
|
||||
{
|
||||
yield return null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Chequeo de capacidad: ¿hay lugar para spawn?
|
||||
if (HasCapacityToSpawn())
|
||||
{
|
||||
float waitNeeded = ComputeNextDelaySeconds();
|
||||
float elapsed = Time.time - _lastSpawnTime;
|
||||
|
||||
if (elapsed >= waitNeeded)
|
||||
TrySpawnOne();
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
inwardDir = dir.normalized;
|
||||
worldPos = spawnWorld;
|
||||
}
|
||||
|
||||
// ====================== SPAWN CORE ======================
|
||||
|
||||
private bool HasCapacityToSpawn()
|
||||
{
|
||||
// -------- MODO SIMPLE --------
|
||||
if (_cfg.dynamic.useSimpleTiming)
|
||||
{
|
||||
int cap = _cfg.dynamic.simpleMaxSimultaneousPlanes;
|
||||
if (cap <= 0) return HasCapacityAdvanced();
|
||||
|
||||
int alive = 0;
|
||||
for (int i = _activeTokens.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_activeTokens[i] == null) { _activeTokens.RemoveAt(i); continue; }
|
||||
alive++;
|
||||
}
|
||||
return alive < cap;
|
||||
}
|
||||
|
||||
// -------- MODO AVANZADO --------
|
||||
return HasCapacityAdvanced();
|
||||
}
|
||||
|
||||
private bool HasCapacityAdvanced()
|
||||
{
|
||||
int planeAlive = 0;
|
||||
int heliAlive = 0;
|
||||
|
||||
for (int i = _activeTokens.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var tok = _activeTokens[i];
|
||||
if (tok == null) { _activeTokens.RemoveAt(i); continue; }
|
||||
|
||||
if (tok.Type == PlaneType.Helicopter) heliAlive++;
|
||||
else planeAlive++;
|
||||
}
|
||||
|
||||
float tSince = Time.time - _levelStartTime;
|
||||
float ramps = (_cfg.dynamic.rampEverySeconds > 0f)
|
||||
? Mathf.Floor(tSince / _cfg.dynamic.rampEverySeconds)
|
||||
: 0f;
|
||||
|
||||
int capPlane = _cfg.dynamic.maxActivePlane + Mathf.Max(0, _cfg.dynamic.extraPlanePerRamp) * (int)ramps;
|
||||
int capHeli = _cfg.dynamic.maxActiveHelicopter + Mathf.Max(0, _cfg.dynamic.extraHeliPerRamp) * (int)ramps;
|
||||
|
||||
if (planeAlive >= capPlane && heliAlive >= capHeli) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Ritmo / dificultad (NO velocidad)
|
||||
// =====================================================================
|
||||
|
||||
private void ProgramNextSpawn()
|
||||
private float ComputeNextDelaySeconds()
|
||||
{
|
||||
if (_dyn == null) { _nextSpawnAtRealtime = Time.realtimeSinceStartup + 1f; return; }
|
||||
|
||||
// base global
|
||||
float baseIvl = Mathf.Max(0.05f, _dyn.baseInterval);
|
||||
float lo = Mathf.Min(_dyn.intervalRange.x, _dyn.intervalRange.y);
|
||||
float hi = Mathf.Max(_dyn.intervalRange.x, _dyn.intervalRange.y);
|
||||
float randAdd = (hi > lo) ? Random.Range(lo, hi) - baseIvl : 0f;
|
||||
|
||||
float ivl = Mathf.Max(0.05f, baseIvl + randAdd);
|
||||
|
||||
// ===== NUEVO: curva global de rate + normalización t =====
|
||||
float t01 = EvalDifficultyT01();
|
||||
float globalRateMul = SafeEval(_dyn.globalSpawnRate, t01, 1f);
|
||||
ivl /= Mathf.Max(0.05f, globalRateMul);
|
||||
|
||||
// tuning de nivel
|
||||
if (_cfg != null) ivl /= Mathf.Max(0.01f, _cfg.spawnRateMultiplier);
|
||||
|
||||
_nextSpawnAtRealtime = Time.realtimeSinceStartup + ivl;
|
||||
}
|
||||
|
||||
private void TryRampUp()
|
||||
{
|
||||
if (_dyn == null) return;
|
||||
if (_dyn.rampEverySeconds <= 0f) return;
|
||||
|
||||
float t = Time.realtimeSinceStartup - _rampStartRealtime;
|
||||
int steps = Mathf.FloorToInt(t / _dyn.rampEverySeconds);
|
||||
if (steps <= _rampSteps) return;
|
||||
|
||||
int newSteps = steps - _rampSteps;
|
||||
_rampSteps = steps;
|
||||
|
||||
for (int i = 0; i < newSteps; i++)
|
||||
// -------- MODO SIMPLE --------
|
||||
if (_cfg.dynamic.useSimpleTiming)
|
||||
{
|
||||
_dyn.baseInterval *= Mathf.Clamp(_dyn.intervalMultiplierPerRamp, 0.2f, 1.0f);
|
||||
_dyn.maxActivePlane = Mathf.Max(0, _dyn.maxActivePlane + _dyn.extraPlanePerRamp);
|
||||
_dyn.maxActiveHelicopter = Mathf.Max(0, _dyn.maxActiveHelicopter + _dyn.extraHeliPerRamp);
|
||||
return Mathf.Max(0.05f, _cfg.dynamic.spawnIntervalSeconds);
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
Debug.Log($"[DynamicSpawnerSystem] Ramp-up x{newSteps}: baseInterval={_dyn.baseInterval:0.00} caps P/H={_dyn.maxActivePlane}/{_dyn.maxActiveHelicopter}");
|
||||
// -------- MODO AVANZADO LEGACY --------
|
||||
float baseInt = Mathf.Max(0.05f, _cfg.dynamic.baseInterval);
|
||||
float rndMin = _cfg.dynamic.intervalRange.x;
|
||||
float rndMax = _cfg.dynamic.intervalRange.y;
|
||||
if (rndMax < rndMin) { float tmp = rndMin; rndMin = rndMax; rndMax = tmp; }
|
||||
float addRand = Random.Range(rndMin, rndMax);
|
||||
|
||||
float raw = baseInt + addRand;
|
||||
|
||||
float tSince = Time.time - _levelStartTime;
|
||||
if (_cfg.dynamic.rampEverySeconds > 0f)
|
||||
{
|
||||
float ramps = Mathf.Floor(tSince / _cfg.dynamic.rampEverySeconds);
|
||||
if (ramps > 0f)
|
||||
{
|
||||
float multPow = Mathf.Pow(Mathf.Clamp(_cfg.dynamic.intervalMultiplierPerRamp, 0.1f, 1f), ramps);
|
||||
raw *= multPow;
|
||||
}
|
||||
}
|
||||
|
||||
float globalMult = (_cfg.spawnRateMultiplier <= 0f) ? 1f : _cfg.spawnRateMultiplier;
|
||||
raw /= globalMult;
|
||||
|
||||
return Mathf.Max(0.05f, raw);
|
||||
}
|
||||
|
||||
private void BuildTypePools()
|
||||
private void TrySpawnOne()
|
||||
{
|
||||
_planePrefabs.Clear(); _planeWeights.Clear(); _planePoolRef.Clear();
|
||||
_heliPrefabs.Clear(); _heliWeights.Clear(); _heliPoolRef.Clear();
|
||||
// elegir tipo / prefab
|
||||
PlaneType chosenType;
|
||||
GameObject chosenPrefab;
|
||||
Color? tintOverride = null;
|
||||
|
||||
if (_pools == null) return;
|
||||
|
||||
foreach (var p in _pools)
|
||||
if (!_didFirstForcedBlue && _cfg.dynamic.forceBlueFirstPlane)
|
||||
{
|
||||
if (p == null || p.prefabs == null || p.prefabs.Length == 0) continue;
|
||||
float w = Mathf.Max(0.01f, p.weight);
|
||||
_didFirstForcedBlue = true;
|
||||
chosenType = _cfg.dynamic.firstPlaneType;
|
||||
chosenPrefab = PickAnyPrefabOfType(chosenType);
|
||||
tintOverride = _cfg.dynamic.firstPlaneTint;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!PickFromWeightedPool(out chosenType, out chosenPrefab)) return;
|
||||
}
|
||||
|
||||
if (p.type == PlaneType.Helicopter)
|
||||
if (chosenPrefab == null) return;
|
||||
|
||||
// generar posición y dirección fuera de cámara (en píxeles exactos)
|
||||
Vector3 spawnPos;
|
||||
Vector3 dirIn;
|
||||
if (!ComputeOffscreenSpawnViewport(out spawnPos, out dirIn))
|
||||
return;
|
||||
|
||||
// instanciar
|
||||
GameObject go = Instantiate(chosenPrefab, spawnPos, Quaternion.identity);
|
||||
_spawnedSoFar++;
|
||||
_lastSpawnTime = Time.time;
|
||||
|
||||
// setup avión
|
||||
PlaneController pc = go.GetComponent<PlaneController>();
|
||||
if (pc != null)
|
||||
{
|
||||
pc.SetInitialDirection(dirIn.normalized);
|
||||
pc.speed *= Mathf.Max(0.01f, _cfg.speedMultiplier);
|
||||
|
||||
if (tintOverride.HasValue)
|
||||
{
|
||||
foreach (var pr in p.prefabs)
|
||||
{
|
||||
if (pr)
|
||||
{
|
||||
_heliPrefabs.Add(pr);
|
||||
_heliWeights.Add(w);
|
||||
_heliPoolRef.Add(p); // guardar referencia al pool
|
||||
}
|
||||
}
|
||||
var sr = pc.GetComponent<SpriteRenderer>();
|
||||
if (sr) sr.color = tintOverride.Value;
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
// token para conteo
|
||||
var tok = go.AddComponent<DynamicSpawnToken>();
|
||||
tok.Initialize(this, chosenType);
|
||||
_activeTokens.Add(tok);
|
||||
}
|
||||
|
||||
// ====================== SPAWN POSITION (FUERA DE CAMARA, VIEWPORT) ======================
|
||||
|
||||
/// <summary>
|
||||
/// Calcula un punto fuera de la pantalla a una distancia en píxeles exacta,
|
||||
/// usando coordenadas de viewport [(0,0) .. (1,1)] y extendiéndolas.
|
||||
/// </summary>
|
||||
private bool ComputeOffscreenSpawnViewport(out Vector3 worldPos, out Vector3 dirToCenter)
|
||||
{
|
||||
worldPos = Vector3.zero;
|
||||
dirToCenter = Vector3.right;
|
||||
|
||||
Camera cam = Camera.main;
|
||||
if (cam == null || !cam.orthographic)
|
||||
{
|
||||
Debug.LogWarning("[DynamicSpawnerSystem] Camera.main ortográfica no encontrada.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// centro (mundo) y centro (viewport)
|
||||
Vector3 camCenterWorld = cam.transform.position; camCenterWorld.z = 0f;
|
||||
Vector3 camCenterVP = new Vector3(0.5f, 0.5f, 0f);
|
||||
|
||||
// offset de píxeles → offset de viewport
|
||||
float px = Mathf.Max(0f, _cfg.dynamic.offscreenPixels);
|
||||
// Convertimos “px fuera” a delta en viewport:
|
||||
// anchoViewport = 1.0 corresponde a Screen.width píxeles
|
||||
// altoViewport = 1.0 corresponde a Screen.height píxeles
|
||||
float dxVP = (Screen.width > 0) ? (px / Screen.width) : 0.05f;
|
||||
float dyVP = (Screen.height > 0) ? (px / Screen.height) : 0.05f;
|
||||
|
||||
// elegir lado: 0=izq,1=der,2=abajo,3=arriba
|
||||
int side = Random.Range(0, 4);
|
||||
|
||||
Vector3 vp = camCenterVP;
|
||||
switch (side)
|
||||
{
|
||||
case 0: // izquierda: x<0
|
||||
vp.x = -dxVP;
|
||||
vp.y = Random.Range(0f, 1f);
|
||||
dirToCenter = Vector3.right;
|
||||
break;
|
||||
case 1: // derecha: x>1
|
||||
vp.x = 1f + dxVP;
|
||||
vp.y = Random.Range(0f, 1f);
|
||||
dirToCenter = Vector3.left;
|
||||
break;
|
||||
case 2: // abajo: y<0
|
||||
vp.y = -dyVP;
|
||||
vp.x = Random.Range(0f, 1f);
|
||||
dirToCenter = Vector3.up;
|
||||
break;
|
||||
default: // arriba: y>1
|
||||
vp.y = 1f + dyVP;
|
||||
vp.x = Random.Range(0f, 1f);
|
||||
dirToCenter = Vector3.down;
|
||||
break;
|
||||
}
|
||||
|
||||
// Convertir a mundo (z debe ser distancia desde cámara)
|
||||
float z = Mathf.Abs(cam.transform.position.z); // cámara ortográfica suele estar a -10
|
||||
vp.z = z;
|
||||
worldPos = cam.ViewportToWorldPoint(vp);
|
||||
worldPos.z = 0f;
|
||||
|
||||
// dirección hacia adentro (centro de cámara)
|
||||
dirToCenter = (camCenterWorld - worldPos);
|
||||
if (dirToCenter.sqrMagnitude < 0.0001f) dirToCenter = Vector3.right;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ====================== POOLS ======================
|
||||
|
||||
private void BuildFlatPoolFromConfig(LevelConfig cfg)
|
||||
{
|
||||
_flatPool.Clear();
|
||||
if (cfg == null || cfg.dynamicPools == null) return;
|
||||
|
||||
foreach (var entry in cfg.dynamicPools)
|
||||
{
|
||||
if (entry == null) continue;
|
||||
if (entry.prefabs == null || entry.prefabs.Length == 0) continue;
|
||||
|
||||
float w = Mathf.Max(0.01f, entry.weight);
|
||||
foreach (var pf in entry.prefabs)
|
||||
{
|
||||
foreach (var pr in p.prefabs)
|
||||
{
|
||||
if (pr)
|
||||
{
|
||||
_planePrefabs.Add(pr);
|
||||
_planeWeights.Add(w);
|
||||
_planePoolRef.Add(p); // guardar referencia al pool
|
||||
}
|
||||
}
|
||||
if (pf == null) continue;
|
||||
PoolEntry pe;
|
||||
pe.type = entry.type;
|
||||
pe.prefab = pf;
|
||||
pe.weight = w;
|
||||
_flatPool.Add(pe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int WeightedPickIndex(List<float> weights)
|
||||
private GameObject PickAnyPrefabOfType(PlaneType t)
|
||||
{
|
||||
if (weights == null || weights.Count == 0) return 0;
|
||||
float sum = 0f; for (int i = 0; i < weights.Count; i++) sum += Mathf.Max(0.0001f, weights[i]);
|
||||
float r = Random.value * sum;
|
||||
float totalW = 0f;
|
||||
for (int i = 0; i < _flatPool.Count; i++)
|
||||
if (_flatPool[i].type == t) totalW += _flatPool[i].weight;
|
||||
|
||||
if (totalW <= 0f) return null;
|
||||
|
||||
float pick = Random.value * totalW;
|
||||
float acc = 0f;
|
||||
for (int i = 0; i < weights.Count; i++)
|
||||
for (int i = 0; i < _flatPool.Count; i++)
|
||||
{
|
||||
acc += Mathf.Max(0.0001f, weights[i]);
|
||||
if (r <= acc) return i;
|
||||
if (_flatPool[i].type != t) continue;
|
||||
acc += _flatPool[i].weight;
|
||||
if (pick <= acc) return _flatPool[i].prefab;
|
||||
}
|
||||
return weights.Count - 1;
|
||||
return null;
|
||||
}
|
||||
|
||||
private int CountAlive(PlaneType type)
|
||||
private bool PickFromWeightedPool(out PlaneType t, out GameObject pf)
|
||||
{
|
||||
var all = FindObjectsOfType<PlaneController>();
|
||||
int count = 0;
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
if (all[i].planeType == type && !all[i].isLanding) count++;
|
||||
return count;
|
||||
}
|
||||
t = PlaneType.Plane;
|
||||
pf = null;
|
||||
|
||||
private float SafeEval(AnimationCurve curve, float t, float fallback)
|
||||
{
|
||||
if (curve == null || curve.length == 0) return fallback;
|
||||
return curve.Evaluate(t);
|
||||
}
|
||||
if (_flatPool.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("[DynamicSpawnerSystem] No hay prefabs en dynamicPools.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// t normalizado 0..1 según difficultyHorizonSeconds (o 0 si no aplica)
|
||||
private float EvalDifficultyT01()
|
||||
{
|
||||
float horizon = (_dyn != null && _dyn.difficultyHorizonSeconds > 0f) ? _dyn.difficultyHorizonSeconds : 0f;
|
||||
if (horizon <= 0f) return 0f;
|
||||
float elapsed = Time.realtimeSinceStartup - _difficultyStartRealtime;
|
||||
return Mathf.Clamp01(elapsed / horizon);
|
||||
float total = 0f;
|
||||
for (int i = 0; i < _flatPool.Count; i++) total += _flatPool[i].weight;
|
||||
|
||||
float pick = Random.value * total;
|
||||
float acc = 0f;
|
||||
for (int i = 0; i < _flatPool.Count; i++)
|
||||
{
|
||||
acc += _flatPool[i].weight;
|
||||
if (pick <= acc)
|
||||
{
|
||||
t = _flatPool[i].type;
|
||||
pf = _flatPool[i].prefab;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
t = _flatPool[0].type;
|
||||
pf = _flatPool[0].prefab;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,3 +15,4 @@ MonoBehaviour:
|
||||
levels:
|
||||
- {fileID: 11400000, guid: 02511a1817fb03f488e79bde43e9b4e3, type: 2}
|
||||
- {fileID: 11400000, guid: 32ed080379bdb4473bebe4c2b9f5e415, type: 2}
|
||||
- {fileID: 11400000, guid: 5e3f058d9125913449b0a15767a2bf05, type: 2}
|
||||
|
||||
@@ -43,7 +43,7 @@ MonoBehaviour:
|
||||
randomizeInterval: 0
|
||||
spawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 0
|
||||
maxSpawns: 2147483647
|
||||
- spawnerId: 1
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
@@ -54,7 +54,7 @@ MonoBehaviour:
|
||||
randomizeInterval: 0
|
||||
spawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 0
|
||||
maxSpawns: 2147483647
|
||||
typeCaps: []
|
||||
randomPickWhenExceedCap: 1
|
||||
waves: []
|
||||
@@ -69,129 +69,32 @@ MonoBehaviour:
|
||||
startingScore: 0
|
||||
scoreTarget: 0
|
||||
speedMultiplier: 1
|
||||
spawnRateMultiplier: 1
|
||||
spawnRateMultiplier: 0.1
|
||||
scoreMultiplier: 1
|
||||
isTutorial: 0
|
||||
useDynamicSpawning: 1
|
||||
dynamic:
|
||||
useSimpleTiming: 1
|
||||
spawnIntervalSeconds: 5
|
||||
simpleMaxSimultaneousPlanes: 4
|
||||
enabled: 1
|
||||
baseInterval: 2.6999998
|
||||
baseInterval: 1
|
||||
intervalRange: {x: 1.5, y: 4}
|
||||
startDelay: 0.5
|
||||
maxActivePlane: 7
|
||||
maxActiveHelicopter: 3
|
||||
rampEverySeconds: 35
|
||||
intervalMultiplierPerRamp: 0.9
|
||||
startDelay: 1
|
||||
maxActivePlane: 9999
|
||||
maxActiveHelicopter: 9999
|
||||
rampEverySeconds: 0.01
|
||||
intervalMultiplierPerRamp: 0.1
|
||||
extraPlanePerRamp: 1
|
||||
extraHeliPerRamp: 1
|
||||
offscreenPixels: 500
|
||||
spawnOutsideCameraEdges: 1
|
||||
edgeInsetWorld: 0.35
|
||||
forceBlueFirstPlane: 0
|
||||
firstPlaneType: 1
|
||||
firstPlaneTint: {r: 0.2, g: 0.9, b: 1, a: 1}
|
||||
difficultyHorizonSeconds: 120
|
||||
globalSpawnRate:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
globalSpeedMultiplier:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 34
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 34
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
dynamicPools:
|
||||
- type: 0
|
||||
prefabs:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
weight: 0
|
||||
maxAlive: 0
|
||||
baseSpawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
speedRange: {x: 0, y: 0}
|
||||
spawnIntervalOverTime:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
speedMultiplierOverTime:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 34
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 34
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
|
||||
@@ -34,7 +34,7 @@ public class LevelConfig : ScriptableObject
|
||||
[Tooltip("Cantidad de aviones que deben aterrizar para ganar. 0 = desactivado.")]
|
||||
public int winCount = 0;
|
||||
|
||||
// ================== CONFIG EXISTENTE ==================
|
||||
// ================== ESCENA ==================
|
||||
[Header("Runways")]
|
||||
public List<RunwaySpawn> runways = new List<RunwaySpawn>();
|
||||
|
||||
@@ -43,7 +43,7 @@ public class LevelConfig : ScriptableObject
|
||||
public List<TypeCap> typeCaps = new List<TypeCap>();
|
||||
public bool randomPickWhenExceedCap = true;
|
||||
|
||||
[Header("Spawns / Oleadas")]
|
||||
[Header("Spawns / Oleadas (legacy opcional)")]
|
||||
public List<SpawnWave> waves = new List<SpawnWave>();
|
||||
|
||||
[Header("Clima / Reglas")]
|
||||
@@ -54,14 +54,18 @@ public class LevelConfig : ScriptableObject
|
||||
public int startingScore = 0;
|
||||
public int scoreTarget = 0;
|
||||
|
||||
[Header("Tuning")]
|
||||
[Header("Tuning Global")]
|
||||
[Tooltip("Multiplica velocidad base de los aviones.")]
|
||||
public float speedMultiplier = 1f;
|
||||
|
||||
[Tooltip("Escala global de ritmo de spawn. 1 = default. Si <1, más lento.")]
|
||||
public float spawnRateMultiplier = 1f;
|
||||
|
||||
[Tooltip("Multiplica puntuación por aterrizaje.")]
|
||||
public float scoreMultiplier = 1f;
|
||||
|
||||
// ================== TUTORIAL (simple overlay/GO) ==================
|
||||
[Header("Tutorial")]
|
||||
[Tooltip("Si está activo, al cargar el nivel se mostrará tu overlay de tutorial (un GameObject de UI) y el juego se pausará.")]
|
||||
[Tooltip("Si está activo, GameManager mostrará el panel tutorial y pausará spawns al inicio.")]
|
||||
public bool isTutorial = false;
|
||||
|
||||
// =====================================================================
|
||||
@@ -78,100 +82,84 @@ public class LevelConfig : ScriptableObject
|
||||
[Tooltip("Pools de prefabs por tipo (con pesos). Al menos uno por tipo que quieras spawnear.")]
|
||||
public List<DynamicPool> dynamicPools = new List<DynamicPool>();
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class DynamicSettings
|
||||
{
|
||||
[Header("Estado")]
|
||||
public bool enabled = true;
|
||||
[Serializable]
|
||||
public class DynamicSettings
|
||||
{
|
||||
[Header("== MODO SIMPLE ==")]
|
||||
[Tooltip("Si está activo, se ignora todo lo de abajo (ramp, intervalRange, etc.). " +
|
||||
"Spawnea 1 avión cada 'spawnIntervalSeconds' mientras haya lugar.")]
|
||||
public bool useSimpleTiming = true;
|
||||
|
||||
[Header("Ritmo base")]
|
||||
[Tooltip("Intervalo medio de spawn (seg).")]
|
||||
public float baseInterval = 3.0f;
|
||||
[Tooltip("Cada cuántos segundos crear un nuevo avión (sólo si useSimpleTiming = true).")]
|
||||
public float spawnIntervalSeconds = 3f;
|
||||
|
||||
[Tooltip("Rango aleatorio del intervalo (seg).")]
|
||||
public Vector2 intervalRange = new Vector2(1.5f, 4.0f);
|
||||
[Tooltip("Máximo simultáneo en pantalla (todos los tipos cuentan). " +
|
||||
"Si está en 0 o menor, se usa la lógica avanzada de abajo.")]
|
||||
public int simpleMaxSimultaneousPlanes = 4;
|
||||
|
||||
[Tooltip("Delay inicial antes del primer spawn (seg).")]
|
||||
public float startDelay = 0.5f;
|
||||
// --------- AVANZADO / LEGACY (se mantiene para no romper nada) ---------
|
||||
|
||||
[Header("Capacidad")]
|
||||
[Tooltip("Máximo de aviones (Plane) activos simultáneamente.")]
|
||||
public int maxActivePlane = 6;
|
||||
[Header("Estado (legacy avanzado)")]
|
||||
public bool enabled = true;
|
||||
|
||||
[Tooltip("Máximo de helicópteros (Helicopter) activos simultáneamente.")]
|
||||
public int maxActiveHelicopter = 2;
|
||||
[Header("Ritmo base (legacy)")]
|
||||
[Tooltip("Intervalo medio de spawn (seg). Sólo se usa si useSimpleTiming = false.")]
|
||||
public float baseInterval = 3.0f;
|
||||
|
||||
[Header("Ramp-up (dificultad)")]
|
||||
[Tooltip("Cada cuántos segundos se acelera y aumenta la capacidad.")]
|
||||
public float rampEverySeconds = 35f;
|
||||
[Tooltip("Rango aleatorio del intervalo (seg). Sólo se usa si useSimpleTiming = false.")]
|
||||
public Vector2 intervalRange = new Vector2(1.5f, 4.0f);
|
||||
|
||||
[Tooltip("Multiplicador por ramp para reducir el intervalo (0.9 = 10% más rápido).")]
|
||||
public float intervalMultiplierPerRamp = 0.9f;
|
||||
[Tooltip("Delay inicial antes del primer spawn (seg). Se usa SIEMPRE, incluso en modo simple.")]
|
||||
public float startDelay = 0.5f;
|
||||
|
||||
[Tooltip("Aumento de capacidad por ramp para Plane.")]
|
||||
public int extraPlanePerRamp = 1;
|
||||
[Header("Capacidad (legacy)")]
|
||||
[Tooltip("Máximo de aviones (Plane) activos simultáneamente (modo avanzado).")]
|
||||
public int maxActivePlane = 6;
|
||||
|
||||
[Tooltip("Aumento de capacidad por ramp para Helicopter.")]
|
||||
public int extraHeliPerRamp = 1;
|
||||
[Tooltip("Máximo de helicópteros (Helicopter) activos simultáneamente (modo avanzado).")]
|
||||
public int maxActiveHelicopter = 2;
|
||||
|
||||
[Header("Borde / Cámara")]
|
||||
[Tooltip("Spawn fuera de cámara (true) o pegado al borde interno (false).")]
|
||||
public bool spawnOutsideCameraEdges = true;
|
||||
[Header("Ramp-up (legacy)")]
|
||||
[Tooltip("Cada cuántos segundos se acelera y aumenta la capacidad (modo avanzado).")]
|
||||
public float rampEverySeconds = 35f;
|
||||
|
||||
[Tooltip("Inset en mundo desde el borde de cámara.")]
|
||||
public float edgeInsetWorld = 0.35f;
|
||||
[Tooltip("Multiplicador por ramp para reducir el intervalo (0.9 = 10% más rápido). (modo avanzado)")]
|
||||
public float intervalMultiplierPerRamp = 0.9f;
|
||||
|
||||
[Header("Primer avión (opcional)")]
|
||||
[Tooltip("Forzar primer spawn como 'azul' (para onboarding).")]
|
||||
public bool forceBlueFirstPlane = false;
|
||||
[Tooltip("Aumento de capacidad por ramp para Plane. (modo avanzado)")]
|
||||
public int extraPlanePerRamp = 1;
|
||||
|
||||
public PlaneType firstPlaneType = PlaneType.Plane;
|
||||
public Color firstPlaneTint = new Color(0.2f, 0.9f, 1f, 1f);
|
||||
[Tooltip("Aumento de capacidad por ramp para Helicopter. (modo avanzado)")]
|
||||
public int extraHeliPerRamp = 1;
|
||||
|
||||
// ====== NUEVO: horizonte y curvas globales ======
|
||||
[Header("Curvas Globales de Dificultad")]
|
||||
[Tooltip("Segundos para normalizar t en curvas 0..1 (si 0, se usa ramp).")]
|
||||
public float difficultyHorizonSeconds = 120f;
|
||||
[Header("Offscreen Spawn")]
|
||||
[Tooltip("Distancia en píxeles FUERA de la pantalla donde nacen los aviones. " +
|
||||
"100 = justo afuera del borde.")]
|
||||
public float offscreenPixels = 100f;
|
||||
|
||||
[Tooltip("Multiplicador global de spawn rate (1 = sin cambio).")]
|
||||
public AnimationCurve globalSpawnRate = AnimationCurve.Linear(0, 1, 1, 1);
|
||||
[Tooltip("Si true, los aviones aparecen fuera del borde. " +
|
||||
"Si false, aparecen pegados al borde interno.")]
|
||||
public bool spawnOutsideCameraEdges = true;
|
||||
|
||||
[Tooltip("Multiplicador global de velocidad (1 = sin cambio).")]
|
||||
public AnimationCurve globalSpeedMultiplier = AnimationCurve.Linear(0, 1, 1, 1);
|
||||
}
|
||||
[Tooltip("Inset en mundo desde el borde de cámara cuando spawnOutsideCameraEdges = false.")]
|
||||
public float edgeInsetWorld = 0.35f;
|
||||
|
||||
[Header("Primer avión (opcional)")]
|
||||
[Tooltip("Forzar primer spawn como 'azul' (para onboarding).")]
|
||||
public bool forceBlueFirstPlane = false;
|
||||
|
||||
[Serializable]
|
||||
public class DynamicPool
|
||||
{
|
||||
public PlaneType type = PlaneType.Plane;
|
||||
public GameObject[] prefabs;
|
||||
[Range(0.01f, 10f)] public float weight = 1f;
|
||||
public PlaneType firstPlaneType = PlaneType.Plane;
|
||||
public Color firstPlaneTint = new Color(0.2f, 0.9f, 1f, 1f);
|
||||
}
|
||||
|
||||
// ====== NUEVO: límites y tiempos por tipo (opcionales) ======
|
||||
[Header("Cap por tipo (opcional)")]
|
||||
[Tooltip("Máximo simultáneo de este tipo. Si <=0, usa el cap global del tipo.")]
|
||||
public int maxAlive = 0;
|
||||
[Serializable]
|
||||
public class DynamicPool
|
||||
{
|
||||
public PlaneType type = PlaneType.Plane;
|
||||
public GameObject[] prefabs;
|
||||
[Range(0.01f, 10f)] public float weight = 1f;
|
||||
}
|
||||
|
||||
[Header("Tiempos por tipo (opcional)")]
|
||||
[Tooltip("Intervalo medio propio (si <=0, usa baseInterval global).")]
|
||||
public float baseSpawnInterval = 0f;
|
||||
|
||||
[Tooltip("Rango aleatorio propio (si x==y==0, usa intervalRange global).")]
|
||||
public Vector2 intervalRange = Vector2.zero;
|
||||
|
||||
[Header("Velocidad por tipo (opcional)")]
|
||||
[Tooltip("Rango de velocidad en unidades/seg para este tipo (si x==y==0, no modifica).")]
|
||||
public Vector2 speedRange = Vector2.zero;
|
||||
|
||||
[Header("Curvas por tipo (0..1 / opcional)")]
|
||||
[Tooltip("Multiplicador sobre el intervalo (1 = sin cambio).")]
|
||||
public AnimationCurve spawnIntervalOverTime = new AnimationCurve(); // vacío = sin efecto
|
||||
|
||||
[Tooltip("Multiplicador de velocidad (1 = sin cambio).")]
|
||||
public AnimationCurve speedMultiplierOverTime = new AnimationCurve(); // vacío = sin efecto
|
||||
}
|
||||
// =====================================================================
|
||||
// ============================ TIPOS ==================================
|
||||
// =====================================================================
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b90922ad5a8044af7a08e521a532f26e
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+2367
-34
File diff suppressed because it is too large
Load Diff
@@ -6341,7 +6341,7 @@ MonoBehaviour:
|
||||
showPressToContinue: 0
|
||||
minLoadingSeconds: 2
|
||||
smoothing: 6
|
||||
forceLockLevelC: 1
|
||||
forceLockLevelC: 0
|
||||
--- !u!114 &1315005676
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -129,6 +129,8 @@ MonoBehaviour:
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_PrefilterUseLegacyLightmaps: 0
|
||||
m_PrefilterReflectionProbeBlending: 1
|
||||
m_PrefilterReflectionProbeBoxProjection: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 0
|
||||
m_Textures:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
<Solution>
|
||||
<Project Path="Assembly-CSharp.csproj" />
|
||||
<Project Path="Google.Play.Games.Editor.csproj" />
|
||||
<Project Path="Google.Play.Games.csproj" />
|
||||
<Project Path="Assembly-CSharp-Editor.csproj" />
|
||||
</Solution>
|
||||
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.collab-proxy": "2.8.2",
|
||||
"com.google.ads.mobile": "10.5.0",
|
||||
"com.unity.collab-proxy": "2.10.0",
|
||||
"com.unity.feature.2d": "2.0.1",
|
||||
"com.unity.ide.rider": "3.0.36",
|
||||
"com.unity.ide.visualstudio": "2.0.23",
|
||||
"com.unity.inputsystem": "1.14.1",
|
||||
"com.unity.ide.rider": "3.0.38",
|
||||
"com.unity.ide.visualstudio": "2.0.25",
|
||||
"com.unity.inputsystem": "1.14.2",
|
||||
"com.unity.multiplayer.center": "1.0.0",
|
||||
"com.unity.recorder": "5.1.3",
|
||||
"com.unity.render-pipelines.universal": "17.0.4",
|
||||
"com.unity.test-framework": "1.5.1",
|
||||
"com.unity.timeline": "1.8.7",
|
||||
"com.unity.test-framework": "1.6.0",
|
||||
"com.unity.timeline": "1.8.9",
|
||||
"com.unity.ugui": "2.0.0",
|
||||
"com.unity.visualscripting": "1.9.7",
|
||||
"com.unity.modules.accessibility": "1.0.0",
|
||||
@@ -43,8 +44,7 @@
|
||||
"com.unity.modules.video": "1.0.0",
|
||||
"com.unity.modules.vr": "1.0.0",
|
||||
"com.unity.modules.wind": "1.0.0",
|
||||
"com.unity.modules.xr": "1.0.0",
|
||||
"com.google.ads.mobile": "10.5.0"
|
||||
"com.unity.modules.xr": "1.0.0"
|
||||
},
|
||||
"scopedRegistries": [
|
||||
{
|
||||
@@ -56,4 +56,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+23
-22
@@ -31,12 +31,12 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.aseprite": {
|
||||
"version": "1.1.9",
|
||||
"version": "1.1.10",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.2d.common": "6.0.6",
|
||||
"com.unity.2d.sprite": "1.0.0",
|
||||
"com.unity.2d.common": "6.0.6",
|
||||
"com.unity.mathematics": "1.2.6",
|
||||
"com.unity.modules.animation": "1.0.0"
|
||||
},
|
||||
@@ -47,11 +47,11 @@
|
||||
"depth": 2,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.burst": "1.8.4",
|
||||
"com.unity.2d.sprite": "1.0.0",
|
||||
"com.unity.mathematics": "1.1.0",
|
||||
"com.unity.modules.uielements": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.uielements": "1.0.0"
|
||||
"com.unity.burst": "1.8.4"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
@@ -83,8 +83,8 @@
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.2d.common": "9.0.7",
|
||||
"com.unity.mathematics": "1.1.0",
|
||||
"com.unity.2d.common": "9.0.7",
|
||||
"com.unity.modules.physics2d": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
@@ -103,8 +103,8 @@
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.2d.tilemap": "1.0.0",
|
||||
"com.unity.modules.tilemap": "1.0.0",
|
||||
"com.unity.2d.tilemap": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
@@ -119,7 +119,7 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.burst": {
|
||||
"version": "1.8.23",
|
||||
"version": "1.8.25",
|
||||
"depth": 2,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
@@ -129,20 +129,21 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.collab-proxy": {
|
||||
"version": "2.8.2",
|
||||
"version": "2.10.0",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.collections": {
|
||||
"version": "2.5.1",
|
||||
"version": "2.6.2",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.burst": "1.8.17",
|
||||
"com.unity.test-framework": "1.4.5",
|
||||
"com.unity.nuget.mono-cecil": "1.11.4",
|
||||
"com.unity.burst": "1.8.23",
|
||||
"com.unity.mathematics": "1.3.2",
|
||||
"com.unity.nuget.mono-cecil": "1.11.5",
|
||||
"com.unity.test-framework": "1.4.6",
|
||||
"com.unity.test-framework.performance": "3.0.3"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
@@ -165,11 +166,11 @@
|
||||
"com.unity.2d.spriteshape": "10.0.7",
|
||||
"com.unity.2d.tilemap": "1.0.0",
|
||||
"com.unity.2d.tilemap.extras": "4.1.0",
|
||||
"com.unity.2d.aseprite": "1.1.9"
|
||||
"com.unity.2d.aseprite": "1.1.10"
|
||||
}
|
||||
},
|
||||
"com.unity.ide.rider": {
|
||||
"version": "3.0.36",
|
||||
"version": "3.0.38",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
@@ -178,16 +179,16 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.visualstudio": {
|
||||
"version": "2.0.23",
|
||||
"version": "2.0.25",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.test-framework": "1.1.9"
|
||||
"com.unity.test-framework": "1.1.31"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.inputsystem": {
|
||||
"version": "1.14.1",
|
||||
"version": "1.14.2",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
@@ -211,7 +212,7 @@
|
||||
}
|
||||
},
|
||||
"com.unity.nuget.mono-cecil": {
|
||||
"version": "1.11.4",
|
||||
"version": "1.11.5",
|
||||
"depth": 2,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
@@ -288,7 +289,7 @@
|
||||
}
|
||||
},
|
||||
"com.unity.test-framework": {
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
@@ -298,7 +299,7 @@
|
||||
}
|
||||
},
|
||||
"com.unity.test-framework.performance": {
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"depth": 2,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
@@ -308,13 +309,13 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.timeline": {
|
||||
"version": "1.8.7",
|
||||
"version": "1.8.9",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.director": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ Library: E:\Earth Genesis Games\Carrasco\Temp\BurstOutput\tempburstlibs\arm64-v8
|
||||
--float-mode=Fast
|
||||
--generate-link-xml=Temp\burst.link.xml
|
||||
--temp-folder=E:\Earth Genesis Games\Carrasco\Temp\Burst
|
||||
--key-folder=C:/Program Files/Unity/Hub/Editor/6000.0.54f1/Editor/Data/PlaybackEngines/AndroidPlayer
|
||||
--key-folder=C:/Program Files/Unity/Hub/Editor/6000.0.61f1/Editor/Data/PlaybackEngines/AndroidPlayer
|
||||
--decode-folder=E:\Earth Genesis Games\Carrasco\Library\Burst
|
||||
--output=E:\Earth Genesis Games\Carrasco\Temp\BurstOutput\tempburstlibs\arm64-v8a\lib_burst_generated
|
||||
--pdb-search-paths=Temp/ManagedSymbols/
|
||||
@@ -30,7 +30,7 @@ Library: E:\Earth Genesis Games\Carrasco\Temp\BurstOutput\tempburstlibs\arm64-v8
|
||||
--target-framework=NetFramework
|
||||
--generate-link-xml=Temp\burst.link.xml
|
||||
--temp-folder=E:\Earth Genesis Games\Carrasco\Temp\Burst
|
||||
--key-folder=C:/Program Files/Unity/Hub/Editor/6000.0.54f1/Editor/Data/PlaybackEngines/AndroidPlayer
|
||||
--key-folder=C:/Program Files/Unity/Hub/Editor/6000.0.61f1/Editor/Data/PlaybackEngines/AndroidPlayer
|
||||
--decode-folder=E:\Earth Genesis Games\Carrasco\Library\Burst
|
||||
--output=E:\Earth Genesis Games\Carrasco\Temp\BurstOutput\tempburstlibs\arm64-v8a\lib_burst_generated
|
||||
--pdb-search-paths=Temp/ManagedSymbols/
|
||||
@@ -47,7 +47,7 @@ Library: E:\Earth Genesis Games\Carrasco\Temp\BurstOutput\tempburstlibs\arm64-v8
|
||||
--target-framework=NetFramework
|
||||
--generate-link-xml=Temp\burst.link.xml
|
||||
--temp-folder=E:\Earth Genesis Games\Carrasco\Temp\Burst
|
||||
--key-folder=C:/Program Files/Unity/Hub/Editor/6000.0.54f1/Editor/Data/PlaybackEngines/AndroidPlayer
|
||||
--key-folder=C:/Program Files/Unity/Hub/Editor/6000.0.61f1/Editor/Data/PlaybackEngines/AndroidPlayer
|
||||
--decode-folder=E:\Earth Genesis Games\Carrasco\Library\Burst
|
||||
--output=E:\Earth Genesis Games\Carrasco\Temp\BurstOutput\tempburstlibs\arm64-v8a\lib_burst_generated
|
||||
--pdb-search-paths=Temp/ManagedSymbols/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<projectSettings>
|
||||
<projectSetting name="com.google.external-dependency-managerAnalyticsEnabled" value="False" />
|
||||
<projectSetting name="com.google.external-dependency-managerAnalyticsCookie" value="a59dbc600ed042c4bd9c113661d2a8fe" />
|
||||
<projectSetting name="com.google.external-dependency-managerAnalyticsEnabled" value="True" />
|
||||
<projectSetting name="Google.VersionHandler.VerboseLoggingEnabled" value="True" />
|
||||
<projectSetting name="Google.VersionHandler.VersionHandlingEnabled" value="True" />
|
||||
<projectSetting name="GooglePlayServices.AutoResolverEnabled" value="False" />
|
||||
|
||||
@@ -74,6 +74,7 @@ PlayerSettings:
|
||||
androidStartInFullscreen: 1
|
||||
androidRenderOutsideSafeArea: 1
|
||||
androidUseSwappy: 1
|
||||
androidDisplayOptions: 1
|
||||
androidBlitType: 0
|
||||
androidResizeableActivity: 1
|
||||
androidDefaultWindowWidth: 1920
|
||||
@@ -90,6 +91,7 @@ PlayerSettings:
|
||||
muteOtherAudioSources: 0
|
||||
Prepare IOS For Recording: 0
|
||||
Force IOS Speakers When Recording: 0
|
||||
audioSpatialExperience: 0
|
||||
deferSystemGesturesMode: 0
|
||||
hideHomeButton: 0
|
||||
submitAnalytics: 1
|
||||
@@ -144,7 +146,7 @@ PlayerSettings:
|
||||
loadStoreDebugModeEnabled: 0
|
||||
visionOSBundleVersion: 1.0
|
||||
tvOSBundleVersion: 1.0
|
||||
bundleVersion: 0.2.0
|
||||
bundleVersion: 0.2.1
|
||||
preloadedAssets:
|
||||
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
metroInputSource: 0
|
||||
@@ -176,7 +178,7 @@ PlayerSettings:
|
||||
iPhone: 0
|
||||
tvOS: 0
|
||||
overrideDefaultApplicationIdentifier: 1
|
||||
AndroidBundleVersionCode: 3
|
||||
AndroidBundleVersionCode: 4
|
||||
AndroidMinSdkVersion: 24
|
||||
AndroidTargetSdkVersion: 0
|
||||
AndroidPreferredInstallLocation: 1
|
||||
@@ -270,13 +272,16 @@ PlayerSettings:
|
||||
AndroidTargetArchitectures: 2
|
||||
AndroidSplashScreenScale: 0
|
||||
androidSplashScreen: {fileID: 0}
|
||||
AndroidKeystoreName: '{dedicated}: Downloads/user.keystore'
|
||||
AndroidKeystoreName: '{inproject}: user.keystore'
|
||||
AndroidKeyaliasName: eggames
|
||||
AndroidEnableArmv9SecurityFeatures: 0
|
||||
AndroidEnableArm64MTE: 0
|
||||
AndroidBuildApkPerCpuArchitecture: 0
|
||||
AndroidTVCompatibility: 0
|
||||
AndroidIsGame: 1
|
||||
androidAppCategory: 3
|
||||
useAndroidAppCategory: 1
|
||||
androidAppCategoryOther:
|
||||
AndroidEnableTango: 0
|
||||
androidEnableBanner: 1
|
||||
androidUseLowAccuracyLocation: 0
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
m_EditorVersion: 6000.0.54f1
|
||||
m_EditorVersionWithRevision: 6000.0.54f1 (4c506e5b5cc5)
|
||||
m_EditorVersion: 6000.0.61f1
|
||||
m_EditorVersionWithRevision: 6000.0.61f1 (74a0adb02c31)
|
||||
|
||||
@@ -13,6 +13,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
shaderVariantLimit: 128
|
||||
overrideShaderVariantLimit: 0
|
||||
customInterpolatorErrorThreshold: 32
|
||||
customInterpolatorWarningThreshold: 16
|
||||
customHeatmapValues: {fileID: 0}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user