Documentation Gael - j5x/PvB2025 GitHub Wiki
A self-contained scene and manager that asynchronously loads any target scene while updating a UI progress bar and optional percentage text. It enforces a minimum display time so the bar never snaps instantly to 100%.
A tiny static helper for passing “which scene to load next” into the LoadingScene.
public static class GameLoader
{
/// <summary>
/// Assign this before loading the LoadingScene.
/// </summary>
public static string NextSceneName;
}
-
Read GameLoader.NextSceneName on Start().
-
Kick off an AsyncOperation for that scene.
-
Update a Slider (progressBar) and TextMeshProUGUI (progressText) each frame.
-
Enforce a minimum load-screen duration (minLoadTime) even if the target scene loads faster.
-
Activate the new scene once both loading and the delay have completed.
public class LoadingScreenManager : MonoBehaviour
{
[Header("UI References")]
[SerializeField] private Slider progressBar;
[SerializeField] private TextMeshProUGUI progressText;
[Header("Timing")]
[Tooltip("Minimum seconds to show the bar filling to 100%")]
[SerializeField] private float minLoadTime = 2f;
private void Start() {
string scene = GameLoader.NextSceneName;
if (string.IsNullOrEmpty(scene)) {
Debug.LogError("NextSceneName not set!");
return;
}
StartCoroutine(LoadWithMinimumTime(scene));
}
private IEnumerator LoadWithMinimumTime(string targetScene) {
var op = SceneManager.LoadSceneAsync(targetScene);
op.allowSceneActivation = false;
float start = Time.time;
while (true) {
float raw = Mathf.Clamp01(op.progress / 0.9f);
float timed = Mathf.Clamp01((Time.time - start) / minLoadTime);
float display = Mathf.Min(raw, timed);
progressBar.value = display;
progressText.text = $"{Mathf.RoundToInt(display * 100f)}%";
if (op.progress >= 0.9f && Time.time - start >= minLoadTime) {
op.allowSceneActivation = true;
yield break;
}
yield return null;
}
}
}
- GameLoader
- No Inspector component; just call
GameLoader.NextSceneName = "YourGameplayScene";
SceneManager.LoadScene("LoadingScene");
-
Progress Bar → assign your UI Slider.
-
Progress Text → assign any TextMeshProUGUI (optional).
-
Min Load Time → tweak how long the bar takes to fill.
A simple “three-round” timer that counts down, spawns up to N enemies per round, advances rounds on enemy death, and triggers game-over if time expires first.
-
Hold an array of round durations (float[] roundDurations = {45, 45, 60}).
-
Expose two UnityEvents:
-
OnRoundStart(int roundNumber) → UI can update “Round X” displays.
-
OnGameOver() → UI can show game-over screen.
-
Count down in a coroutine.
-
Watch a flag isEnemyDead (set externally) to advance to the next round.
public class RoundTimer : MonoBehaviour
{
public UnityEvent<int> OnRoundStart;
public UnityEvent OnGameOver;
[SerializeField] private TMP_Text timerText;
private float[] roundDurations = {45f, 45f, 60f};
private int currentRound;
private float timeRemaining;
private bool isTimerRunning;
private bool isEnemyDead;
private void Start() => StartRound(0);
private void StartRound(int idx) {
if (idx >= roundDurations.Length) {
Debug.Log("All rounds complete!");
return;
}
currentRound = idx;
timeRemaining = roundDurations[idx];
isTimerRunning = true;
isEnemyDead = false;
OnRoundStart.Invoke(idx + 1);
UpdateTimerUI();
StartCoroutine(TimerCoroutine());
}
private IEnumerator TimerCoroutine() {
while (isTimerRunning && timeRemaining > 0) {
yield return new WaitForSeconds(1f);
timeRemaining--;
UpdateTimerUI();
if (isEnemyDead) {
StartNextRound();
yield break;
}
}
if (timeRemaining <= 0f) OnGameOver.Invoke();
}
private void StartNextRound() {
isTimerRunning = false;
StartRound(currentRound + 1);
}
private void UpdateTimerUI() {
if (timerText) timerText.text = $"Time: {timeRemaining}s";
}
/// <summary>
/// Call this from Enemy’s OnDeath to signal the round is won.
/// </summary>
public void EnemyDefeated() => isEnemyDead = true;
}
-
Spawn up to maxSpawns instances of a given enemyPrefab, each after respawnDelay seconds.
-
Assigns itself (spawner) into the new Enemy so the Enemy can notify it on death.
public class EnemySpawner : MonoBehaviour
{
[SerializeField] private GameObject enemyPrefab;
[SerializeField] private Transform spawnPoint;
[SerializeField] private float respawnDelay = 2f;
[SerializeField] private int maxSpawns = 3;
private int spawnCount;
private GameObject currentEnemy;
private void Start() => SpawnEnemy();
public void SpawnEnemy() {
if (spawnCount >= maxSpawns) return;
StartCoroutine(SpawnWithDelay());
}
private IEnumerator SpawnWithDelay() {
yield return new WaitForSeconds(respawnDelay);
currentEnemy = Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation);
var e = currentEnemy.GetComponent<Enemy>();
if (e != null) e.spawner = this;
spawnCount++;
}
}
-
Hook its OnRoundStart(int) → update your “Round X” UI.
-
Hook its OnGameOver() → show game-over screen or return to menu.
- Set Enemy Prefab, Spawn Point, Respawn Delay, Max Spawns.
- On its HealthComponent.OnDeath, call both:
spawner?.SpawnEnemy();
FindObjectOfType<RoundTimer>()?.EnemyDefeated();
- This signals the RoundTimer that the current enemy died, advancing the round, and tells the spawner to spawn the next.
-
When OnRoundStart(roundIndex) fires, you can:
-
Increase the player’s stats (e.g. healthComponent.ApplyHealthBuff(50, 0)).
-
Or adjust the next enemy’s config (e.g. enemyPrefab.GetComponent().attackConfigs[0].attackDamage += 10).
-
After the third round’s enemies spawn and die, StartRound(3) will “out of bounds” and simply log “All rounds complete!” — tie that to your victory UI or scene-transition call in OnRoundStart or via another UnityEvent.
-
ScriptableObject that defines per-character health parameters:
-
maxHealth
-
canRegenerate
-
regenRate
®enAmount
-
Each character (player or enemy) points at one of these configs so you can tweak HP and regen in the Inspector.
-
Fields
-
baseMaxHealth, currentHealth
-
regenMultiplier, damageMultiplier
-
-
InitializeHealth(HealthConfig) reads the SO into baseMaxHealth & currentHealth.
-
Starts a repeating regen coroutine if enabled.
-
TakeDamage(int amount) → applies multipliers, clamps at 0, fires OnHealthChanged(int) and OnDeath when HP hits zero.
-
Heal(int amount) → clamps at max, fires OnHealthChanged.
-
OnHealthChanged(int) → subscribe UI bars or other feedback.
-
OnDeath → triggers character cleanup or round logic.
-
In Awake() it ensures there’s a HealthComponent, runs InitializeHealth, and hooks OnDeath → Die().
-
Exposes public virtual void TakeDamage(float) which simply calls the HealthComponent.
-
Defines two abstract methods Attack() and Defend() for subclasses to implement.
-
A list of AttackConfig ScriptableObjects, each defining an animation trigger, damage amount, and delay.
-
A flag isAIControlled to auto-loop attacks on enemies.
- Chooses an AttackConfig, fires the Animator trigger, and uses Invoke(ExecuteAttackLogic, delay) to schedule the hit.
-
Looks at character (Player or Enemy) and finds the opposing actor in the scene (FindObjectOfType() or ()).
-
Calls that actor’s TakeDamage(attackDamage).
-
Logs the hit.
-
Match-3 trigger (or a button press) calls Player.Attack(), which under the hood does attackComponent.PerformAttack().
-
The Animator plays the punch/kick animation.
-
After attackDelay seconds, ExecuteAttackLogic() runs and applies damage to the target’s HealthComponent.
-
HealthComponent.TakeDamage() reduces HP, fires OnHealthChanged (UI updates), and if HP ≤ 0, fires OnDeath.
-
OnDeath in the Character base class calls Die(), which cleans up the GameObject (and informs the spawner or round timer).