enemy spawner
This commit is contained in:
32
Game/Scripts/Runtime/Data/BossSchedule.cs
Normal file
32
Game/Scripts/Runtime/Data/BossSchedule.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Game.Scripts.Runtime.Data
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Game/Spawning/Boss Schedule", fileName = "BossSchedule")]
|
||||
public class BossSchedule : ScriptableObject
|
||||
{
|
||||
[Serializable]
|
||||
public class BossEvent
|
||||
{
|
||||
[Min(0f)] public float TimeSinceStart;
|
||||
public EnemyDefinition Boss;
|
||||
public int Count = 1;
|
||||
[SerializeField] private bool useSpawnRadiusOverride;
|
||||
[SerializeField] private float spawnRadiusOverride = 10f;
|
||||
|
||||
public bool HasSpawnRadiusOverride => useSpawnRadiusOverride;
|
||||
public float SpawnRadiusOverride => spawnRadiusOverride;
|
||||
}
|
||||
|
||||
[SerializeField] private List<BossEvent> events = new();
|
||||
|
||||
public IReadOnlyList<BossEvent> Events => events;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
events.Sort((a, b) => a.TimeSinceStart.CompareTo(b.TimeSinceStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Game/Scripts/Runtime/Data/BossSchedule.cs.meta
Normal file
2
Game/Scripts/Runtime/Data/BossSchedule.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e2ad306ddab577d19b1e0e6bbb6fea9
|
||||
23
Game/Scripts/Runtime/Data/EnemyDefinition.cs
Normal file
23
Game/Scripts/Runtime/Data/EnemyDefinition.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Game.Scripts.Runtime.Data
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Game/Spawning/Enemy Definition", fileName = "EnemyDefinition")]
|
||||
public class EnemyDefinition : ScriptableObject
|
||||
{
|
||||
[Tooltip("Unique identifier leveraged by pooling and save systems. Defaults to asset name when left blank.")]
|
||||
[SerializeField] private string id;
|
||||
|
||||
[Tooltip("Prefab that represents this enemy. Should include required behaviours and visuals.")]
|
||||
public GameObject Prefab;
|
||||
|
||||
public bool IsBoss;
|
||||
public float BaseHP = 10f;
|
||||
public float MoveSpeed = 3.5f;
|
||||
public float Damage = 1f;
|
||||
public Vector3 PrefabPivotOffset = Vector3.zero;
|
||||
public string[] Tags = System.Array.Empty<string>();
|
||||
|
||||
public string Id => string.IsNullOrEmpty(id) ? name : id;
|
||||
}
|
||||
}
|
||||
2
Game/Scripts/Runtime/Data/EnemyDefinition.cs.meta
Normal file
2
Game/Scripts/Runtime/Data/EnemyDefinition.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b0e84710d3119d60af5d5373005e029
|
||||
107
Game/Scripts/Runtime/Data/SpawnTable.cs
Normal file
107
Game/Scripts/Runtime/Data/SpawnTable.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Game.Scripts.Runtime.Data
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Game/Spawning/Spawn Table", fileName = "SpawnTable")]
|
||||
public class SpawnTable : ScriptableObject
|
||||
{
|
||||
[Serializable]
|
||||
public class Entry
|
||||
{
|
||||
public EnemyDefinition Def;
|
||||
[Min(0)] public int Weight = 1;
|
||||
public int MinWave = 0;
|
||||
public int MaxWave = int.MaxValue;
|
||||
[Tooltip("Optional cap per wave. Values <= 0 mean unlimited.")]
|
||||
public int Cap = -1;
|
||||
|
||||
public bool IsWaveValid(int wave) => wave >= MinWave && wave <= MaxWave;
|
||||
|
||||
public bool IsUnderCap(int currentCount)
|
||||
{
|
||||
if (Cap <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return currentCount < Cap;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private List<Entry> entries = new();
|
||||
|
||||
private readonly List<Entry> _eligibleBuffer = new();
|
||||
|
||||
public IReadOnlyList<Entry> Entries => entries;
|
||||
|
||||
public EnemyDefinition Pick(int wave, System.Random rng)
|
||||
{
|
||||
if (entries == null || entries.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_eligibleBuffer.Clear();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry?.Def == null || entry.Weight <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.IsWaveValid(wave))
|
||||
{
|
||||
_eligibleBuffer.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (_eligibleBuffer.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var totalWeight = 0;
|
||||
foreach (var entry in _eligibleBuffer)
|
||||
{
|
||||
totalWeight += Mathf.Max(1, entry.Weight);
|
||||
}
|
||||
|
||||
var roll = rng.Next(totalWeight);
|
||||
foreach (var entry in _eligibleBuffer)
|
||||
{
|
||||
var weight = Mathf.Max(1, entry.Weight);
|
||||
if (roll < weight)
|
||||
{
|
||||
return entry.Def;
|
||||
}
|
||||
|
||||
roll -= weight;
|
||||
}
|
||||
|
||||
return _eligibleBuffer[^1].Def;
|
||||
}
|
||||
|
||||
public IEnumerable<Entry> EnumerateEligibleEntries(int wave)
|
||||
{
|
||||
if (entries == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry?.Def == null || entry.Weight <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.IsWaveValid(wave))
|
||||
{
|
||||
yield return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Game/Scripts/Runtime/Data/SpawnTable.cs.meta
Normal file
2
Game/Scripts/Runtime/Data/SpawnTable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fd6e160d6ab3dfecbc59a0685007a97
|
||||
44
Game/Scripts/Runtime/Data/SpawnerConfig.cs
Normal file
44
Game/Scripts/Runtime/Data/SpawnerConfig.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Game.Scripts.Runtime.Data
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Game/Spawning/Spawner Config", fileName = "SpawnerConfig")]
|
||||
public class SpawnerConfig : ScriptableObject
|
||||
{
|
||||
[Header("Radii")]
|
||||
public float MinSpawnRadius = 8f;
|
||||
public float MaxSpawnRadius = 24f;
|
||||
|
||||
[Header("Timing")]
|
||||
public float SpawnInterval = 2f;
|
||||
public AnimationCurve SpawnRateOverTime = AnimationCurve.Linear(0f, 1f, 1f, 1f);
|
||||
|
||||
[Header("Limits")]
|
||||
public int MaxConcurrent = 30;
|
||||
public int MaxPlacementAttempts = 6;
|
||||
public int PrewarmPerType = 4;
|
||||
public int PoolHardCapPerType = 60;
|
||||
|
||||
[Header("Placement")]
|
||||
public float GroundRaycastHeight = 5f;
|
||||
public float MaxHeightDelta = 4f;
|
||||
public bool UseLineOfSight = false;
|
||||
|
||||
[Header("Layers")]
|
||||
public LayerMask GroundMask = Physics.DefaultRaycastLayers;
|
||||
public LayerMask ObstacleMask = Physics.DefaultRaycastLayers;
|
||||
|
||||
[Header("NavMesh")]
|
||||
public int NavMeshAreaMask = -1;
|
||||
|
||||
[Header("RNG")]
|
||||
[SerializeField] private bool useSeed;
|
||||
[SerializeField] private int seedValue = 0;
|
||||
|
||||
[Header("Debug")]
|
||||
public bool DrawGizmos = false;
|
||||
public int GizmoSampleCount = 16;
|
||||
|
||||
public int? Seed => useSeed ? seedValue : (int?)null;
|
||||
}
|
||||
}
|
||||
2
Game/Scripts/Runtime/Data/SpawnerConfig.cs.meta
Normal file
2
Game/Scripts/Runtime/Data/SpawnerConfig.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4af5312c5abd9f725a626ceb6f7f8ae5
|
||||
Reference in New Issue
Block a user