Create a diving minigame MVP (#6)

- Obstacles
- Tiles
- Object pooling
- Monster spawns
- Scoring
- Minigame End

Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Co-authored-by: AlexanderT <alexander@foolhardyhorizons.com>
Reviewed-on: #6
This commit is contained in:
2025-09-22 12:16:32 +00:00
parent 46755fecb3
commit 5305c20b00
24 changed files with 3466 additions and 165 deletions

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
@@ -48,13 +49,31 @@ namespace Minigames.DivingForPictures
private float _screenBottom;
private float _screenTop;
private TrenchTilePool _tilePool;
// Current velocity for tile movement
private float _currentVelocity;
// Interval for velocity calculations (seconds)
[SerializeField] private float velocityCalculationInterval = 0.5f;
private const float TileSpawnZ = -1f;
private const float DefaultTileHeight = 5f;
// Direction state
private bool _isSurfacing = false;
private bool _stopSpawning = false;
// Event triggered when the last tile leaves the screen after stopping spawning
public UnityEvent onLastTileLeft = new UnityEvent();
// Velocity management
private float _baseMoveSpeed;
private float _velocityFactor = 1.0f;
private void Awake()
{
_mainCamera = Camera.main;
_baseMoveSpeed = moveSpeed; // Store the original base speed
// Calculate tile heights for each prefab
CalculateTileHeights();
@@ -89,15 +108,57 @@ namespace Minigames.DivingForPictures
{
CalculateScreenBounds();
SpawnInitialTiles();
// Initialize velocity and start the velocity calculation coroutine
_currentVelocity = moveSpeed * Time.fixedDeltaTime;
StartCoroutine(VelocityCalculationRoutine());
}
private void Update()
{
MoveTiles();
HandleMovement();
HandleTileDestruction();
HandleTileSpawning();
HandleSpeedRamping();
}
/// <summary>
/// Gets the current velocity of the tiles
/// </summary>
/// <returns>The current upward velocity of the tiles</returns>
public float GetCurrentVelocity()
{
return _currentVelocity;
}
/// <summary>
/// Sets a custom velocity, overriding the calculated one
/// </summary>
/// <param name="velocity">The new velocity value</param>
public void SetVelocity(float velocity)
{
_currentVelocity = velocity;
}
/// <summary>
/// Coroutine that periodically calculates the velocity based on game state
/// </summary>
private IEnumerator VelocityCalculationRoutine()
{
while (true)
{
CalculateVelocity();
yield return new WaitForSeconds(velocityCalculationInterval);
}
}
/// <summary>
/// Calculates the current velocity based on move speed
/// </summary>
private void CalculateVelocity()
{
_currentVelocity = moveSpeed * Time.fixedDeltaTime;
}
/// <summary>
/// Calculate height values for all tile prefabs
@@ -170,9 +231,25 @@ namespace Minigames.DivingForPictures
/// </summary>
private void SpawnInitialTiles()
{
// Calculate starting Y position - moved 2 tiles up from the bottom of the screen
float startingY = _screenBottom;
// If we have prefab tiles with known heights, use their average height for offset calculation
float tileHeightEstimate = DefaultTileHeight;
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null)
{
if (_tileHeights.TryGetValue(tilePrefabs[0], out float height))
{
tileHeightEstimate = height;
}
}
// Move starting position up by 2 tile heights
startingY += tileHeightEstimate * 2;
for (int i = 0; i < initialTileCount; i++)
{
float y = _screenBottom;
float y = startingY;
// Calculate proper Y position based on previous tiles
if (i > 0 && _activeTiles.Count > 0)
{
@@ -206,16 +283,61 @@ namespace Minigames.DivingForPictures
}
/// <summary>
/// Move all active tiles upward
/// Called when the velocity factor changes from the DivingGameManager
/// </summary>
private void MoveTiles()
public void OnVelocityFactorChanged(float velocityFactor)
{
_velocityFactor = velocityFactor;
// Update the actual move speed based on the velocity factor
// This keeps the original move speed intact for game logic
moveSpeed = _baseMoveSpeed * Mathf.Abs(_velocityFactor);
// Recalculate velocity immediately
CalculateVelocity();
Debug.Log($"[TrenchTileSpawner] Velocity factor updated to {_velocityFactor:F2}, moveSpeed: {moveSpeed:F2}");
}
/// <summary>
/// Reverses direction to start surfacing
/// </summary>
public void StartSurfacing()
{
if (_isSurfacing) return; // Already surfacing
// Set surfacing flag for spawn/despawn logic
_isSurfacing = true;
// Reverse the active tiles array to maintain consistent indexing logic
_activeTiles.Reverse();
Debug.Log("[TrenchTileSpawner] Started surfacing - reversed array order");
}
/// <summary>
/// Stops spawning new tiles
/// </summary>
public void StopSpawning()
{
_stopSpawning = true;
}
/// <summary>
/// Handles the movement of all active tiles based on the current velocity
/// </summary>
private void HandleMovement()
{
float moveDelta = moveSpeed * Time.deltaTime;
foreach (var tile in _activeTiles)
{
if (tile != null)
{
tile.transform.position += Vector3.up * moveDelta;
// Use velocity factor to determine direction
Vector3 direction = Vector3.up * Mathf.Sign(_velocityFactor);
float speed = _currentVelocity;
// Apply movement
tile.transform.position += direction * speed;
}
}
}
@@ -235,7 +357,20 @@ namespace Minigames.DivingForPictures
}
float tileHeight = GetTileHeight(topTile);
if (topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer)
bool shouldDestroy;
if (_isSurfacing)
{
// When surfacing, destroy tiles at the bottom
shouldDestroy = topTile.transform.position.y + tileHeight / 2 < _screenBottom - tileSpawnBuffer;
}
else
{
// When descending, destroy tiles at the top
shouldDestroy = topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer;
}
if (shouldDestroy)
{
_activeTiles.RemoveAt(0);
onTileDestroyed?.Invoke(topTile);
@@ -265,7 +400,15 @@ namespace Minigames.DivingForPictures
/// </summary>
private void HandleTileSpawning()
{
if (_activeTiles.Count == 0) return;
if (_activeTiles.Count == 0)
{
// If we have no active tiles and spawning is stopped, trigger the event
if (_stopSpawning)
{
onLastTileLeft.Invoke();
}
return;
}
GameObject bottomTile = _activeTiles[^1];
if (bottomTile == null)
@@ -274,15 +417,60 @@ namespace Minigames.DivingForPictures
return;
}
// Get the tile height once to use in all calculations
float tileHeight = GetTileHeight(bottomTile);
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
if (bottomEdge > _screenBottom - tileSpawnBuffer)
// If we're in stop spawning mode, don't spawn new tiles
if (_stopSpawning)
{
// Check if this is the last tile, and if it's about to leave the screen
if (_activeTiles.Count == 1)
{
bool isLastTileLeaving;
if (_isSurfacing)
{
// When surfacing, check if the bottom of the tile is above the top of the screen
isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer;
}
else
{
// When descending, check if the top of the tile is below the bottom of the screen
isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - tileSpawnBuffer;
}
if (isLastTileLeaving)
{
onLastTileLeft.Invoke();
}
}
return;
}
bool shouldSpawn;
float newY;
if (_isSurfacing)
{
// When surfacing, spawn new tiles at the top
float topEdge = bottomTile.transform.position.y + tileHeight / 2;
shouldSpawn = topEdge < _screenTop + tileSpawnBuffer;
newY = bottomTile.transform.position.y + tileHeight;
}
else
{
// When descending, spawn new tiles at the bottom
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
shouldSpawn = bottomEdge > _screenBottom - tileSpawnBuffer;
newY = bottomTile.transform.position.y - tileHeight;
}
if (shouldSpawn)
{
float newY = bottomTile.transform.position.y - tileHeight;
SpawnTileAtY(newY);
}
}
/// <summary>
/// Handle increasing the movement speed over time
/// </summary>