mirror of
https://github.com/Earth-Genesis-Games/Carrasco.git
synced 2026-09-11 09:52:19 +00:00
fixeo general
This commit is contained in:
@@ -2,466 +2,415 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Spawner dinámico data-driven con fallback por Inspector.
|
||||
/// - Lee LevelConfig.dynamic / dynamicPools cuando están disponibles.
|
||||
/// - Si el SO no está o está sin pools, cae a los prefabs del Inspector y spawnea igual.
|
||||
/// - Spawnea en bordes de cámara (dentro o fuera), con ligera variación angular.
|
||||
/// - Primer avión opcional tintado (para tutorial).
|
||||
/// - Usa reloj en tiempo real (realtime) y respeta flags del GameManager,
|
||||
/// pero NO se cuelga si Time.timeScale = 0.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[DefaultExecutionOrder(-5)]
|
||||
public class DynamicSpawnerSystem : MonoBehaviour
|
||||
{
|
||||
[Header("Debug/Diagnóstico")]
|
||||
public bool verbose = true;
|
||||
public bool heartbeatLogs = true;
|
||||
[Tooltip("Segundos entre heartbeats (estado, bloqueos, caps, etc.).")]
|
||||
public float heartbeatEvery = 2f;
|
||||
[Header("Estado")]
|
||||
[SerializeField] private bool running = false;
|
||||
[SerializeField] private bool usingCfg = false;
|
||||
|
||||
[Header("Modo / Fallback")]
|
||||
[Tooltip("Si no se recibe LevelConfig válido, o si no tiene pools, arrancar con los parámetros del Inspector.")]
|
||||
public bool autoStartIfNoConfig = true;
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool verbose = false;
|
||||
[SerializeField] private float statusLogEvery = 10f;
|
||||
|
||||
[Tooltip("Spawnear fuera de la cámara (true) o pegado al borde interno (false).")]
|
||||
public bool spawnOutsideCameraEdges = true;
|
||||
[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;
|
||||
|
||||
[Tooltip("Inset en unidades de mundo para el spawn en bordes (positivo = fuera si 'spawnOutsideCameraEdges' = true).")]
|
||||
public float edgeInsetWorld = 0.8f;
|
||||
// ----- Config cargada -----
|
||||
private LevelConfig _cfg;
|
||||
private LevelConfig.DynamicSettings _dyn;
|
||||
private List<LevelConfig.DynamicPool> _pools;
|
||||
|
||||
[Tooltip("Variación angular aleatoria del rumbo hacia adentro.")]
|
||||
public float headingJitterDeg = 8f;
|
||||
// ----- Conteo activo por tipo -----
|
||||
private int _activePlane = 0;
|
||||
private int _activeHeli = 0;
|
||||
|
||||
[Header("Fallback Prefabs (Inspector)")]
|
||||
public GameObject[] planePrefabsFallback;
|
||||
public GameObject[] heliPrefabsFallback;
|
||||
// tokens vivos
|
||||
private readonly HashSet<DynamicSpawnToken> _tokens = new HashSet<DynamicSpawnToken>();
|
||||
|
||||
[Header("Fallback Ritmo")]
|
||||
public float baseIntervalFallback = 2.0f;
|
||||
public Vector2 intervalRangeFallback = new Vector2(1.0f, 3.0f);
|
||||
public float startDelayFallback = 0.2f;
|
||||
// control de ritmo
|
||||
private float _nextSpawnAtRealtime = 0f;
|
||||
private float _lastStatusLog = -999f;
|
||||
|
||||
[Header("Fallback Caps")]
|
||||
public int maxActivePlaneFallback = 4;
|
||||
public int maxActiveHelicopterFallback = 1;
|
||||
// ramp (dificultad, NO velocidad)
|
||||
private float _rampStartRealtime = 0f;
|
||||
private int _rampSteps = 0;
|
||||
|
||||
[Header("Primer avión (tutorial)")]
|
||||
public bool forceBlueFirstPlane = false;
|
||||
public PlaneType firstPlaneType = PlaneType.Plane;
|
||||
public Color firstPlaneTint = new Color(0.25f, 0.8f, 1f, 1f);
|
||||
// ===== NUEVO: tiempo para normalizar curvas 0..1 =====
|
||||
private float _difficultyStartRealtime = 0f;
|
||||
|
||||
// ========= Estado general =========
|
||||
private bool running = false;
|
||||
private Coroutine co;
|
||||
// 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>();
|
||||
|
||||
// Config activa (SO o fallback)
|
||||
private bool usingConfig = false;
|
||||
// ===== 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 float currentInterval;
|
||||
private Vector2 currentRange;
|
||||
private int capPlane;
|
||||
private int capHeli;
|
||||
private Coroutine _runner;
|
||||
|
||||
private float nextSpawnAtRealtime = 0f;
|
||||
private float lastSpawnRealtime = -999f;
|
||||
// =====================================================================
|
||||
// API
|
||||
// =====================================================================
|
||||
|
||||
private bool firstForcedDone = false;
|
||||
|
||||
// Contadores activos
|
||||
private int activePlane = 0;
|
||||
private int activeHeli = 0;
|
||||
private int spawnedSoFar = 0;
|
||||
|
||||
// Pools activos (normalizados)
|
||||
private GameObject[] poolPlane;
|
||||
private GameObject[] poolHeli;
|
||||
|
||||
// Tokens vivos
|
||||
private readonly HashSet<DynamicSpawnToken> tokens = new HashSet<DynamicSpawnToken>();
|
||||
|
||||
// Bloqueos externos
|
||||
private bool tutorialBlocked = false;
|
||||
|
||||
// Diagnóstico
|
||||
private string lastBlockedReason = "";
|
||||
private float nextHeartbeatAt = 0f;
|
||||
|
||||
// ========= API pública =========
|
||||
/// <summary>Llamado por GameManager cuando el nivel quiere usar spawner dinámico.</summary>
|
||||
public void StartWithLevel(LevelConfig cfg)
|
||||
{
|
||||
StopRun();
|
||||
StopAllSpawning();
|
||||
|
||||
// Intentar usar SO
|
||||
bool cfgOk = (cfg != null && cfg.useDynamicSpawning && cfg.dynamic != null && cfg.dynamic.enabled);
|
||||
_cfg = cfg;
|
||||
usingCfg = (_cfg != null);
|
||||
if (!usingCfg) { if (verbose) Debug.LogWarning("[DynamicSpawnerSystem] StartWithLevel sin LevelConfig."); return; }
|
||||
|
||||
if (cfgOk)
|
||||
{
|
||||
usingConfig = true;
|
||||
_dyn = _cfg.dynamic;
|
||||
_pools = _cfg.dynamicPools ?? new List<LevelConfig.DynamicPool>();
|
||||
|
||||
// Pools desde SO
|
||||
poolPlane = FilterPrefabs(ResolvePoolFromSO(cfg, PlaneType.Plane));
|
||||
poolHeli = FilterPrefabs(ResolvePoolFromSO(cfg, PlaneType.Helicopter));
|
||||
BuildTypePools();
|
||||
|
||||
// Si NO hay pools en el SO → fallback automático a Inspector
|
||||
if (!HasAnyPool(poolPlane, poolHeli))
|
||||
{
|
||||
if (verbose)
|
||||
Debug.LogWarning("[DynamicSpawnerSystem] LevelConfig.dynamicPools no tiene prefabs válidos. Usando FALLBACK de Inspector.");
|
||||
_activePlane = 0;
|
||||
_activeHeli = 0;
|
||||
_tokens.Clear();
|
||||
|
||||
ApplyFallbackConfig(); // carga pools/ritmo/caps del Inspector
|
||||
usingConfig = false; // dejamos claro que estamos en fallback
|
||||
StartCommon(startDelayFallback);
|
||||
return;
|
||||
}
|
||||
_rampStartRealtime = Time.realtimeSinceStartup;
|
||||
_difficultyStartRealtime = _rampStartRealtime; // punto 0 para curvas 0..1
|
||||
_rampSteps = 0;
|
||||
|
||||
// Ritmo + multiplicador global de nivel
|
||||
float rateMul = Mathf.Max(0.01f, cfg.spawnRateMultiplier <= 0f ? 1f : cfg.spawnRateMultiplier);
|
||||
currentInterval = Mathf.Max(0.05f, cfg.dynamic.baseInterval / rateMul);
|
||||
currentRange = new Vector2(
|
||||
Mathf.Min(cfg.dynamic.intervalRange.x, cfg.dynamic.intervalRange.y),
|
||||
Mathf.Max(cfg.dynamic.intervalRange.x, cfg.dynamic.intervalRange.y)
|
||||
);
|
||||
running = (_cfg.useDynamicSpawning && _dyn != null && _dyn.enabled);
|
||||
if (!running) { if (verbose) Debug.Log("[DynamicSpawnerSystem] Dynamic desactivado por config."); return; }
|
||||
|
||||
// Caps
|
||||
capPlane = Mathf.Max(0, cfg.dynamic.maxActivePlane);
|
||||
capHeli = Mathf.Max(0, cfg.dynamic.maxActiveHelicopter);
|
||||
|
||||
// Borde
|
||||
spawnOutsideCameraEdges = cfg.dynamic.spawnOutsideCameraEdges;
|
||||
edgeInsetWorld = cfg.dynamic.edgeInsetWorld;
|
||||
|
||||
// Primer avión
|
||||
forceBlueFirstPlane = cfg.dynamic.forceBlueFirstPlane;
|
||||
firstPlaneType = cfg.dynamic.firstPlaneType;
|
||||
firstPlaneTint = cfg.dynamic.firstPlaneTint;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
Debug.Log($"[DynamicSpawnerSystem] Iniciado con SO '{cfg.levelId}'. " +
|
||||
$"Pools: plane={(poolPlane!=null?poolPlane.Length:0)} heli={(poolHeli!=null?poolHeli.Length:0)} | " +
|
||||
$"interval≈{currentInterval:0.00}s [{currentRange.x:0.00},{currentRange.y:0.00}] | " +
|
||||
$"caps P/H={capPlane}/{capHeli} | spawnOutside={spawnOutsideCameraEdges} inset={edgeInsetWorld:0.00}");
|
||||
}
|
||||
|
||||
StartCommon(cfg.dynamic.startDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback total si no hay SO válido
|
||||
if (!autoStartIfNoConfig)
|
||||
{
|
||||
if (verbose) Debug.Log("[DynamicSpawnerSystem] SO inválido y 'autoStartIfNoConfig' = false → no inicia.");
|
||||
return;
|
||||
}
|
||||
|
||||
usingConfig = false;
|
||||
ApplyFallbackConfig();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
Debug.Log($"[DynamicSpawnerSystem] Iniciado en FALLBACK (Inspector). " +
|
||||
$"Pools: plane={(poolPlane!=null?poolPlane.Length:0)} heli={(poolHeli!=null?poolHeli.Length:0)} | " +
|
||||
$"interval≈{currentInterval:0.00}s [{currentRange.x:0.00},{currentRange.y:0.00}] | " +
|
||||
$"caps P/H={capPlane}/{capHeli} | spawnOutside={spawnOutsideCameraEdges} inset={edgeInsetWorld:0.00}");
|
||||
}
|
||||
|
||||
StartCommon(startDelayFallback);
|
||||
}
|
||||
_nextSpawnAtRealtime = Time.realtimeSinceStartup + Mathf.Max(0f, _dyn.startDelay);
|
||||
_runner = StartCoroutine(CoRunRealtime());
|
||||
if (verbose) Debug.Log("[DynamicSpawnerSystem] Iniciado.");
|
||||
}
|
||||
|
||||
/// <summary>Reanuda el loop (mantiene config actual).</summary>
|
||||
public void StartRun()
|
||||
{
|
||||
if (running) return;
|
||||
if (!HasAnyPool(poolPlane, poolHeli))
|
||||
{
|
||||
if (verbose) Debug.LogWarning("[DynamicSpawnerSystem] StartRun: no hay pools cargados.");
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
if (nextSpawnAtRealtime <= 0f) nextSpawnAtRealtime = Time.realtimeSinceStartup + 0.1f;
|
||||
co = StartCoroutine(CoRunRealtime());
|
||||
if (verbose) Debug.Log("[DynamicSpawnerSystem] Reanudado.");
|
||||
}
|
||||
public void StopStreaming() => running = false;
|
||||
|
||||
/// <summary>Detiene el loop de spawns (no destruye nada).</summary>
|
||||
public void StopRun()
|
||||
public void StopAllSpawning()
|
||||
{
|
||||
running = false;
|
||||
if (co != null) StopCoroutine(co);
|
||||
co = null;
|
||||
if (verbose) Debug.Log("[DynamicSpawnerSystem] Detenido.");
|
||||
if (_runner != null) StopCoroutine(_runner);
|
||||
_runner = null;
|
||||
_tokens.Clear();
|
||||
}
|
||||
|
||||
/// <summary>El tutorial abre/cierra: gateo explícito.</summary>
|
||||
public void SetTutorialBlocked(bool blocked)
|
||||
// Compat.
|
||||
private void Stop() => StopAllSpawning();
|
||||
|
||||
// Callbacks desde DynamicSpawnToken
|
||||
public void OnTokenEnteredViewport(DynamicSpawnToken token) { /* opcional */ }
|
||||
|
||||
public void OnTokenDestroyed(DynamicSpawnToken token)
|
||||
{
|
||||
tutorialBlocked = blocked;
|
||||
if (verbose) Debug.Log("[DynamicSpawnerSystem] TutorialBlocked=" + blocked);
|
||||
if (token == null) return;
|
||||
if (_tokens.Remove(token))
|
||||
{
|
||||
if (token.Type == PlaneType.Plane) _activePlane = Mathf.Max(0, _activePlane - 1);
|
||||
else if (token.Type == PlaneType.Helicopter) _activeHeli = Mathf.Max(0, _activeHeli - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ========= Internals =========
|
||||
private void ApplyFallbackConfig()
|
||||
{
|
||||
poolPlane = FilterPrefabs(planePrefabsFallback);
|
||||
poolHeli = FilterPrefabs(heliPrefabsFallback);
|
||||
|
||||
currentInterval = Mathf.Max(0.05f, baseIntervalFallback);
|
||||
currentRange = new Vector2(
|
||||
Mathf.Min(intervalRangeFallback.x, intervalRangeFallback.y),
|
||||
Mathf.Max(intervalRangeFallback.x, intervalRangeFallback.y)
|
||||
);
|
||||
|
||||
capPlane = Mathf.Max(0, maxActivePlaneFallback);
|
||||
capHeli = Mathf.Max(0, maxActiveHelicopterFallback);
|
||||
}
|
||||
|
||||
private void StartCommon(float startDelay)
|
||||
{
|
||||
firstForcedDone = false;
|
||||
activePlane = activeHeli = spawnedSoFar = 0;
|
||||
tokens.Clear();
|
||||
|
||||
float now = Time.realtimeSinceStartup;
|
||||
nextSpawnAtRealtime = now + Mathf.Max(0f, startDelay);
|
||||
|
||||
running = true;
|
||||
nextHeartbeatAt = now + Mathf.Max(0.5f, heartbeatEvery);
|
||||
co = StartCoroutine(CoRunRealtime());
|
||||
}
|
||||
// =====================================================================
|
||||
// LOOP
|
||||
// =====================================================================
|
||||
|
||||
private IEnumerator CoRunRealtime()
|
||||
{
|
||||
while (running)
|
||||
{
|
||||
// Heartbeat (diagnóstico)
|
||||
if (heartbeatLogs && Time.realtimeSinceStartup >= nextHeartbeatAt)
|
||||
{
|
||||
nextHeartbeatAt += Mathf.Max(0.5f, heartbeatEvery);
|
||||
Debug.Log("[DynamicSpawnerSystem] " + GetStatusLine());
|
||||
}
|
||||
var wait = new WaitForSecondsRealtime(0.05f);
|
||||
|
||||
// Bloqueos globales
|
||||
if (IsGloballyBlocked(out string reason))
|
||||
while (true)
|
||||
{
|
||||
if (!running || !usingCfg || _dyn == null)
|
||||
{
|
||||
lastBlockedReason = reason;
|
||||
yield return null;
|
||||
yield return wait;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Caps y pools
|
||||
if (!HasAnyPool(poolPlane, poolHeli))
|
||||
TryRampUp(); // solo ritmo/caps
|
||||
|
||||
if (verbose && Time.realtimeSinceStartup - _lastStatusLog >= statusLogEvery)
|
||||
{
|
||||
lastBlockedReason = "no-pools";
|
||||
yield return null; continue;
|
||||
_lastStatusLog = Time.realtimeSinceStartup;
|
||||
Debug.Log($"[DynamicSpawnerSystem] running={running} usingCfg={usingCfg} activeTot={_activePlane + _activeHeli} plane={_activePlane} heli={_activeHeli} pools P/H={_planePrefabs.Count}/{_heliPrefabs.Count}");
|
||||
}
|
||||
|
||||
bool fullPlane = (activePlane >= Mathf.Max(0, capPlane));
|
||||
bool fullHeli = (activeHeli >= Mathf.Max(0, capHeli));
|
||||
if ((poolPlane == null || poolPlane.Length == 0) && fullHeli) { lastBlockedReason = "heli-cap"; yield return null; continue; }
|
||||
if ((poolHeli == null || poolHeli.Length == 0) && fullPlane){ lastBlockedReason = "plane-cap"; yield return null; continue; }
|
||||
if (fullPlane && fullHeli) { lastBlockedReason = "caps-full"; yield return null; continue; }
|
||||
|
||||
// Espera hasta el próximo spawn
|
||||
float now = Time.realtimeSinceStartup;
|
||||
if (now < nextSpawnAtRealtime) { yield return null; continue; }
|
||||
|
||||
// Elegir tipo
|
||||
PlaneType picked = PickTypeRespectingCaps();
|
||||
if (!firstForcedDone && forceBlueFirstPlane) picked = firstPlaneType;
|
||||
|
||||
if (TrySpawnOne(picked, tintBlue: (!firstForcedDone && forceBlueFirstPlane)))
|
||||
if (Time.realtimeSinceStartup >= _nextSpawnAtRealtime)
|
||||
{
|
||||
firstForcedDone = true;
|
||||
lastBlockedReason = "";
|
||||
lastSpawnRealtime = now;
|
||||
TrySpawnTick();
|
||||
ProgramNextSpawn();
|
||||
}
|
||||
|
||||
// Programar siguiente
|
||||
float waitRnd = Random.Range(currentRange.x, currentRange.y);
|
||||
float wait = Mathf.Clamp(waitRnd, 0.05f, Mathf.Max(0.06f, currentInterval * 2f));
|
||||
nextSpawnAtRealtime = Time.realtimeSinceStartup + wait;
|
||||
|
||||
yield return null;
|
||||
yield return wait;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsGloballyBlocked(out string reason)
|
||||
{
|
||||
reason = "";
|
||||
if (tutorialBlocked) { reason = "tutorial"; return true; }
|
||||
// =====================================================================
|
||||
// SPAWN
|
||||
// =====================================================================
|
||||
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null)
|
||||
{
|
||||
if (gm.isPaused) { reason = "paused"; return true; }
|
||||
if (gm.isPausedByGameOver) { reason = "gameover"; return true; }
|
||||
if (gm.IsWin()) { reason = "win"; return true; }
|
||||
}
|
||||
return false;
|
||||
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 bool TrySpawnOne(PlaneType type, bool tintBlue)
|
||||
private void TrySpawnOne(PlaneType t)
|
||||
{
|
||||
GameObject[] list = (type == PlaneType.Helicopter) ? poolHeli : poolPlane;
|
||||
if (list == null || list.Length == 0)
|
||||
GameObject prefab = null;
|
||||
LevelConfig.DynamicPool poolRef = null;
|
||||
|
||||
if (t == PlaneType.Plane)
|
||||
{
|
||||
if (verbose) Debug.LogWarning("[DynamicSpawnerSystem] No hay prefabs configurados para '" + type + "'.");
|
||||
return false;
|
||||
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 (type == PlaneType.Plane && activePlane >= Mathf.Max(0, capPlane)) return false;
|
||||
if (type == PlaneType.Helicopter && activeHeli >= Mathf.Max(0, capHeli)) return false;
|
||||
if (prefab == null || poolRef == null) return;
|
||||
|
||||
GameObject prefab = list[Random.Range(0, list.Length)];
|
||||
if (!prefab) return false;
|
||||
// Cap por tipo (opcional)
|
||||
int aliveByType = CountAlive(t);
|
||||
if (poolRef.maxAlive > 0 && aliveByType >= poolRef.maxAlive)
|
||||
return;
|
||||
|
||||
// Posición y rumbo
|
||||
ResolveEdgeSpawn(out var pos, out var dirIn);
|
||||
|
||||
float deg = Mathf.Atan2(dirIn.y, dirIn.x) * Mathf.Rad2Deg;
|
||||
Quaternion rot = Quaternion.Euler(0, 0, deg);
|
||||
|
||||
// Instanciar
|
||||
var go = Instantiate(prefab, pos, rot);
|
||||
spawnedSoFar++;
|
||||
|
||||
// Token
|
||||
var tok = go.GetComponent<DynamicSpawnToken>();
|
||||
if (!tok) tok = go.AddComponent<DynamicSpawnToken>();
|
||||
tok.Initialize(this, type);
|
||||
tokens.Add(tok);
|
||||
|
||||
// Config PlaneController (rumbo y colisión ON)
|
||||
var pc = go.GetComponent<PlaneController>();
|
||||
if (pc != null)
|
||||
if (!TryGetSpawnOutsideViewportPixels(out Vector3 pos, out Vector3 inwardDir))
|
||||
{
|
||||
pc.isLanding = false;
|
||||
pc.noCol = false;
|
||||
if (pc.boxCollider2D != null) pc.boxCollider2D.enabled = true;
|
||||
pc.SetInitialDirection(dirIn);
|
||||
// fallback: si no hay cámara, no spawneamos
|
||||
return;
|
||||
}
|
||||
|
||||
if (tintBlue)
|
||||
var go = Instantiate(prefab, pos, Quaternion.identity);
|
||||
go.name = prefab.name + "_Dyn";
|
||||
|
||||
// Token de housekeeping
|
||||
var token = go.GetComponent<DynamicSpawnToken>();
|
||||
if (token == null) token = go.AddComponent<DynamicSpawnToken>();
|
||||
token.Initialize(this, t);
|
||||
_tokens.Add(token);
|
||||
|
||||
// Dirección hacia adentro
|
||||
var plane = go.GetComponent<PlaneController>();
|
||||
if (plane) plane.SetInitialDirection(inwardDir);
|
||||
|
||||
// ===== 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)
|
||||
{
|
||||
var sr = go.GetComponent<SpriteRenderer>();
|
||||
if (sr) sr.color = firstPlaneTint;
|
||||
float baseSpeed = Random.Range(
|
||||
Mathf.Min(poolRef.speedRange.x, poolRef.speedRange.y),
|
||||
Mathf.Max(poolRef.speedRange.x, poolRef.speedRange.y)
|
||||
);
|
||||
finalSpeed = baseSpeed;
|
||||
}
|
||||
|
||||
if (type == PlaneType.Plane) activePlane++;
|
||||
else activeHeli++;
|
||||
// Multiplicadores globales y por tipo + tuning del nivel
|
||||
finalSpeed *= globalSpeedMul;
|
||||
finalSpeed *= perTypeSpeedMul;
|
||||
finalSpeed *= Mathf.Max(0.01f, _cfg != null ? _cfg.speedMultiplier : 1f);
|
||||
|
||||
if (verbose)
|
||||
Debug.Log($"[DynamicSpawnerSystem] Spawn {type} | active P/H={activePlane}/{activeHeli} total={spawnedSoFar}");
|
||||
if (plane) plane.speed = finalSpeed;
|
||||
|
||||
if (t == PlaneType.Plane) _activePlane++;
|
||||
else _activeHeli++;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
private bool TryGetSpawnOutsideViewportPixels(out Vector3 worldPos, out Vector3 inwardDir)
|
||||
{
|
||||
worldPos = Vector3.zero;
|
||||
inwardDir = Vector3.right;
|
||||
|
||||
var cam = Camera.main;
|
||||
if (cam == null) return false;
|
||||
|
||||
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)
|
||||
{
|
||||
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 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)
|
||||
{
|
||||
var camCenter = cam.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, depthToZ0));
|
||||
camCenter.z = 0f;
|
||||
dir = camCenter - spawnWorld;
|
||||
}
|
||||
inwardDir = dir.normalized;
|
||||
worldPos = spawnWorld;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResolveEdgeSpawn(out Vector3 spawnPos, out Vector3 dirIn)
|
||||
// =====================================================================
|
||||
// Ritmo / dificultad (NO velocidad)
|
||||
// =====================================================================
|
||||
|
||||
private void ProgramNextSpawn()
|
||||
{
|
||||
Camera cam = GetActiveOrthoCamera();
|
||||
if (cam == null)
|
||||
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++)
|
||||
{
|
||||
spawnPos = Vector3.zero; dirIn = Vector3.right; return;
|
||||
_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);
|
||||
}
|
||||
|
||||
float halfH = cam.orthographicSize;
|
||||
float halfW = halfH * cam.aspect;
|
||||
Vector3 c = cam.transform.position; c.z = 0f;
|
||||
|
||||
float inset = Mathf.Max(0f, edgeInsetWorld);
|
||||
int side = Random.Range(0, 4);
|
||||
|
||||
switch (side)
|
||||
{
|
||||
case 0: // izquierda
|
||||
spawnPos = new Vector3(c.x - halfW + (spawnOutsideCameraEdges ? -inset : inset),
|
||||
Random.Range(c.y - halfH + inset, c.y + halfH - inset), 0f);
|
||||
dirIn = Vector3.right; break;
|
||||
case 1: // derecha
|
||||
spawnPos = new Vector3(c.x + halfW + (spawnOutsideCameraEdges ? inset : -inset),
|
||||
Random.Range(c.y - halfH + inset, c.y + halfH - inset), 0f);
|
||||
dirIn = Vector3.left; break;
|
||||
case 2: // arriba
|
||||
spawnPos = new Vector3(Random.Range(c.x - halfW + inset, c.x + halfW - inset),
|
||||
c.y + halfH + (spawnOutsideCameraEdges ? inset : -inset), 0f);
|
||||
dirIn = Vector3.down; break;
|
||||
default: // abajo
|
||||
spawnPos = new Vector3(Random.Range(c.x - halfW + inset, c.x + halfW - inset),
|
||||
c.y - halfH + (spawnOutsideCameraEdges ? -inset : inset), 0f);
|
||||
dirIn = Vector3.up; break;
|
||||
}
|
||||
|
||||
float jitter = Mathf.Clamp(headingJitterDeg, 0f, 45f);
|
||||
dirIn = (Quaternion.Euler(0, 0, Random.Range(-jitter, jitter)) * dirIn).normalized;
|
||||
if (verbose)
|
||||
Debug.Log($"[DynamicSpawnerSystem] Ramp-up x{newSteps}: baseInterval={_dyn.baseInterval:0.00} caps P/H={_dyn.maxActivePlane}/{_dyn.maxActiveHelicopter}");
|
||||
}
|
||||
|
||||
private Camera GetActiveOrthoCamera()
|
||||
private void BuildTypePools()
|
||||
{
|
||||
if (Camera.main != null && Camera.main.orthographic) return Camera.main;
|
||||
var cams = Camera.allCameras;
|
||||
for (int i = 0; i < cams.Length; i++)
|
||||
if (cams[i] && cams[i].orthographic) return cams[i];
|
||||
return null;
|
||||
}
|
||||
_planePrefabs.Clear(); _planeWeights.Clear(); _planePoolRef.Clear();
|
||||
_heliPrefabs.Clear(); _heliWeights.Clear(); _heliPoolRef.Clear();
|
||||
|
||||
private PlaneType PickTypeRespectingCaps()
|
||||
{
|
||||
bool planeOk = (poolPlane != null && poolPlane.Length > 0) && activePlane < Mathf.Max(0, capPlane);
|
||||
bool heliOk = (poolHeli != null && poolHeli.Length > 0) && activeHeli < Mathf.Max(0, capHeli);
|
||||
if (_pools == null) return;
|
||||
|
||||
if (planeOk && !heliOk) return PlaneType.Plane;
|
||||
if (!planeOk && heliOk) return PlaneType.Helicopter;
|
||||
if (!planeOk && !heliOk) return PlaneType.Plane;
|
||||
|
||||
return (Random.value < 0.7f) ? PlaneType.Plane : PlaneType.Helicopter;
|
||||
}
|
||||
|
||||
private GameObject[] ResolvePoolFromSO(LevelConfig cfg, PlaneType type)
|
||||
{
|
||||
if (cfg.dynamicPools == null || cfg.dynamicPools.Count == 0) return null;
|
||||
foreach (var p in cfg.dynamicPools)
|
||||
foreach (var p in _pools)
|
||||
{
|
||||
if (p == null || p.prefabs == null || p.prefabs.Length == 0) continue;
|
||||
if (p.type == type) return p.prefabs;
|
||||
float w = Mathf.Max(0.01f, p.weight);
|
||||
|
||||
if (p.type == PlaneType.Helicopter)
|
||||
{
|
||||
foreach (var pr in p.prefabs)
|
||||
{
|
||||
if (pr)
|
||||
{
|
||||
_heliPrefabs.Add(pr);
|
||||
_heliWeights.Add(w);
|
||||
_heliPoolRef.Add(p); // guardar referencia al pool
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var pr in p.prefabs)
|
||||
{
|
||||
if (pr)
|
||||
{
|
||||
_planePrefabs.Add(pr);
|
||||
_planeWeights.Add(w);
|
||||
_planePoolRef.Add(p); // guardar referencia al pool
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GameObject[] FilterPrefabs(GameObject[] src)
|
||||
private int WeightedPickIndex(List<float> weights)
|
||||
{
|
||||
if (src == null || src.Length == 0) return null;
|
||||
List<GameObject> list = new List<GameObject>(src.Length);
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
if (src[i] != null) list.Add(src[i]);
|
||||
return (list.Count > 0) ? list.ToArray() : null;
|
||||
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 acc = 0f;
|
||||
for (int i = 0; i < weights.Count; i++)
|
||||
{
|
||||
acc += Mathf.Max(0.0001f, weights[i]);
|
||||
if (r <= acc) return i;
|
||||
}
|
||||
return weights.Count - 1;
|
||||
}
|
||||
|
||||
private static bool HasAnyPool(GameObject[] a, GameObject[] b)
|
||||
private int CountAlive(PlaneType type)
|
||||
{
|
||||
return (a != null && a.Length > 0) || (b != null && b.Length > 0);
|
||||
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;
|
||||
}
|
||||
|
||||
// ========= Callbacks desde los tokens =========
|
||||
public void OnTokenEnteredViewport(DynamicSpawnToken tok) { /* hook opcional */ }
|
||||
|
||||
public void OnTokenDestroyed(DynamicSpawnToken tok)
|
||||
private float SafeEval(AnimationCurve curve, float t, float fallback)
|
||||
{
|
||||
if (tok == null) return;
|
||||
if (tokens.Contains(tok)) tokens.Remove(tok);
|
||||
if (tok.Type == PlaneType.Plane) activePlane = Mathf.Max(0, activePlane - 1);
|
||||
if (tok.Type == PlaneType.Helicopter) activeHeli = Mathf.Max(0, activeHeli - 1);
|
||||
if (curve == null || curve.length == 0) return fallback;
|
||||
return curve.Evaluate(t);
|
||||
}
|
||||
|
||||
// ========= Diagnóstico =========
|
||||
public string GetStatusLine()
|
||||
// t normalizado 0..1 según difficultyHorizonSeconds (o 0 si no aplica)
|
||||
private float EvalDifficultyT01()
|
||||
{
|
||||
float since = (lastSpawnRealtime < -100f) ? 999f : (Time.realtimeSinceStartup - lastSpawnRealtime);
|
||||
int activeTot = activePlane + activeHeli;
|
||||
return $"running={running} usingCfg={usingConfig} activeTot={activeTot} plane={activePlane} heli={activeHeli} " +
|
||||
$"caps P/H={capPlane}/{capHeli} pools P/H={(poolPlane!=null?poolPlane.Length:0)}/{(poolHeli!=null?poolHeli.Length:0)} " +
|
||||
$"interval≈{currentInterval:0.00}s lastBlocked='{lastBlockedReason}' lastSpawnT={since:0.00}s ago";
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,30 @@ MonoBehaviour:
|
||||
landingDirectionMode: 0
|
||||
presetForwardOverride: {x: 1, y: 0}
|
||||
spawnAnchors: []
|
||||
spawnerRules: []
|
||||
spawnerRules:
|
||||
- spawnerId: 0
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
type: 0
|
||||
planePrefabsOverride:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
- {fileID: 2464552394801184783, guid: 760b99edcaf9e44b49903c0806d02239, type: 3}
|
||||
overrideTiming: 0
|
||||
randomizeInterval: 0
|
||||
spawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 0
|
||||
- spawnerId: 1
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
type: 1
|
||||
planePrefabsOverride:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
overrideTiming: 0
|
||||
randomizeInterval: 0
|
||||
spawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 0
|
||||
typeCaps: []
|
||||
randomPickWhenExceedCap: 1
|
||||
waves: []
|
||||
@@ -48,16 +71,15 @@ MonoBehaviour:
|
||||
speedMultiplier: 1
|
||||
spawnRateMultiplier: 1
|
||||
scoreMultiplier: 1
|
||||
useSimpleTutorialUI: 0
|
||||
dialog: {fileID: 0}
|
||||
isTutorial: 0
|
||||
useDynamicSpawning: 1
|
||||
dynamic:
|
||||
enabled: 1
|
||||
baseInterval: 3
|
||||
baseInterval: 2.6999998
|
||||
intervalRange: {x: 1.5, y: 4}
|
||||
startDelay: 0.5
|
||||
maxActivePlane: 6
|
||||
maxActiveHelicopter: 2
|
||||
maxActivePlane: 7
|
||||
maxActiveHelicopter: 3
|
||||
rampEverySeconds: 35
|
||||
intervalMultiplierPerRamp: 0.9
|
||||
extraPlanePerRamp: 1
|
||||
@@ -67,8 +89,109 @@ MonoBehaviour:
|
||||
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
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 992642e39ca424642a79f24dceb98581
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,148 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d7420bd8a7628a8429e53eb8069649f4, type: 3}
|
||||
m_Name: LevelConfigSummerIslands
|
||||
m_EditorClassIdentifier:
|
||||
levelId: Level_03
|
||||
displayName: Nivel 2
|
||||
description: Nivel de puntaje Summer Islands
|
||||
recommendedDifficulty: 1
|
||||
tileset: {fileID: 11400000, guid: 0a5243d3b357e714293207eb8064216e, type: 2}
|
||||
worldOrigin: {x: 0, y: 0}
|
||||
worldSize: {x: 100, y: 60}
|
||||
requiredTotalLandings: 1
|
||||
unlockHintOverride:
|
||||
useLandingWinCondition: 1
|
||||
winCount: 50
|
||||
runways:
|
||||
- preset: {fileID: 11400000, guid: 01f4af7339a2a13449409af583b33556, type: 2}
|
||||
position: {x: 9.59, y: -1.45}
|
||||
rotationZ: 90
|
||||
runwayId: 1
|
||||
landingDirectionMode: 0
|
||||
presetForwardOverride: {x: 1, y: 0}
|
||||
spawnAnchors: []
|
||||
- preset: {fileID: 11400000, guid: ccc1dcfd545fbfc4f801e8957e21bbb5, type: 2}
|
||||
position: {x: 11.82, y: -1.98}
|
||||
rotationZ: 0
|
||||
runwayId: 1
|
||||
landingDirectionMode: 0
|
||||
presetForwardOverride: {x: 1, y: 0}
|
||||
spawnAnchors: []
|
||||
- preset: {fileID: 11400000, guid: fd619444b585428478fde4ac10a9dffb, type: 2}
|
||||
position: {x: -12.41, y: -1.98}
|
||||
rotationZ: 0
|
||||
runwayId: 1
|
||||
landingDirectionMode: 0
|
||||
presetForwardOverride: {x: 1, y: 0}
|
||||
spawnAnchors: []
|
||||
spawnerRules:
|
||||
- spawnerId: A
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
type: 0
|
||||
planePrefabsOverride:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
overrideTiming: 1
|
||||
randomizeInterval: 1
|
||||
spawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 28
|
||||
- spawnerId: B
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
type: 1
|
||||
planePrefabsOverride:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
overrideTiming: 1
|
||||
randomizeInterval: 1
|
||||
spawnInterval: 2
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 28
|
||||
- spawnerId: C
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
type: 2
|
||||
planePrefabsOverride:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
overrideTiming: 1
|
||||
randomizeInterval: 1
|
||||
spawnInterval: 2
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 28
|
||||
typeCaps: []
|
||||
randomPickWhenExceedCap: 1
|
||||
waves:
|
||||
- waveId: 1
|
||||
startTime: 3
|
||||
duration: 500
|
||||
entries:
|
||||
- planeType: 0
|
||||
count: 28
|
||||
spawnRate: 7
|
||||
spawnPosition: {x: 0, y: 0}
|
||||
alignWithRunwayForward: 1
|
||||
initialDirection: {x: 0, y: 0}
|
||||
initialSpeed: 0
|
||||
targetRunwayId: 1
|
||||
scoreValue: 0
|
||||
allowCurves: 0
|
||||
separationNoise: 0
|
||||
useRunwayAnchor: 0
|
||||
fromRunwayId: RWY_A
|
||||
anchorId: A1
|
||||
angleOffset: 0
|
||||
- planeType: 1
|
||||
count: 1000
|
||||
spawnRate: 7
|
||||
spawnPosition: {x: 0, y: 0}
|
||||
alignWithRunwayForward: 1
|
||||
initialDirection: {x: 0, y: 0}
|
||||
initialSpeed: 0
|
||||
targetRunwayId: 1
|
||||
scoreValue: 0
|
||||
allowCurves: 0
|
||||
separationNoise: 0
|
||||
useRunwayAnchor: 0
|
||||
fromRunwayId: RWY_A
|
||||
anchorId: A1
|
||||
angleOffset: 0
|
||||
- planeType: 2
|
||||
count: 1000
|
||||
spawnRate: 7
|
||||
spawnPosition: {x: 0, y: 0}
|
||||
alignWithRunwayForward: 1
|
||||
initialDirection: {x: 0, y: 0}
|
||||
initialSpeed: 0
|
||||
targetRunwayId: 1
|
||||
scoreValue: 0
|
||||
allowCurves: 0
|
||||
separationNoise: 0
|
||||
useRunwayAnchor: 0
|
||||
fromRunwayId: RWY_A
|
||||
anchorId: A1
|
||||
angleOffset: 0
|
||||
weather: {fileID: 11400000, guid: 027a9c7e8ea49fe44bed2df133d42d5f, type: 2}
|
||||
objectives:
|
||||
- type: 1
|
||||
targetValue: 1
|
||||
tolerance: 1
|
||||
note: 1
|
||||
timeLimitSeconds: 0
|
||||
lives: 1
|
||||
startingScore: 0
|
||||
scoreTarget: 0
|
||||
speedMultiplier: 1
|
||||
spawnRateMultiplier: 1
|
||||
scoreMultiplier: 1
|
||||
useSimpleTutorialUI: 0
|
||||
dialog: {fileID: 0}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e3f058d9125913449b0a15767a2bf05
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 957313a006f1cd84a9dae79ecc62f738, type: 3}
|
||||
m_Name: RunwayPresetDiagonal
|
||||
m_EditorClassIdentifier:
|
||||
runwayType: 0
|
||||
runwayPrefab: {fileID: 4776354397367489766, guid: 2d2a3835ba639e041bd1d4fed715033f, type: 3}
|
||||
landingDirection: {x: 0, y: 0}
|
||||
defaultForward: {x: 0, y: 0}
|
||||
prefabVisualZOffset: 0
|
||||
visualRootName: Visual
|
||||
rotateVisualOnly: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccc1dcfd545fbfc4f801e8957e21bbb5
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 957313a006f1cd84a9dae79ecc62f738, type: 3}
|
||||
m_Name: RunwayPresetHelipuerto
|
||||
m_EditorClassIdentifier:
|
||||
runwayType: 0
|
||||
runwayPrefab: {fileID: 6233147436181382244, guid: a648fec6188409c4697f42ecf5f30933, type: 3}
|
||||
landingDirection: {x: 0, y: 0}
|
||||
defaultForward: {x: 1, y: 0}
|
||||
prefabVisualZOffset: 0
|
||||
visualRootName: Visual
|
||||
rotateVisualOnly: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd619444b585428478fde4ac10a9dffb
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 957313a006f1cd84a9dae79ecc62f738, type: 3}
|
||||
m_Name: RunwayPresetRecta
|
||||
m_EditorClassIdentifier:
|
||||
runwayType: 0
|
||||
runwayPrefab: {fileID: 451076641906574467, guid: 26042d6d00aba0c49ba58968d1f54176, type: 3}
|
||||
entryLocal: {x: 1, y: 0}
|
||||
finishLocal: {x: 0, y: 0}
|
||||
landingDirection: {x: 0, y: 0}
|
||||
defaultForward: {x: 1, y: 0}
|
||||
prefabVisualZOffset: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfbd1d2b3572c734ebbe3b6e2e94746f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d2d4f6b9206e7164fb0041fac5b761ef, type: 3}
|
||||
m_Name: TilesetPresetSummerIslands
|
||||
m_EditorClassIdentifier:
|
||||
mapPrefab: {fileID: 198719004240592418, guid: b90922ad5a8044af7a08e521a532f26e, type: 3}
|
||||
ambience: {fileID: 8300000, guid: 3e572e74eda88d8479e212343c77f8b2, type: 3}
|
||||
ambientLight: {r: 1, g: 1, b: 1, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a5243d3b357e714293207eb8064216e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fc18d27254db9c345888a7fc44f3f8bf, type: 3}
|
||||
m_Name: WeatherProfile
|
||||
m_EditorClassIdentifier:
|
||||
weatherType: 0
|
||||
visibility: 1
|
||||
windDirection: {x: 1, y: 0}
|
||||
windSpeed: 0
|
||||
gustiness: 0
|
||||
turbulence: 0
|
||||
lightning: 0
|
||||
night: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 027a9c7e8ea49fe44bed2df133d42d5f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -31,12 +31,42 @@ MonoBehaviour:
|
||||
landingDirectionMode: 0
|
||||
presetForwardOverride: {x: 1, y: 0}
|
||||
spawnAnchors: []
|
||||
spawnerRules: []
|
||||
spawnerRules:
|
||||
- spawnerId: 0
|
||||
enable: 1
|
||||
overrideType: 0
|
||||
type: 0
|
||||
planePrefabsOverride:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
overrideTiming: 0
|
||||
randomizeInterval: 0
|
||||
spawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
maxSpawns: 0
|
||||
typeCaps:
|
||||
- type: 0
|
||||
maxActiveSpawners: 4
|
||||
randomPickWhenExceedCap: 1
|
||||
waves: []
|
||||
waves:
|
||||
- waveId: 0
|
||||
startTime: 0
|
||||
duration: 1
|
||||
entries:
|
||||
- planeType: 0
|
||||
count: 1
|
||||
spawnRate: 0
|
||||
spawnPosition: {x: -20.87, y: 0}
|
||||
alignWithRunwayForward: 0
|
||||
initialDirection: {x: 0, y: 0}
|
||||
initialSpeed: 0
|
||||
targetRunwayId:
|
||||
scoreValue: 0
|
||||
allowCurves: 0
|
||||
separationNoise: 0
|
||||
useRunwayAnchor: 0
|
||||
fromRunwayId:
|
||||
anchorId:
|
||||
angleOffset: 0
|
||||
weather: {fileID: 11400000, guid: 8b14bf9148d48f14e96376c9c42ad8e1, type: 2}
|
||||
objectives:
|
||||
- type: 1
|
||||
@@ -54,11 +84,11 @@ MonoBehaviour:
|
||||
useDynamicSpawning: 1
|
||||
dynamic:
|
||||
enabled: 1
|
||||
baseInterval: 3
|
||||
baseInterval: 0.68630344
|
||||
intervalRange: {x: 1.5, y: 4}
|
||||
startDelay: 0.5
|
||||
maxActivePlane: 6
|
||||
maxActiveHelicopter: 2
|
||||
maxActivePlane: 20
|
||||
maxActiveHelicopter: 16
|
||||
rampEverySeconds: 35
|
||||
intervalMultiplierPerRamp: 0.9
|
||||
extraPlanePerRamp: 1
|
||||
@@ -68,8 +98,73 @@ MonoBehaviour:
|
||||
forceBlueFirstPlane: 1
|
||||
firstPlaneType: 0
|
||||
firstPlaneTint: {r: 0.2, g: 0.9, b: 1, a: 1}
|
||||
difficultyHorizonSeconds: 120
|
||||
globalSpawnRate:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0
|
||||
outWeight: 0
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 1.0078583
|
||||
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: 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
|
||||
dynamicPools:
|
||||
- type: 0
|
||||
prefabs:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
weight: 0.1
|
||||
maxAlive: 0
|
||||
baseSpawnInterval: 0
|
||||
intervalRange: {x: 0, y: 0}
|
||||
speedRange: {x: 0, y: 0}
|
||||
spawnIntervalOverTime:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
speedMultiplierOverTime:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
|
||||
@@ -59,9 +59,10 @@ public class LevelConfig : ScriptableObject
|
||||
public float spawnRateMultiplier = 1f;
|
||||
public float scoreMultiplier = 1f;
|
||||
|
||||
[Header("Tutorial")]
|
||||
[Tooltip("solo si es necesario para activar tutorial")]
|
||||
public bool isTutorial;
|
||||
// ================== 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á.")]
|
||||
public bool isTutorial = false;
|
||||
|
||||
// =====================================================================
|
||||
// ====================== DYNAMIC SPAWNER (DIFICULTAD) ==================
|
||||
@@ -77,65 +78,100 @@ 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("Estado")]
|
||||
public bool enabled = true;
|
||||
|
||||
[Header("Ritmo base")]
|
||||
[Tooltip("Intervalo medio de spawn (seg).")]
|
||||
public float baseInterval = 3.0f;
|
||||
[Header("Ritmo base")]
|
||||
[Tooltip("Intervalo medio de spawn (seg).")]
|
||||
public float baseInterval = 3.0f;
|
||||
|
||||
[Tooltip("Rango aleatorio del intervalo (seg).")]
|
||||
public Vector2 intervalRange = new Vector2(1.5f, 4.0f);
|
||||
[Tooltip("Rango aleatorio del intervalo (seg).")]
|
||||
public Vector2 intervalRange = new Vector2(1.5f, 4.0f);
|
||||
|
||||
[Tooltip("Delay inicial antes del primer spawn (seg).")]
|
||||
public float startDelay = 0.5f;
|
||||
[Tooltip("Delay inicial antes del primer spawn (seg).")]
|
||||
public float startDelay = 0.5f;
|
||||
|
||||
[Header("Capacidad")]
|
||||
[Tooltip("Máximo de aviones (Plane) activos simultáneamente.")]
|
||||
public int maxActivePlane = 6;
|
||||
[Header("Capacidad")]
|
||||
[Tooltip("Máximo de aviones (Plane) activos simultáneamente.")]
|
||||
public int maxActivePlane = 6;
|
||||
|
||||
[Tooltip("Máximo de helicópteros (Helicopter) activos simultáneamente.")]
|
||||
public int maxActiveHelicopter = 2;
|
||||
[Tooltip("Máximo de helicópteros (Helicopter) activos simultáneamente.")]
|
||||
public int maxActiveHelicopter = 2;
|
||||
|
||||
[Header("Ramp-up (dificultad)")]
|
||||
[Tooltip("Cada cuántos segundos se acelera y aumenta la capacidad.")]
|
||||
public float rampEverySeconds = 35f;
|
||||
[Header("Ramp-up (dificultad)")]
|
||||
[Tooltip("Cada cuántos segundos se acelera y aumenta la capacidad.")]
|
||||
public float rampEverySeconds = 35f;
|
||||
|
||||
[Tooltip("Multiplicador por ramp para reducir el intervalo (0.9 = 10% más rápido).")]
|
||||
public float intervalMultiplierPerRamp = 0.9f;
|
||||
[Tooltip("Multiplicador por ramp para reducir el intervalo (0.9 = 10% más rápido).")]
|
||||
public float intervalMultiplierPerRamp = 0.9f;
|
||||
|
||||
[Tooltip("Aumento de capacidad por ramp para Plane.")]
|
||||
public int extraPlanePerRamp = 1;
|
||||
[Tooltip("Aumento de capacidad por ramp para Plane.")]
|
||||
public int extraPlanePerRamp = 1;
|
||||
|
||||
[Tooltip("Aumento de capacidad por ramp para Helicopter.")]
|
||||
public int extraHeliPerRamp = 1;
|
||||
[Tooltip("Aumento de capacidad por ramp para Helicopter.")]
|
||||
public int extraHeliPerRamp = 1;
|
||||
|
||||
[Header("Borde / Cámara")]
|
||||
[Tooltip("Spawn fuera de cámara (true) o pegado al borde interno (false).")]
|
||||
public bool spawnOutsideCameraEdges = true;
|
||||
[Header("Borde / Cámara")]
|
||||
[Tooltip("Spawn fuera de cámara (true) o pegado al borde interno (false).")]
|
||||
public bool spawnOutsideCameraEdges = true;
|
||||
|
||||
[Tooltip("Inset en mundo desde el borde de cámara.")]
|
||||
public float edgeInsetWorld = 0.35f;
|
||||
[Tooltip("Inset en mundo desde el borde de cámara.")]
|
||||
public float edgeInsetWorld = 0.35f;
|
||||
|
||||
[Header("Primer avión (opcional)")]
|
||||
[Tooltip("Forzar primer spawn como 'azul' (para onboarding).")]
|
||||
public bool forceBlueFirstPlane = false;
|
||||
[Header("Primer avión (opcional)")]
|
||||
[Tooltip("Forzar primer spawn como 'azul' (para onboarding).")]
|
||||
public bool forceBlueFirstPlane = false;
|
||||
|
||||
public PlaneType firstPlaneType = PlaneType.Plane;
|
||||
public Color firstPlaneTint = new Color(0.2f, 0.9f, 1f, 1f);
|
||||
}
|
||||
public PlaneType firstPlaneType = PlaneType.Plane;
|
||||
public Color firstPlaneTint = new Color(0.2f, 0.9f, 1f, 1f);
|
||||
|
||||
[Serializable]
|
||||
public class DynamicPool
|
||||
{
|
||||
public PlaneType type = PlaneType.Plane;
|
||||
public GameObject[] prefabs;
|
||||
[Range(0.01f, 10f)] public float weight = 1f;
|
||||
}
|
||||
// ====== 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;
|
||||
|
||||
[Tooltip("Multiplicador global de spawn rate (1 = sin cambio).")]
|
||||
public AnimationCurve globalSpawnRate = AnimationCurve.Linear(0, 1, 1, 1);
|
||||
|
||||
[Tooltip("Multiplicador global de velocidad (1 = sin cambio).")]
|
||||
public AnimationCurve globalSpeedMultiplier = AnimationCurve.Linear(0, 1, 1, 1);
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class DynamicPool
|
||||
{
|
||||
public PlaneType type = PlaneType.Plane;
|
||||
public GameObject[] prefabs;
|
||||
[Range(0.01f, 10f)] public float weight = 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;
|
||||
|
||||
[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 ==================================
|
||||
// =====================================================================
|
||||
@@ -243,4 +279,4 @@ public class LevelConfig : ScriptableObject
|
||||
public int tolerance = 0;
|
||||
public string note;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class LevelLoader : MonoBehaviour
|
||||
|
||||
public System.Action<LevelConfig> OnLevelLoaded;
|
||||
|
||||
// <- Este flag es el que usan desde GameManager para encender/apagar el overlay simple
|
||||
[Tooltip("Flag de conveniencia para saber si el nivel cargado es de tutorial.")]
|
||||
public bool tutorialActivated;
|
||||
|
||||
struct AnchorInfo
|
||||
@@ -46,9 +46,9 @@ public class LevelLoader : MonoBehaviour
|
||||
{
|
||||
CleanupScene();
|
||||
|
||||
// 🔧 FIX: primero asignamos el level actual y recién después leemos sus flags
|
||||
// ⚠️ ANTES: se leía currentLevel.isTutorial ANTES de asignar currentLevel → siempre mal
|
||||
currentLevel = cfg;
|
||||
tutorialActivated = (currentLevel != null) && currentLevel.isTutorial;
|
||||
tutorialActivated = (currentLevel != null && currentLevel.isTutorial);
|
||||
|
||||
// Tileset
|
||||
if (cfg.tileset && cfg.tileset.mapPrefab)
|
||||
@@ -74,21 +74,20 @@ public class LevelLoader : MonoBehaviour
|
||||
float logicZ = r.rotationZ;
|
||||
go.transform.rotation = Quaternion.Euler(0, 0, logicZ);
|
||||
|
||||
// 2) Offset VISUAL (NO debe afectar Entry/Finish)
|
||||
// 2) Offset visual (si el prefab lo requiere)
|
||||
ApplyVisualOffset(go.transform, r.preset);
|
||||
|
||||
// Leer Entry/Finish del PREFAB (sólo lectura)
|
||||
// Leer Entry/Finish del PREFAB
|
||||
var entry = FindChildDeep(go.transform, "Entry");
|
||||
var finish = FindChildDeep(go.transform, "Finish");
|
||||
|
||||
if (finish != null) runwayFinish[r.runwayId] = finish;
|
||||
|
||||
// FORWARD (LandingDirection) — por posición Entry→Finish
|
||||
// FORWARD (por marcadores o por rotación)
|
||||
Vector2 forward;
|
||||
if (r.landingDirectionMode == LandingDirectionMode.FromPrefabMarkers && entry != null && finish != null)
|
||||
{
|
||||
forward = (finish.position - entry.position).normalized;
|
||||
|
||||
if (forward.sqrMagnitude < 0.0001f)
|
||||
{
|
||||
Debug.LogWarning($"[{r.runwayId}] Entry y Finish superpuestos. Usando forward por rotación del root.");
|
||||
@@ -109,7 +108,7 @@ public class LevelLoader : MonoBehaviour
|
||||
}
|
||||
runwayForward[r.runwayId] = forward.sqrMagnitude < 0.001f ? Vector2.right : forward.normalized;
|
||||
|
||||
// ANCLAS (spawners)
|
||||
// ANCLAS (spawners) → locales a la pista → convertir a mundo según root (NO offset visual)
|
||||
foreach (var a in r.spawnAnchors)
|
||||
{
|
||||
var worldPos = go.transform.TransformPoint(a.localPosition);
|
||||
@@ -122,18 +121,6 @@ public class LevelLoader : MonoBehaviour
|
||||
runwayForward = runwayForward[r.runwayId]
|
||||
};
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (entry && finish)
|
||||
{
|
||||
Debug.DrawLine(entry.position, finish.position, Color.green, 3f);
|
||||
Debug.Log($"[{r.runwayId}] Entry:{entry.position} Finish:{finish.position} Forward:{runwayForward[r.runwayId]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[{r.runwayId}] NO se encontraron Entry/Finish en el prefab.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Ambient
|
||||
@@ -188,13 +175,11 @@ public class LevelLoader : MonoBehaviour
|
||||
|
||||
if (visual != null)
|
||||
{
|
||||
// Rotación local del hijo visual: NO afecta Entry/Finish
|
||||
visual.localRotation = Quaternion.Euler(0, 0, preset.prefabVisualZOffset) * visual.localRotation;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: si no hay hijo visual, rotamos el root (esto sí afectaría Entry/Finish)
|
||||
runwayRoot.rotation = Quaternion.Euler(0, 0, runwayRoot.eulerAngles.z + preset.prefabVisualZOffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -341,6 +341,9 @@ MonoBehaviour:
|
||||
isLanding: 0
|
||||
noCol: 0
|
||||
scoreValue: 1
|
||||
edgePaddingWorld: 0.5
|
||||
bounceStrength: 1.5
|
||||
viewportMargin: 0.02
|
||||
oscillationAmplitude: 0.5
|
||||
oscillationFrequency: 1
|
||||
autoResolveCameraBounds: 1
|
||||
|
||||
@@ -1036,7 +1036,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 1}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: -162, y: -60}
|
||||
m_AnchoredPosition: {x: -198, y: -50}
|
||||
m_SizeDelta: {x: 200, y: 50}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &280612377
|
||||
@@ -3353,6 +3353,55 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 822435449}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &862270259
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 862270261}
|
||||
- component: {fileID: 862270260}
|
||||
m_Layer: 0
|
||||
m_Name: spawn
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &862270260
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 862270259}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 62cc583ebfacb4e68bd5c8012df8f638, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
running: 0
|
||||
usingCfg: 0
|
||||
verbose: 1
|
||||
statusLogEvery: 10
|
||||
spawnMarginPixels: 500
|
||||
--- !u!4 &862270261
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 862270259}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 726.364, y: 403.8052, z: -10.0989}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &931356569
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4637,7 +4686,6 @@ GameObject:
|
||||
- component: {fileID: 1399668022}
|
||||
- component: {fileID: 1399668021}
|
||||
- component: {fileID: 1399668020}
|
||||
- component: {fileID: 1399668027}
|
||||
m_Layer: 0
|
||||
m_Name: MainManager
|
||||
m_TagString: Untagged
|
||||
@@ -4756,36 +4804,6 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &1399668027
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1399668019}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 62cc583ebfacb4e68bd5c8012df8f638, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
verbose: 1
|
||||
heartbeatLogs: 1
|
||||
heartbeatEvery: 2
|
||||
autoStartIfNoConfig: 1
|
||||
spawnOutsideCameraEdges: 1
|
||||
edgeInsetWorld: 0.8
|
||||
headingJitterDeg: 8
|
||||
planePrefabsFallback:
|
||||
- {fileID: 8887022185975264338, guid: 1f94160a177d8411984fe48801bb9541, type: 3}
|
||||
heliPrefabsFallback: []
|
||||
baseIntervalFallback: 2
|
||||
intervalRangeFallback: {x: 1, y: 3}
|
||||
startDelayFallback: 0.2
|
||||
maxActivePlaneFallback: 4
|
||||
maxActiveHelicopterFallback: 1
|
||||
forceBlueFirstPlane: 0
|
||||
firstPlaneType: 1
|
||||
firstPlaneTint: {r: 0.25, g: 0.8, b: 1, a: 1}
|
||||
--- !u!1 &1520870961
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4795,7 +4813,6 @@ GameObject:
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1520870963}
|
||||
- component: {fileID: 1520870968}
|
||||
- component: {fileID: 1520870971}
|
||||
- component: {fileID: 1520870970}
|
||||
- component: {fileID: 1520870969}
|
||||
@@ -4821,18 +4838,6 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &1520870968
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1520870961}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 133f96389d108b64bb7bc8fcd871a5aa, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!114 &1520870969
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -5369,7 +5374,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UiScaleMode: 0
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
@@ -6064,7 +6069,7 @@ MonoBehaviour:
|
||||
repeatLog: 1
|
||||
repeatEverySeconds: 5
|
||||
createDynamicIfNoneFound: 1
|
||||
dynamicSpawnerPrefab: {fileID: 1399668027}
|
||||
dynamicSpawnerPrefab: {fileID: 0}
|
||||
--- !u!1 &2102060756
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -6237,7 +6242,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!224 &2109539263
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -6522,3 +6527,4 @@ SceneRoots:
|
||||
- {fileID: 401981673}
|
||||
- {fileID: 1334721174}
|
||||
- {fileID: 2058553546}
|
||||
- {fileID: 862270261}
|
||||
|
||||
+2133
-64
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,7 @@ public class GameManager : MonoBehaviour
|
||||
private DynamicSpawnerSystem dynSpawner;
|
||||
|
||||
[Header("Tutorial GO")]
|
||||
[Tooltip("Asigna aquí tu overlay/canvas de tutorial. Se activará si LevelConfig.isTutorial = true.")]
|
||||
public GameObject tutorialUI;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -298,12 +299,7 @@ public class GameManager : MonoBehaviour
|
||||
{
|
||||
var c = t.GetComponent<Canvas>();
|
||||
if (c != null) { t.gameObject.SetActive(false); return; }
|
||||
// Case-insensitive sin usar .ToLower() ni .contains
|
||||
if (t.name.IndexOf("gameover", System.StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
t.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
if (t.name.ToLower().Contains("gameover")) { t.gameObject.SetActive(false); return; }
|
||||
t = t.parent;
|
||||
}
|
||||
}
|
||||
@@ -425,11 +421,12 @@ public class GameManager : MonoBehaviour
|
||||
var loader = FindObjectOfType<LevelLoader>(true);
|
||||
if (loader) loader.Load(cfg);
|
||||
|
||||
// 🔧 Aquí se decide si mostrar el overlay/tutorial simple
|
||||
bool tutorialFlag = (loader != null) ? loader.tutorialActivated : cfg.isTutorial;
|
||||
if (tutorialUI != null) tutorialUI.SetActive(tutorialFlag);
|
||||
// ---------- TUTORIAL ----------
|
||||
// Mostrar/ocultar tu overlay según LO QUE DICE EL SO (cfg.isTutorial)
|
||||
if (tutorialUI != null)
|
||||
tutorialUI.SetActive(cfg.isTutorial);
|
||||
|
||||
// Dynamic Spawner o spawners clásicos
|
||||
// ---------- Dynamic Spawner o spawners clásicos ----------
|
||||
StartCoroutine(CoChooseSpawnerNextFrame(cfg));
|
||||
}
|
||||
|
||||
@@ -443,7 +440,7 @@ public class GameManager : MonoBehaviour
|
||||
{
|
||||
dynSpawner.gameObject.SetActive(true);
|
||||
|
||||
// SAFE STOP: evita dependencia de nombres de método
|
||||
// SAFE STOP por si venís de otra escena/estado
|
||||
SafeStopDynamicSpawner(dynSpawner);
|
||||
|
||||
// Iniciar con el LevelConfig actual
|
||||
@@ -452,13 +449,13 @@ public class GameManager : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: aplica reglas a PlaneSpawners en escena
|
||||
// Fallback: aplica reglas a spawners clásicos
|
||||
ApplySpawnerRules(cfg);
|
||||
Debug.Log("[GameManager] PlaneSpawners clásicos activos (Dynamic desactivado o no presente).");
|
||||
}
|
||||
}
|
||||
|
||||
// Evita errores si tu spawner no tiene el mismo método
|
||||
// Evita errores si el spawner no define los mismos métodos entre ramas
|
||||
private void SafeStopDynamicSpawner(DynamicSpawnerSystem sp)
|
||||
{
|
||||
if (!sp) return;
|
||||
|
||||
@@ -21,9 +21,18 @@ public class PlaneController : MonoBehaviour
|
||||
|
||||
[Header("Contención en cámara")]
|
||||
public float edgePaddingWorld = 0.5f;
|
||||
public float bounceStrength = 1.5f;
|
||||
public float bounceStrength = 1.5f; // (se conserva para compatibilidad, ya no se usa en el reflect)
|
||||
[Range(0f, 0.2f)] public float viewportMargin = 0.02f;
|
||||
|
||||
[Header("Bounces")]
|
||||
[Tooltip("Segundos sin oscilación después de rebotar.")]
|
||||
public float bounceCooldown = 0.2f;
|
||||
[Tooltip("Empujón mínimo hacia adentro tras el rebote (unidades mundo).")]
|
||||
public float edgeNudge = 0.02f;
|
||||
[Tooltip("Apagar oscilación mientras dure el bounceCooldown.")]
|
||||
public bool disableOscillationDuringBounce = true;
|
||||
private float bounceTimer = 0f;
|
||||
|
||||
[Header("Oscilación de crucero")]
|
||||
public float oscillationAmplitude = 0.5f;
|
||||
public float oscillationFrequency = 1f;
|
||||
@@ -39,6 +48,10 @@ public class PlaneController : MonoBehaviour
|
||||
public Vector2 fallbackBoundsMax = new Vector2( 50f, 30f);
|
||||
public float minBoundsSize = 0.1f;
|
||||
|
||||
// Activa los límites sólo cuando el avión está completamente en pantalla
|
||||
private bool hasEnteredCamera = false;
|
||||
public bool enableBoundsAfterEntry = true;
|
||||
|
||||
public GameObject proximityCircle;
|
||||
public float proximityRadius = 2f;
|
||||
|
||||
@@ -120,6 +133,9 @@ public class PlaneController : MonoBehaviour
|
||||
ResolveCameraBounds(force:false);
|
||||
}
|
||||
|
||||
// Cooldown de rebote (para desactivar oscilación momentáneamente)
|
||||
if (bounceTimer > 0f) bounceTimer -= Time.deltaTime;
|
||||
|
||||
tiempo += Time.deltaTime;
|
||||
|
||||
if (allowEmergency && !inEmergency && !isLanding && tiempo >= autoEmergencyAfter)
|
||||
@@ -148,10 +164,38 @@ public class PlaneController : MonoBehaviour
|
||||
if (path != null && pathIndex < path.Length) FollowPath();
|
||||
else ContinueMovement();
|
||||
|
||||
KeepWithinBounds();
|
||||
// Aplicar límites sólo cuando esté completamente dentro del visor
|
||||
if (enableBoundsAfterEntry)
|
||||
{
|
||||
if (!hasEnteredCamera)
|
||||
{
|
||||
if (IsFullyInsideCamera())
|
||||
hasEnteredCamera = true;
|
||||
}
|
||||
|
||||
if (hasEnteredCamera)
|
||||
KeepWithinBounds();
|
||||
}
|
||||
else
|
||||
{
|
||||
KeepWithinBounds();
|
||||
}
|
||||
|
||||
CheckProximity();
|
||||
}
|
||||
|
||||
private bool IsFullyInsideCamera()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (cam == null) return true;
|
||||
|
||||
var vp = cam.WorldToViewportPoint(transform.position);
|
||||
if (vp.z < 0) return false;
|
||||
|
||||
float m = viewportMargin;
|
||||
return (vp.x >= 0f + m && vp.x <= 1f - m && vp.y >= 0f + m && vp.y <= 1f - m);
|
||||
}
|
||||
|
||||
void KeepWithinBounds()
|
||||
{
|
||||
Camera cam = Camera.main;
|
||||
@@ -170,40 +214,46 @@ public class PlaneController : MonoBehaviour
|
||||
if (minY > maxY) { float m = (minY + maxY) * 0.5f; minY = maxY = m; }
|
||||
|
||||
Vector3 pos = transform.position;
|
||||
bool bouncedX = false;
|
||||
bool bouncedY = false;
|
||||
bool bounced = false;
|
||||
|
||||
// Rebotar en X
|
||||
if (pos.x < minX)
|
||||
{
|
||||
pos.x = minX;
|
||||
currentDirection.x = Mathf.Abs(currentDirection.x) * bounceStrength;
|
||||
bouncedX = true;
|
||||
pos.x = minX + edgeNudge; // nudge hacia adentro
|
||||
currentDirection = Vector2.Reflect(currentDirection, Vector2.right).normalized; // normal +X
|
||||
bounced = true;
|
||||
}
|
||||
else if (pos.x > maxX)
|
||||
{
|
||||
pos.x = maxX;
|
||||
currentDirection.x = -Mathf.Abs(currentDirection.x) * bounceStrength;
|
||||
bouncedX = true;
|
||||
pos.x = maxX - edgeNudge;
|
||||
currentDirection = Vector2.Reflect(currentDirection, Vector2.left).normalized; // normal -X
|
||||
bounced = true;
|
||||
}
|
||||
|
||||
// Rebotar en Y
|
||||
if (pos.y < minY)
|
||||
{
|
||||
pos.y = minY;
|
||||
currentDirection.y = Mathf.Abs(currentDirection.y) * bounceStrength;
|
||||
bouncedY = true;
|
||||
pos.y = minY + edgeNudge;
|
||||
currentDirection = Vector2.Reflect(currentDirection, Vector2.up).normalized; // normal +Y
|
||||
bounced = true;
|
||||
}
|
||||
else if (pos.y > maxY)
|
||||
{
|
||||
pos.y = maxY;
|
||||
currentDirection.y = -Mathf.Abs(currentDirection.y) * bounceStrength;
|
||||
bouncedY = true;
|
||||
pos.y = maxY - edgeNudge;
|
||||
currentDirection = Vector2.Reflect(currentDirection, Vector2.down).normalized; // normal -Y
|
||||
bounced = true;
|
||||
}
|
||||
|
||||
if (bouncedX || bouncedY)
|
||||
if (bounced)
|
||||
{
|
||||
if (currentDirection.sqrMagnitude < 1e-4f) currentDirection = Vector3.right;
|
||||
currentDirection = currentDirection.normalized;
|
||||
|
||||
// Reset de oscilación y cooldown para despegarse del borde
|
||||
oscillationTime = 0f;
|
||||
bounceTimer = bounceCooldown;
|
||||
|
||||
// Alinear rotación inmediata con la nueva dirección
|
||||
float angle = Mathf.Atan2(currentDirection.y, currentDirection.x) * Mathf.Rad2Deg - 90f;
|
||||
transform.rotation = Quaternion.Euler(0, 0, angle);
|
||||
}
|
||||
@@ -215,10 +265,20 @@ public class PlaneController : MonoBehaviour
|
||||
{
|
||||
Vector3 direction = currentDirection.sqrMagnitude < 1e-6f ? Vector3.right : currentDirection.normalized;
|
||||
|
||||
Vector3 perpendicular = Vector3.Cross(direction, Vector3.forward).normalized;
|
||||
oscillationTime += Time.deltaTime * oscillationFrequency;
|
||||
float oscillationOffset = Mathf.Sin(oscillationTime) * oscillationAmplitude;
|
||||
Vector3 finalDirection = (direction + perpendicular * oscillationOffset).normalized;
|
||||
Vector3 finalDirection;
|
||||
|
||||
// Durante el cooldown de rebote, evitamos oscilación para que se despegue del borde
|
||||
if (disableOscillationDuringBounce && bounceTimer > 0f)
|
||||
{
|
||||
finalDirection = direction; // sin componente perpendicular
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 perpendicular = Vector3.Cross(direction, Vector3.forward).normalized;
|
||||
oscillationTime += Time.deltaTime * oscillationFrequency;
|
||||
float oscillationOffset = Mathf.Sin(oscillationTime) * oscillationAmplitude;
|
||||
finalDirection = (direction + perpendicular * oscillationOffset).normalized;
|
||||
}
|
||||
|
||||
float angle = Mathf.Atan2(finalDirection.y, finalDirection.x) * Mathf.Rad2Deg - 90f;
|
||||
Quaternion targetRotation = Quaternion.Euler(0, 0, angle);
|
||||
@@ -326,7 +386,7 @@ public class PlaneController : MonoBehaviour
|
||||
if (GameManager.Instance != null) GameManager.Instance.scoreGral += scoreValue;
|
||||
GameManager.Instance?.NotifyPlaneLanded(planeType, targetRunway != null ? targetRunway.name : "");
|
||||
|
||||
// (tutorial eliminado) — antes: TutorialEvents.RaiseLanding(...)
|
||||
// (tutorial eliminado)
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
@@ -462,4 +522,4 @@ public class PlaneController : MonoBehaviour
|
||||
{
|
||||
return (maxBounds.x - minBounds.x) > minBoundsSize && (maxBounds.y - minBounds.y) > minBoundsSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user