Implement enemy spawning and following waypoints

This commit is contained in:
2024-08-12 14:21:11 +02:00
parent 5104b336ca
commit 3f715b2bbc
44 changed files with 1894 additions and 52 deletions

View File

@@ -0,0 +1,27 @@
using Sirenix.OdinInspector;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class AnimatorComponent : MonoBehaviour
{
[SerializeField]
[ReadOnly]
private Animator animator;
[SerializeField]
[ReadOnly]
private float velocity;
private void Awake()
{
animator = GetComponent<Animator>();
}
public void UpdateVelocity(float velocity)
{
this.velocity = velocity;
animator.SetFloat("Velocity", velocity);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e22e730d77a8fa1488817a22b3acee38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -7,6 +7,11 @@ using UnityEngine.EventSystems;
public class HealthComponent : MonoBehaviour
{
[SerializeField]
[Required]
[OnValueChanged("Setup")]
private Enemy enemy;
[ReadOnly]
[SerializeField]
private int health;
@@ -20,7 +25,7 @@ public class HealthComponent : MonoBehaviour
[Button]
public void Setup()
{
health = 100;
health = enemy.health;
isDead = false;
}

View File

@@ -0,0 +1,76 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Sirenix.OdinInspector;
using UnityEngine.AI;
using UnityEngine.Events;
[RequireComponent(typeof(NavMeshAgent))]
public class NavMeshComponent : MonoBehaviour
{
[SerializeField]
[ReadOnly]
public int currentWaypointIndex = 0;
[SerializeField]
[ReadOnly]
public WaypointsGroup waypointGroup;
[SerializeField]
[ReadOnly]
private NavMeshAgent agent;
[SerializeField]
private UnityEvent<float> onVelocityChanged;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
}
public void MoveTo(WaypointsGroup waypointGroup)
{
this.waypointGroup = waypointGroup;
MoveToNext();
}
private void MoveToNext()
{
currentWaypointIndex++;
agent.destination = waypointGroup.waypoints[currentWaypointIndex]
.GetPosition();
}
private void Update()
{
var velocity = agent.velocity.magnitude / agent.speed;
onVelocityChanged.Invoke(velocity);
if (waypointGroup == null)
return;
if (ReachedDestinationOrGaveUp())
{
if (currentWaypointIndex != waypointGroup.waypoints.Count - 1)
{
MoveToNext();
}
}
}
private bool ReachedDestinationOrGaveUp()
{
if (agent.pathPending)
return false;
if (agent.remainingDistance > agent.stoppingDistance)
return false;
if (!(!agent.hasPath || agent.velocity.sqrMagnitude == 0f))
return false;
return true;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3f8cfc01629a2a4d8e8eeba3344c8f8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnComponent : MonoBehaviour
{
public void Spawn(SpawnInformation info)
{
var gameObject = Instantiate(info.prefab, transform);
gameObject.transform.position = info.position;
gameObject.name = info.prefab.name;
info.onSpawned.Invoke(gameObject);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 435f0c23cdef1c340bb304e83dc5d8a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
[CreateAssetMenu(fileName = "Enemy", menuName = "Data/Enemy")]
public class Enemy : ScriptableObject
{
[SerializeField]
public int health;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fbf9bdf5c9924664eaf24e1e5b44fbce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using Sirenix.OdinInspector;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Level", menuName = "Data/Level")]
public class Level : ScriptableObject
{
[SerializeField]
public Wave[] waves = new Wave[0];
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6113d1f06beb6a24cb28f2425b7ea301
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class SpawnInformation
{
[SerializeField]
public GameObject prefab;
[SerializeField]
public Vector3 position;
[SerializeField]
public Action<GameObject> onSpawned;
public SpawnInformation(
GameObject prefab,
Vector3 position,
Action<GameObject> onSpawned)
{
this.prefab = prefab;
this.position = position;
this.onSpawned = onSpawned;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30cb692399aec2148bec8ce075f3cac8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using Sirenix.OdinInspector;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class Wave
{
[HorizontalGroup("Split", width: 130)]
[VerticalGroup("Split/Left")]
[Title("Time To Next Wave")]
[HideLabel]
[SerializeField]
public float timeToNextWave = 60;
[Title("Time To Next Group")]
[HideLabel]
[VerticalGroup("Split/Left")]
[SerializeField]
public float timeToNextGroup = 1;
[VerticalGroup("Split/Right")]
[SerializeField]
public WaveGroup[] groups = new WaveGroup[0];
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00d898c9ee1f3f543853c351aa4de552
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
using Sirenix.OdinInspector;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class WaveGroup
{
[SerializeField]
[AssetsOnly]
[Required]
public GameObject prefab;
[SerializeField]
[Range(1, 30)]
public int count = 1;
[SerializeField]
[Range(0, 30)]
[LabelText("Time to next")]
public float timeToNext = 1;
//[LabelText("1")]
//[HorizontalGroup(Title = "Waypoint group")]
//public bool inWaypointGroup1;
//[LabelText("2")]
//[HorizontalGroup]
//public bool inWaypointGroup2;
//[LabelText("3")]
//[HorizontalGroup]
//public bool inWaypointGroup3;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e9b63d2d9d8d3ae43b8bb7d8c8fc8e82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 524fe00bb628d7a42abaca8d223206d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,83 @@
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
public class WaveManager : MonoBehaviour
{
[SerializeField]
private Level level;
[SerializeField]
[ReadOnly]
private int currentWaveIndex = -1;
private Wave currentWave {
get { return level.waves[currentWaveIndex]; }
}
[SerializeField]
[ReadOnly]
private bool waveInProgress = false;
private bool isLastWave {
get { return currentWaveIndex == level.waves.Length - 1; }
}
public UnityEvent<SpawnInformation> spawnEnemy;
[SerializeField]
[ReadOnly]
private WaypointsGroup[] waypointGroups;
[DisableInEditorMode]
[Button]
private void StartNextWave()
{
if (waypointGroups.IsNullOrEmpty())
{
Debug.LogWarning("No waypoint groups set in wave manager");
return;
}
if (waveInProgress) return;
if (isLastWave) return;
currentWaveIndex++;
waveInProgress = true;
StartCoroutine(SpawnWave());
}
private void Start()
{
waypointGroups = GetComponentsInChildren<WaypointsGroup>();
StartNextWave();
}
private IEnumerator SpawnWave()
{
foreach (var spawn in currentWave.groups)
{
for (var i = 0; i < spawn.count; i++)
{
spawnEnemy.Invoke(new(spawn.prefab, transform.position, (enemy) =>
{
var agent = enemy.GetComponent<NavMeshComponent>();
agent.MoveTo(waypointGroups.First());
}));
yield return new WaitForSeconds(spawn.timeToNext);
}
yield return new WaitForSeconds(currentWave.timeToNextGroup);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d24ff1cd925ce404c913998a333b4fbd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: acd1bffb56e5bc9438bbefba06adbf6f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b951cba42bf31948bff38a5dc64fe4c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(WaypointsGroup))]
public class WaypointsGroupEditor : Editor
{
WaypointsGroup waypointsGroup;
List<Waypoint> waypoints;
Waypoint selectedWaypoint = null;
private void OnEnable()
{
waypointsGroup = target as WaypointsGroup;
waypoints = waypointsGroup.GetWaypointChildren();
}
private void OnSceneGUI()
{
DrawWaypoints(waypoints );
}
override public void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField("Waypoints");
bool dorepaint = false;
if (waypoints != null)
{
int delIndex = -1;
for (int cnt = 0; cnt < waypoints.Count; cnt++)
{
Color guiColor = GUI.color;
Waypoint cwp = waypoints[cnt];
if (cwp == selectedWaypoint)
GUI.color = Color.green;
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("S", GUILayout.Width(20)))
{
if(selectedWaypoint == cwp)
{
selectedWaypoint = null;
}
else
{
selectedWaypoint = cwp;
}
dorepaint = true;
}
EditorGUI.BeginChangeCheck();
Vector3 oldV = cwp.GetPosition(waypointsGroup.XYZConstraint);
Vector3 newV = EditorGUILayout.Vector3Field("", oldV);
if(EditorGUI.EndChangeCheck())
{
Undo.RecordObject(waypointsGroup, "Waypoint Moved");
cwp.UpdatePosition(newV - oldV, waypointsGroup.XYZConstraint);
}
if (GUILayout.Button("D", GUILayout.Width(25)))
{
delIndex = cnt;
dorepaint = true;
}
GUI.color = guiColor;
EditorGUILayout.EndHorizontal();
}
if (delIndex > -1)
{
if (waypoints[delIndex] == selectedWaypoint)
selectedWaypoint = null;
waypoints.RemoveAt(delIndex);
}
}
if (GUILayout.Button("Add"))
{
Undo.RecordObject(waypointsGroup, "Waypoint Added");
int ndx = -1;
if(selectedWaypoint != null)
{
ndx = waypoints.IndexOf(selectedWaypoint);
if (ndx == -1)
selectedWaypoint = null;
else
ndx += 1;
}
Waypoint wp = new Waypoint();
wp.CopyOther(selectedWaypoint);
waypointsGroup.AddWaypoint(wp, ndx);
selectedWaypoint = wp;
dorepaint = true;
}
EditorGUILayout.EndVertical();
if (dorepaint)
{
SceneView.RepaintAll();
}
}
public void DrawWaypoints(List<Waypoint> waypoints)
{
bool doRepaint = false;
if (waypoints != null)
{
int cnt = 0;
foreach (Waypoint wp in waypoints)
{
doRepaint |= DrawInScene(wp);
// Draw a pointer line
if(cnt < waypoints.Count-1)
{
Waypoint wpnext = waypoints[cnt+1];
Handles.DrawLine(wp.GetPosition(waypointsGroup.XYZConstraint), wpnext.GetPosition(waypointsGroup.XYZConstraint));
}
else
{
Waypoint wpnext = waypoints[0];
Color c = Handles.color;
Handles.color = Color.gray;
Handles.DrawLine(wp.GetPosition(waypointsGroup.XYZConstraint), wpnext.GetPosition(waypointsGroup.XYZConstraint));
Handles.color = c;
}
cnt += 1;
}
}
if(doRepaint)
{
Repaint();
}
}
public bool DrawInScene(Waypoint waypoint, int controlID = -1)
{
if (waypoint == null)
{
Debug.Log("NO WP!");
return false;
}
bool doRepaint = false;
//None serialized field, gets "lost" during serailize updates;
waypoint.SetWaypointGroup(waypointsGroup);
if (selectedWaypoint == waypoint)
{
Color c = Handles.color;
Handles.color = Color.green;
//Vector3 newPos = Handles.FreeMoveHandle(waypoint.GetPosition(), waypoint.rotation, 1.0f, Vector3.zero, Handles.SphereHandleCap);
EditorGUI.BeginChangeCheck();
Vector3 oldpos = waypoint.GetPosition(waypointsGroup.XYZConstraint);
Vector3 newPos = Handles.PositionHandle(oldpos, waypoint.rotation);
float handleSize = HandleUtility.GetHandleSize(newPos);
Handles.SphereHandleCap(-1, newPos, waypoint.rotation, 0.25f * handleSize, EventType.Repaint);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(waypointsGroup, "Waypoint Moved");
waypoint.UpdatePosition(newPos - oldpos, waypointsGroup.XYZConstraint);
}
Handles.color = c;
}
else
{
Vector3 currPos = waypoint.GetPosition(waypointsGroup.XYZConstraint);
float handleSize = HandleUtility.GetHandleSize(currPos);
if (Handles.Button(currPos, waypoint.rotation, 0.25f * handleSize, 0.25f * handleSize, Handles.SphereHandleCap))
{
doRepaint = true;
selectedWaypoint = waypoint;
}
}
return doRepaint;
}
// Menu item for creating a waypoints group
[MenuItem("GameObject/WaypointsFree/Create WaypointsGroup")]
public static void CreateRFPathManager()
{
GameObject go = new GameObject("WaypointsGroup");
go.AddComponent<WaypointsGroup>();
// Select it:
Selection.activeGameObject = go;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08bf8998ce77c2245ba5d8436ec723b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,141 @@
using System;
using UnityEngine;
public enum PositionConstraint
{
XYZ, // 3D
XY, // 2D
XZ // 2D
}
/// <summary>
/// Traversal direction for list of waypoints (in the WaypointpointsGroup)
/// </summary>
public enum TravelDirection
{
FORWARD, // 0 to Length-1
REVERSE // Length-1 to 0
}
public enum EndpointBehavior
{
STOP, // Movement stops when end position reached
LOOP, // Movement loops back to first position
PINGPONG, // Reverse direction through the the positions list
}
public enum MoveType
{
LERP, // Uses the MoveLerpSimple function to update transform position
FORWARD_TRANSLATE // uses MoveForwardToNext function to translate position - ROTATION DEPENDENT!
}
/// <summary>
/// Waypoint class - managed through the WaypointGroups editor
///
/// </summary>
[Serializable]
public class Waypoint
{
// OFFSET position to parent WaypointsGroup;
// Use GETPOSITION to read properly (as it depends on the PositionConstraint of the WaypointGroup parent)
public Vector3 position;
[HideInInspector]
public Quaternion rotation = Quaternion.identity;
[SerializeField]
[HideInInspector]
Vector3 xyzPosition; // OFFSET position to parent WaypointsGroup
[HideInInspector]
[SerializeField]
Vector3 xyPosition; // OFFSET position to parent WaypointsGroup
[HideInInspector]
[SerializeField]
Vector3 xzPosition; // OFFSET position to parent WaypointsGroup
WaypointsGroup wpGroup;
public Vector3 XY
{
get { return xyPosition; }
}
public Vector3 XYZ
{
get { return xyzPosition; }
}
public Vector3 XZ
{
get { return xzPosition; }
}
public void SetWaypointGroup(WaypointsGroup wpg)
{
wpGroup = wpg;
}
public void CopyOther(Waypoint other)
{
if (other == null) return;
xyPosition = other.XY;
xzPosition = other.XZ;
xyzPosition = other.XYZ;
Debug.Log(other.XYZ);
Debug.Log(xyzPosition);
}
public Vector3 GetPosition(PositionConstraint constraint=PositionConstraint.XYZ)
{
if(wpGroup != null)
{
constraint = wpGroup.XYZConstraint;
}
if (constraint == PositionConstraint.XY)
position = xyPosition;
else if (constraint == PositionConstraint.XZ)
position = xzPosition;
else
position = xyzPosition;
if (wpGroup != null)
return wpGroup.transform.position + position;
else
return position;
}
public void UpdatePosition( Vector3 newPos, PositionConstraint constraint )
{
xyPosition.x += newPos.x;
xzPosition.x += newPos.x;
xyzPosition.x += newPos.x;
if(constraint == PositionConstraint.XY)
{
xyzPosition.y += newPos.y;
xyPosition.y += newPos.y;
}
else if(constraint == PositionConstraint.XZ)
{
xzPosition.z += newPos.z;
xyzPosition.z += newPos.z;
}
else if(constraint == PositionConstraint.XYZ)
{
xyzPosition.y += newPos.y;
xyzPosition.z += newPos.z;
xyPosition.y += newPos.y;
xzPosition.z += newPos.z;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 524ba8ae70727da4283498854cea1ca8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaypointsGroup : MonoBehaviour
{
public PositionConstraint XYZConstraint = PositionConstraint.XYZ;
[HideInInspector]
public List<Waypoint> waypoints; // The waypoint components controlled by this WaypointsGroupl IMMEDIATE children only
private void Awake()
{
if(waypoints != null)
{
foreach (Waypoint wp in waypoints)
wp.SetWaypointGroup(this);
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
/// <summary>
/// Returns a list of Waypoints; resets the parent transform if reparent == true
/// </summary>
/// <returns></returns>
public List<Waypoint> GetWaypointChildren(bool reparent = true)
{
if (waypoints == null)
waypoints = new List<Waypoint>();
if(reparent == true)
{
foreach (Waypoint wp in waypoints)
wp.SetWaypointGroup(this);
}
return waypoints;
}
public void AddWaypoint(Waypoint wp, int ndx = -1)
{
if (waypoints == null) waypoints = new List<Waypoint>();
if (ndx == -1)
waypoints.Add(wp);
else
waypoints.Insert(ndx, wp);
wp.SetWaypointGroup(this);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c9625c5b7836584397b59b00907d6dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,369 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaypointsTraveler : MonoBehaviour
{
[Tooltip("WaypointsGroup gameobject containing the waypoints to travel.")]
public WaypointsGroup Waypoints = null;
[Tooltip("Movement and look-at constraints.")]
public PositionConstraint XYZConstraint = PositionConstraint.XYZ;
[Tooltip("Auto-start movement if true.")]
public bool AutoStart = false;
//[Range(0,float.MaxValue)]
public float MoveSpeed = 5.0f;
//[Range(0, float.MaxValue)]
public float LookAtSpeed = 3.0f;
[Tooltip("Starts movement from the position vector at this index. Dependent upon StartTravelDirection!")]
public int StartIndex = 0;
[Tooltip("Immediately move starting position to postion at StartIndex.")]
public bool AutoPositionAtStart = true;
[Tooltip("Initial direction of travel through the positions list.")]
public TravelDirection StartTravelDirection = TravelDirection.FORWARD;
[Tooltip("Movement behavior to apply when last postion reached.")]
public EndpointBehavior EndReachedBehavior = EndpointBehavior.LOOP;
[Tooltip("Movement function type")]
public MoveType StartingMovementType = MoveType.LERP;
public bool IsMoving
{
get { return isMoving; }
}
delegate bool MovementFunction ();
MovementFunction moveFunc = null;
int positionIndex = -1; // Index of the next waypoint to move toward
List<Waypoint> waypointsList; //Reference to the list of waypoints located in Waypoints
Vector3 nextPosition; // The next position to travel to.
Vector3 startPosition;
Vector3 destinationPosition;
float distanceToNextWaypoint;
float distanceTraveled = 0;
float timeTraveled = 0;
int travelIndexCounter = 1;
bool isMoving = false; // Movement on/off
Vector3 positionOriginal;
Quaternion rotationOriginal;
float moveSpeedOriginal = 0;
float lookAtSpeedOriginal = 0;
public void ResetTraveler()
{
transform.position = positionOriginal;
transform.rotation = rotationOriginal;
MoveSpeed = moveSpeedOriginal;
LookAtSpeed = lookAtSpeedOriginal;
StartAtIndex(StartIndex, AutoPositionAtStart);
SetNextPosition();
travelIndexCounter = StartTravelDirection == TravelDirection.REVERSE ? -1 : 1;
if (StartingMovementType == MoveType.LERP)
moveFunc = MoveLerpSimple;
else if (StartingMovementType == MoveType.FORWARD_TRANSLATE)
moveFunc = MoveForwardToNext;
}
// Start is called before the first frame update
void Start()
{
moveSpeedOriginal = MoveSpeed;
lookAtSpeedOriginal = LookAtSpeed;
positionOriginal = transform.position;
rotationOriginal = transform.rotation;
ResetTraveler();
Move( AutoStart );
}
public void Move(bool tf)
{
isMoving = tf;
}
private void Awake()
{
if (Waypoints != null)
{
waypointsList = Waypoints.waypoints;
}
}
// Update is called once per frame
void Update()
{
if (isMoving == true && moveFunc != null)
{
bool arrivedAtDestination = false;
// Call the delegate Movement Function...
arrivedAtDestination = moveFunc();
if (arrivedAtDestination == true)
{
SetNextPosition();
}
}
}
/// <summary>
/// Setup the list of positions to follow
/// </summary>
/// <param name="positionsList">List of Vector3s indicating move-to locations</param>
public void SetWaypointsGroup(WaypointsGroup newGroup)
{
Waypoints = newGroup;
waypointsList = null;
if(newGroup != null)
{
waypointsList = newGroup.waypoints;
}
}
/// <summary>
/// Sets the position to beging moving toward; if autpAupdatePostion is true, then start
/// at that index-related position immediately.
/// </summary>
/// <param name="ndx"></param>
/// <param name="autoUpdatePosition"></param>
void StartAtIndex(int ndx, bool autoUpdatePosition = true)
{
if (StartTravelDirection == TravelDirection.REVERSE)
ndx = waypointsList.Count - ndx - 1;
ndx = Mathf.Clamp(ndx, 0, waypointsList.Count - 1);
positionIndex = ndx - 1;
if (autoUpdatePosition)
{
transform.position = waypointsList[ndx].GetPosition();
if(LookAtSpeed > 0)
{
if (StartTravelDirection == TravelDirection.REVERSE)
{
ndx -= 1;
if (ndx < 0) ndx = waypointsList.Count - 1;
}
else
{
ndx += 1;
if (ndx >= waypointsList.Count)
ndx = 0;
}
/*
Waypoint wp = waypointsList[ndx];
Vector3 wpPos = wp.GetPosition();
Vector3 worldUp = Vector3.forward;
transform.LookAt(wpPos, worldUp);
*/
}
}
}
/// <summary>
/// Fetch the next waypoint position in the waypoints list
/// Depending on Endpoint behavior, ping pong, loop, or stop.
/// - Stop : Stops movement at the endpoint
/// - Ping Pong: reverses traveseral of the Waypoint Positions list
/// - Loop : Resets the index to 0; restarting at the first waypoint
/// </summary>
void SetNextPosition()
{
int posCount = waypointsList.Count;
if (posCount > 0)
{
// Reached the endpoint; determing what do do next
if( (positionIndex == 0 && travelIndexCounter < 0) || (positionIndex == posCount - 1 && travelIndexCounter > 0))
{
// Stop moving when an endpoint is reached
if(EndReachedBehavior == EndpointBehavior.STOP)
{
isMoving = false;
return;
}
// Continue movement, but reverse direction
else if(EndReachedBehavior == EndpointBehavior.PINGPONG)
{
travelIndexCounter = -travelIndexCounter;
}
// General Loop (default)
else if (EndReachedBehavior == EndpointBehavior.LOOP)
{
}
}
positionIndex += travelIndexCounter;
if (positionIndex >= posCount)
positionIndex = 0;
else if (positionIndex < 0)
positionIndex = posCount - 1;
nextPosition = waypointsList[positionIndex].GetPosition();
if (XYZConstraint == PositionConstraint.XY)
{
nextPosition.z = transform.position.z;
}
else if (XYZConstraint == PositionConstraint.XZ)
{
nextPosition.y = transform.position.y;
}
ResetMovementValues();
}
}
/// <summary>
/// Reset movement metrics based on the current transform postion and the next waypoint.
/// </summary>
void ResetMovementValues()
{
// Update current position, distance, and start time -- used in Update to move the transform
startPosition = transform.position;
destinationPosition = nextPosition;
distanceToNextWaypoint = Vector3.Distance(startPosition, destinationPosition);
distanceTraveled = 0;
timeTraveled = 0;
}
/// <summary>
/// Uses a Vector3 Lerp function to update the gameobject's transform position.
/// MoveSpeed needs to be > 0
/// </summary>
/// <returns></returns>
bool MoveLerpSimple()
{
if (MoveSpeed < 0)
MoveSpeed = 0;
timeTraveled += Time.deltaTime;
distanceTraveled += Time.deltaTime * MoveSpeed;
float fracAmount = distanceTraveled / distanceToNextWaypoint;
transform.position = Vector3.Lerp(startPosition, destinationPosition, fracAmount);
// set LookAt speed to 0 if no rotation toward the destination is desired.
UpdateLookAtRotation();
return fracAmount >= 1;
}
/// <summary>
/// Translates the waypoint traveler using the forward direction. Note that "forward" is dependent
/// upon the directional/position constraint.
/// - XYZ: Vector3.forward
/// - XY : Vector3.up
/// - XZ : Vector3.right
///
/// -- When using this method of translation, LOOKATSPEED must be > 0.
/// </summary>
/// <returns></returns>
bool MoveForwardToNext()
{
if (MoveSpeed < 0)
MoveSpeed = 0;
float rate = Time.deltaTime * MoveSpeed;
float distance = Vector3.Distance(transform.position, destinationPosition);
if (distance < rate)
{
transform.position = destinationPosition;
return true;
}
// If lookatspeed is 0, then set it to max; this method of movement requires
// the gameobject transform to be looking toward it's destination
if (LookAtSpeed <= 0) LookAtSpeed = float.MaxValue;
UpdateLookAtRotation();
Vector3 moveDir = Vector3.forward;
if (XYZConstraint == PositionConstraint.XY)
{
moveDir = Vector3.up;
}
transform.Translate(moveDir * rate);
return false;
}
/// <summary>
/// Rotate the traveler to "look" at the next waypoint.
/// Note: When using the FORWARD_TRANSLATE movement mode, LookAtSpeed is always > 0
/// </summary>
void UpdateLookAtRotation()
{
// LookatSpeed is 0; no rotating...
if (LookAtSpeed <= 0) return;
float step = LookAtSpeed * Time.deltaTime;
Vector3 targetDir = nextPosition - transform.position;
if (XYZConstraint == PositionConstraint.XY)
{
float angle = Mathf.Atan2(targetDir.y, targetDir.x) * Mathf.Rad2Deg - 90;
Quaternion qt = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.RotateTowards(transform.rotation, qt, step);
}
else if (XYZConstraint == PositionConstraint.XZ)
{
float angle = Mathf.Atan2(targetDir.x, targetDir.z) * Mathf.Rad2Deg;
Quaternion qt = Quaternion.AngleAxis(angle, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, qt, step);
}
else
{
Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0f);
// Move our position a step closer to the target.
transform.rotation = Quaternion.LookRotation(newDir);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4650cc3407a19a941a6c01a1ddbc876c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: