Add Monster Spawn points, monster head placeholders. Add utility for prefab variation by sprite selection.

This commit is contained in:
2025-09-19 12:51:25 +02:00
parent 31f2ce338d
commit d110616907
29 changed files with 2612 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
using UnityEngine;
using System.Collections.Generic;
using System;
using UnityEngine.Events;
namespace Minigames.DivingForPictures
{
public class DivingGameManager : MonoBehaviour
{
[Header("Monster Prefabs")]
[Tooltip("Array of monster prefabs to spawn randomly")]
[SerializeField] private GameObject[] monsterPrefabs;
[Header("Spawn Probability")]
[Tooltip("Base chance (0-1) of spawning a monster on each tile")]
[SerializeField] private float baseSpawnProbability = 0.2f;
[Tooltip("Maximum chance (0-1) of spawning a monster")]
[SerializeField] private float maxSpawnProbability = 0.5f;
[Tooltip("How fast the probability increases per second")]
[SerializeField] private float probabilityIncreaseRate = 0.01f;
[Header("Spawn Timing")]
[Tooltip("Force a spawn after this many seconds without spawns")]
[SerializeField] private float guaranteedSpawnTime = 30f;
[Tooltip("Minimum time between monster spawns")]
[SerializeField] private float spawnCooldown = 5f;
[Header("Scoring")]
[Tooltip("Base points for taking a picture")]
[SerializeField] private int basePoints = 100;
[Tooltip("Additional points per depth unit")]
[SerializeField] private int depthMultiplier = 10;
// Private state variables
private int playerScore = 0;
private float currentSpawnProbability;
private float lastSpawnTime = -100f;
private float timeSinceLastSpawn = 0f;
private List<Monster> activeMonsters = new List<Monster>();
// Public properties
public int PlayerScore => playerScore;
// Events
public event Action<int> OnScoreChanged;
public event Action<Monster> OnMonsterSpawned;
public event Action<Monster, int> OnPictureTaken;
public event Action<float> OnSpawnProbabilityChanged;
private void Awake()
{
currentSpawnProbability = baseSpawnProbability;
}
private void Start()
{
// Subscribe to tile spawned event
TrenchTileSpawner tileSpawner = FindObjectOfType<TrenchTileSpawner>();
if (tileSpawner != null)
{
tileSpawner.onTileSpawned.AddListener(OnTileSpawned);
}
else
{
Debug.LogWarning("No TrenchTileSpawner found in scene. Monster spawning won't work.");
}
}
private void Update()
{
timeSinceLastSpawn += Time.deltaTime;
// Gradually increase spawn probability over time
float previousProbability = currentSpawnProbability;
if (currentSpawnProbability < maxSpawnProbability)
{
currentSpawnProbability += probabilityIncreaseRate * Time.deltaTime;
currentSpawnProbability = Mathf.Min(currentSpawnProbability, maxSpawnProbability);
// Only fire event if probability changed significantly
if (Mathf.Abs(currentSpawnProbability - previousProbability) > 0.01f)
{
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
}
}
}
private void OnTileSpawned(GameObject tile)
{
// Check for spawn points in the new tile
MonsterSpawnPoint[] spawnPoints = tile.GetComponentsInChildren<MonsterSpawnPoint>();
if (spawnPoints.Length == 0) return;
bool forceSpawn = timeSinceLastSpawn >= guaranteedSpawnTime;
bool onCooldown = timeSinceLastSpawn < spawnCooldown;
// Don't spawn if on cooldown, unless forced
if (onCooldown && !forceSpawn) return;
// Check probability or forced spawn
if (forceSpawn || UnityEngine.Random.value <= currentSpawnProbability)
{
// Pick a random spawn point from this tile
MonsterSpawnPoint spawnPoint = spawnPoints[UnityEngine.Random.Range(0, spawnPoints.Length)];
// Spawn the monster
SpawnMonster(spawnPoint.transform.position);
// Reset timer and adjust probability
lastSpawnTime = Time.time;
timeSinceLastSpawn = 0f;
currentSpawnProbability = baseSpawnProbability;
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
}
}
private void SpawnMonster(Vector3 position)
{
if (monsterPrefabs.Length == 0)
{
Debug.LogWarning("No monster prefabs assigned to DivingGameManager.");
return;
}
// Select random monster prefab
GameObject prefab = monsterPrefabs[UnityEngine.Random.Range(0, monsterPrefabs.Length)];
// Instantiate monster
GameObject monsterObj = Instantiate(prefab, position, Quaternion.identity);
Monster monster = monsterObj.GetComponent<Monster>();
if (monster != null)
{
// Subscribe to monster events
monster.OnPictureTaken += OnMonsterPictureTaken;
monster.OnMonsterDespawned += OnMonsterDespawned;
// Add to active monsters list
activeMonsters.Add(monster);
// Fire event
OnMonsterSpawned?.Invoke(monster);
}
else
{
Debug.LogError($"Monster prefab {prefab.name} does not have a Monster component!");
Destroy(monsterObj);
}
}
private void OnMonsterPictureTaken(Monster monster)
{
// Calculate points based on depth
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * depthMultiplier);
int pointsAwarded = basePoints + depthBonus;
// Add score
playerScore += pointsAwarded;
// Fire events
OnScoreChanged?.Invoke(playerScore);
OnPictureTaken?.Invoke(monster, pointsAwarded);
}
private void OnMonsterDespawned(Monster monster)
{
// Remove from active list
activeMonsters.Remove(monster);
// Unsubscribe from events
monster.OnPictureTaken -= OnMonsterPictureTaken;
monster.OnMonsterDespawned -= OnMonsterDespawned;
}
// Call this when the game ends
public void EndGame()
{
// Clean up active monsters
foreach (var monster in activeMonsters.ToArray())
{
if (monster != null)
{
monster.DespawnMonster();
}
}
activeMonsters.Clear();
// Final score could be saved to player prefs or other persistence
Debug.Log($"Final Score: {playerScore}");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b2b072821054504b03fc4014b063153
timeCreated: 1758273243

View File

@@ -0,0 +1,75 @@
using UnityEngine;
using TMPro;
namespace Minigames.DivingForPictures
{
public class DivingScoreUI : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI scoreText;
[SerializeField] private GameObject scorePopupPrefab;
[SerializeField] private Transform popupParent;
private DivingGameManager gameManager;
private void Start()
{
gameManager = FindObjectOfType<DivingGameManager>();
if (gameManager != null)
{
// Subscribe to events
gameManager.OnScoreChanged += UpdateScoreDisplay;
gameManager.OnPictureTaken += ShowScorePopup;
// Initialize display
UpdateScoreDisplay(gameManager.PlayerScore);
}
else
{
Debug.LogWarning("No DivingGameManager found in scene.");
}
// Create popup parent if needed
if (popupParent == null)
{
popupParent = transform;
}
}
private void OnDestroy()
{
if (gameManager != null)
{
// Unsubscribe from events
gameManager.OnScoreChanged -= UpdateScoreDisplay;
gameManager.OnPictureTaken -= ShowScorePopup;
}
}
private void UpdateScoreDisplay(int score)
{
if (scoreText != null)
{
scoreText.text = $"Score: {score}";
}
}
private void ShowScorePopup(Monster monster, int points)
{
if (scorePopupPrefab == null) return;
// Create popup at monster position
GameObject popup = Instantiate(scorePopupPrefab, monster.transform.position, Quaternion.identity, popupParent);
// Find text component and set value
TextMeshProUGUI popupText = popup.GetComponentInChildren<TextMeshProUGUI>();
if (popupText != null)
{
popupText.text = $"+{points}";
}
// Auto-destroy after delay
Destroy(popup, 2f);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d5ec365b02ab496d8fa1d5f7d41a33e9
timeCreated: 1758273243

View File

@@ -0,0 +1,127 @@
using UnityEngine;
using System;
using System.Collections;
namespace Minigames.DivingForPictures
{
public class Monster : MonoBehaviour
{
[Header("References")]
[SerializeField] private CircleCollider2D detectionCollider;
private bool pictureAlreadyTaken = false;
private Camera mainCamera;
// Events
public event Action<Monster> OnPictureTaken;
public event Action<Monster> OnMonsterSpawned;
public event Action<Monster> OnMonsterDespawned;
// Properties
public float PictureRadius => detectionCollider != null ? detectionCollider.radius : 0f;
private void Awake()
{
if (detectionCollider == null)
detectionCollider = GetComponent<CircleCollider2D>();
mainCamera = Camera.main;
// Start checking if monster is off-screen
StartCoroutine(CheckIfOffScreen());
}
private void OnEnable()
{
pictureAlreadyTaken = false;
OnMonsterSpawned?.Invoke(this);
}
private IEnumerator CheckIfOffScreen()
{
WaitForSeconds wait = new WaitForSeconds(0.5f);
while (true)
{
yield return wait;
if (!IsVisibleToCamera())
{
DespawnMonster();
yield break;
}
}
}
private bool IsVisibleToCamera()
{
if (mainCamera == null)
mainCamera = Camera.main;
if (mainCamera == null)
return false;
Vector3 viewportPoint = mainCamera.WorldToViewportPoint(transform.position);
float buffer = 0.2f; // Extra buffer outside the screen
return viewportPoint.x > -buffer &&
viewportPoint.x < 1 + buffer &&
viewportPoint.y > -buffer &&
viewportPoint.y < 1 + buffer;
}
private void OnTriggerEnter2D(Collider2D other)
{
// Check if it's the player
if (other.CompareTag("Player") && !pictureAlreadyTaken)
{
TakePicture();
}
}
// Called when a picture is taken of this monster
public void TakePicture()
{
if (pictureAlreadyTaken) return;
pictureAlreadyTaken = true;
OnPictureTaken?.Invoke(this);
DespawnMonster();
}
// Public method to despawn this monster
public void DespawnMonster()
{
if (gameObject.activeSelf)
{
OnMonsterDespawned?.Invoke(this);
gameObject.SetActive(false);
Destroy(gameObject);
}
}
// Visualization for the picture radius in editor
private void OnDrawGizmosSelected()
{
// Get the collider in edit mode
if (detectionCollider == null)
detectionCollider = GetComponent<CircleCollider2D>();
if (detectionCollider != null)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, detectionCollider.radius / 2);
}
}
#if UNITY_EDITOR
// Update collider radius in editor
private void OnValidate()
{
if (detectionCollider == null)
detectionCollider = GetComponent<CircleCollider2D>();
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e1b1889b120f4259a9fa9b7e415ea58a
timeCreated: 1758273243

View File

@@ -0,0 +1,25 @@
using UnityEngine;
namespace Minigames.DivingForPictures
{
public class MonsterSpawnPoint : MonoBehaviour
{
[Tooltip("Visual radius for spawn point in editor")]
public float gizmoRadius = 0.5f;
// Visual indicator for editor only
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, gizmoRadius);
// Draw a cross in the center for better visibility
Gizmos.DrawLine(
transform.position + Vector3.left * gizmoRadius * 0.5f,
transform.position + Vector3.right * gizmoRadius * 0.5f);
Gizmos.DrawLine(
transform.position + Vector3.up * gizmoRadius * 0.5f,
transform.position + Vector3.down * gizmoRadius * 0.5f);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5ddb6d3629fe4b46b1d7ae972a83539c
timeCreated: 1758273243