feat: enemies spawn in spawn zones and wander on a graph

This commit is contained in:
Chris
2025-10-24 13:03:16 -04:00
parent f0a99253e5
commit fb515ed1cf
6 changed files with 633 additions and 270 deletions

View File

@@ -5,7 +5,10 @@ using Unity.Netcode;
using UnityEngine;
namespace Reset.Units{
public class Enemy : Unit, ILockOnTarget, IKillable {
public class Enemy : Unit, ILockOnTarget, IKillable{
// Spawn Info
public EnemySpawn relatedSpawner;
// Lock-On
public float lockonTargetRadius{ get; set; } = 10f;
[ShowInInspector]

View File

@@ -1,26 +1,65 @@
using Pathfinding;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Pathfinding;
using Pathfinding.Drawing;
using UnityEngine;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
namespace Reset.Units{
public class EnemyPathfinding : MonoBehaviour, IUnitDirectionProvider{
[RequireComponent(typeof(Seeker))]
public class EnemyPathfinding : UnitComponent, IUnitDirectionProvider{
public Vector2 Direction{ get; set; }
public Transform targetPosition;
private Seeker seeker;
public float nextWaypointDistance = 3;
private int currentWaypoint;
public Path path;
public bool reachedEndOfPath;
private Coroutine wanderPathCoroutine;
public void Start(){
seeker = GetComponent<Seeker>();
// If you are writing a 2D game you should remove this line
// and use the alternative way to move sugggested further below.
Enemy thisEnemy = (Unit as Enemy);
if (thisEnemy.relatedSpawner){
seeker.graphMask = GraphMask.FromGraph(thisEnemy.relatedSpawner.relatedGraph);
} else {
Debug.LogWarning("Creating a graph for a singular enemy is not yet implemented. Graph mask not set on this unit.", transform);
}
StartCoroutine(WaitForWanderPath());
}
seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);
IEnumerator WaitForWanderPath(){
yield return new WaitForSeconds(Random.Range(1f, 7f));
StartWanderPath();
wanderPathCoroutine = null;
}
void StartWanderPath(){
Enemy thisEnemy = (Unit as Enemy);
Vector3 pathTargetPos = transform.position;
if (thisEnemy.relatedSpawner) {
pathTargetPos = thisEnemy.relatedSpawner.transform.position;
}
Vector3 randomizedDestination = new Vector3(
Random.Range(-thisEnemy.relatedSpawner.radius, thisEnemy.relatedSpawner.radius),
0f,
Random.Range(-thisEnemy.relatedSpawner.radius, thisEnemy.relatedSpawner.radius)
);
pathTargetPos += randomizedDestination;
seeker.StartPath(transform.position, pathTargetPos, OnPathComplete);
}
public void OnPathComplete(Path p){
@@ -40,6 +79,7 @@ namespace Reset.Units{
reachedEndOfPath = false;
// The distance to the next waypoint in the path
float distanceToWaypoint;
while (true) {
// If you want maximum performance you can check the squared distance instead to get rid of a
// square root calculation. But that is outside the scope of this tutorial.
@@ -52,22 +92,29 @@ namespace Reset.Units{
// Set a status variable to indicate that the agent has reached the end of the path.
// You can use this to trigger some special code if your game requires that.
reachedEndOfPath = true;
if (wanderPathCoroutine == null) {
wanderPathCoroutine = StartCoroutine(WaitForWanderPath());
}
break;
}
} else {
break;
}
}
// Pass the direction to the direction handler interface
if (currentWaypoint + 1 == path.vectorPath.Count) {
Direction = (path.vectorPath[currentWaypoint] - transform.position).ToVector2();
Direction = Vector3.ClampMagnitude(Direction, 1f);
} else {
Direction = (path.vectorPath[currentWaypoint] - transform.position).normalized.ToVector2();
}
// Slow down smoothly upon approaching the end of the path
// This value will smoothly go from 1 to 0 as the agent approaches the last waypoint in the path.
var speedFactor = reachedEndOfPath ? Mathf.Sqrt(distanceToWaypoint / nextWaypointDistance) : 1f;
// Direction to the next waypoint
// Normalize it so that it has a length of 1 world unit
Vector3 dirToPoint = (path.vectorPath[currentWaypoint] - transform.position).normalized * speedFactor;
Direction = dirToPoint.ToVector2();
// Draw an indicator for the path
Draw.ingame.SolidCircle(path.vectorPath[currentWaypoint] + Vector3.up * .02f, Vector3.up, .3f);
Draw.ingame.Arrow(transform.position, transform.position + Direction.ToVector3(), Vector3.up, .3f);
}
}

View File

@@ -1,47 +1,65 @@
using System.Collections.Generic;
using Drawing;
using Pathfinding;
using Sirenix.OdinInspector;
using UnityEngine;
public class EnemySpawn : MonoBehaviour{
public float radius = 30f;
namespace Reset.Units{
public class EnemySpawn : MonoBehaviour{
public float radius = 30f;
public int minimumEnemies = 1;
public int maximumEnemies = 5;
public Vector2 enemyCount;
public int minimumEnemies = 1;
public int maximumEnemies = 5;
// TODO: Replace this with an Enemy selector based on difficulty, random chance, etc?
public GameObject enemy;
public Vector2 enemyCount;
public List<GameObject> enemies;
void Start(){
SpawnEnemies();
}
// TODO: Replace this with an Enemy selector based on difficulty, random chance, etc?
public GameObject enemy;
void SpawnEnemies(){
int count = Random.Range(minimumEnemies, maximumEnemies + 1);
public List<GameObject> enemies;
for (int i = 0; i < count; i++) {
Vector3 newPosition = transform.position;
public GridGraph relatedGraph;
float randomX = Random.Range(-(radius / 2f), radius / 2f);
float randomZ = Random.Range(-(radius / 2f), radius / 2f);
void Start(){
CreateAstarGraph();
SpawnEnemies();
}
newPosition += new Vector3(randomX, transform.position.y, randomZ);
void CreateAstarGraph(){
relatedGraph = AstarPath.active.data.AddGraph(typeof(GridGraph)) as GridGraph;
float randomRot = Random.Range(0f, 360f);
relatedGraph.SetDimensions(Mathf.FloorToInt(radius) * 2, Mathf.FloorToInt(radius) * 2, 1f);
relatedGraph.collision.diameter = 3f;
GameObject newEnemy = Instantiate(enemy, newPosition, Quaternion.AngleAxis(randomRot, Vector3.up));
enemies.Add(newEnemy);
AstarPath.active.Scan(relatedGraph);
GetComponent<ProceduralGraphMover>().graph = relatedGraph;
}
void SpawnEnemies(){
int count = Random.Range(minimumEnemies, maximumEnemies + 1);
for (int i = 0; i < count; i++) {
Vector3 newPosition = transform.position;
float randomX = Random.Range(-(radius / 2f), radius / 2f);
float randomZ = Random.Range(-(radius / 2f), radius / 2f);
newPosition += new Vector3(randomX, transform.position.y, randomZ);
float randomRot = Random.Range(0f, 360f);
GameObject newEnemy = Instantiate(enemy, newPosition, Quaternion.AngleAxis(randomRot, Vector3.up));
newEnemy.GetComponent<Enemy>().relatedSpawner = this;
enemies.Add(newEnemy);
}
}
// Update is called once per frame
void Update(){
Draw.WireCylinder(transform.position, transform.position + Vector3.up * 7f, radius);
}
}
// Update is called once per frame
void Update()
{
Draw.WireCylinder(transform.position, transform.position + Vector3.up * 7f, radius);
}
}
}