Files
Cielonos/Assets/Opsive/BehaviorDesigner/Add-Ons/MovementPack/Scripts/Tasks/Wander.cs
SoulliesOfficial ef7b479712 initial
2025-11-25 08:19:33 -05:00

217 lines
9.5 KiB
C#

/// ---------------------------------------------
/// Movement Pack for Behavior Designer Pro
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.AddOns.MovementPack.Runtime.Tasks
{
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Utility;
using Opsive.GraphDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Variables;
using Unity.Entities;
using UnityEngine;
[Opsive.Shared.Utility.Description("Wanders around the pathfinding area. The task can wander within a specified bounds or in random directions.")]
[NodeIcon("f900ccca7c66371459b52036efeb8778", "cfd0e78235c50db46bc12d1751492ecf")]
public class Wander : MovementBase
{
/// <summary>
/// Specifies the bounds that the agent can wander in.
/// </summary>
public enum WanderBounds
{
None, // The agent will wander in an unrestricted area.
Sphere, // The agent will wander within a sphere.
Box // The agent will wander within a box.
}
[Tooltip("Specifies the bounds that the agent can wander in.")]
[SerializeField] protected SharedVariable<WanderBounds> m_Bounds;
[Tooltip("Specifies the center of the sphere or box bounds.")]
[SerializeField] protected SharedVariable<Vector3> m_BoundsCenter;
[Tooltip("Specifies the radius of the bounding sphere.")]
[SerializeField] protected SharedVariable<float> m_BoundsRadius = 10;
[Tooltip("Specifies the size of the bounding box.")]
[SerializeField] protected SharedVariable<Vector3> m_BoundsSize = new Vector3(10, 10, 10);
[Tooltip("Specifies the distance ahead of the current position to look ahead for a destination.")]
[SerializeField] protected SharedVariable<RangeFloat> m_WanderDistance = new RangeFloat(5, 10);
[Tooltip("Specifies the number of degrees that the agent can turn when wandering.")]
[SerializeField] protected SharedVariable<RangeFloat> m_WanderDegrees = new RangeFloat(-30, 30);
[Tooltip("Amount to decrease the distance if the wander position can't be found.")]
[SerializeField] protected SharedVariable<float> m_WanderDistanceDecrease = 1;
[Tooltip("Amount to increase the degrees if the wander position can't be found.")]
[SerializeField] protected SharedVariable<float> m_WanderDegreesIncrease = 5;
[Tooltip("Specifies the amount of time the agent should wait at the destination before selecting a new wander destination.")]
[SerializeField] protected SharedVariable<RangeFloat> m_WaitAtDestinationDuration;
[Tooltip("Specifies the remaining distance when the agent should select a new destination.")]
[SerializeField] protected SharedVariable<RangeFloat> m_DestinationSelectionDistance = new RangeFloat(1, 3);
[Tooltip("The maximum number of retries per tick (set higher if using a slow tick time)")]
[SerializeField] protected SharedVariable<int> m_DestinationRetries = 1;
#if UNITY_EDITOR
[Tooltip("Should a ray be drawn at the invalid destination location?")]
[SerializeField] protected bool m_DrawInvalidDestinationRay;
#endif
public SharedVariable<WanderBounds> Bounds { get => m_Bounds; set => m_Bounds = value; }
private float m_WaitTime = -1;
private float m_DestinationReachedTime = -1;
/// <summary>
/// Starts the task.
/// </summary>
public override void OnStart()
{
base.OnStart();
if (m_DestinationReachedTime == -1) {
TrySetDestination();
}
}
/// <summary>
/// Returns the target destination.
/// </summary>
/// <returns>The target destination.</returns>
public override TaskStatus OnUpdate()
{
// Select a new destination if the agent has arrived or the agent should selected a new destination before reaching the original destination.
var destinationSelectionDistance = m_DestinationSelectionDistance.Value.RandomValue;
if (HasArrived() || RemainingDistance < destinationSelectionDistance) {
// The agent can wait at the destination for a period of time.
if (m_WaitTime == -1) {
var waitDuration = m_WaitAtDestinationDuration.Value.RandomValue;
if (waitDuration > 0) {
m_DestinationReachedTime = Time.time;
m_WaitTime = waitDuration;
}
} else if (m_DestinationReachedTime + m_WaitTime <= Time.time) {
// Reset the WaitTime so the destination is set.
m_DestinationReachedTime = -1;
m_WaitTime = -1;
}
// If WaitTime is still -1 then the agent is either not waiting or is done waiting.
if (m_WaitTime == -1) {
TrySetDestination();
return TaskStatus.Running;
}
}
return TaskStatus.Running;
}
/// <summary>
/// Tries to set the destination based on the bounds type.
/// </summary>
/// <returns>True if the destination was set.</returns>
private bool TrySetDestination()
{
var direction = transform.forward;
var attempts = 0;
Vector3 destination;
while (attempts < m_DestinationRetries.Value) {
direction = Quaternion.Euler(0, Mathf.Clamp(m_WanderDegrees.Value.RandomValue + m_WanderDegreesIncrease.Value * attempts, -360, 360), 0) * direction;
destination = transform.position + direction.normalized * Mathf.Max(0, m_WanderDistance.Value.RandomValue - (m_WanderDistanceDecrease.Value * attempts));
// The destination should stay within the bounds of the sphere or box.
var validDestination = true;
if (m_Bounds.Value == WanderBounds.Sphere) {
var destinationRadius = (destination - m_BoundsCenter.Value).magnitude;
if (destinationRadius > m_BoundsRadius.Value) {
validDestination = false;
#if UNITY_EDITOR
if (m_DrawInvalidDestinationRay) {
Debug.DrawRay(destination, Vector3.up, Color.red, 1);
}
#endif
}
} else if (m_Bounds.Value == WanderBounds.Box) {
var destinationDirection = (destination - m_BoundsCenter.Value);
if (Mathf.Abs(destinationDirection.x) > m_BoundsSize.Value.x ||
Mathf.Abs(destinationDirection.y) > m_BoundsSize.Value.y ||
Mathf.Abs(destinationDirection.z) > m_BoundsSize.Value.z) {
validDestination = false;
#if UNITY_EDITOR
if (m_DrawInvalidDestinationRay) {
Debug.DrawRay(destination, Vector3.up, Color.red, 1);
}
#endif
}
}
if (validDestination && SamplePosition(ref destination)) {
SetDestination(destination);
return true;
}
attempts++;
}
return false;
}
/// <summary>
/// The task has ended.
/// </summary>
public override void OnEnd()
{
base.OnEnd();
m_WaitTime = -1;
m_DestinationReachedTime = -1;
}
/// <summary>
/// Returns the current task state.
/// </summary>
/// <param name="world">The DOTS world.</param>
/// <param name="entity">The DOTS entity.</param>
/// <returns>The current task state.</returns>
public override object Save(World world, Entity entity)
{
var saveData = new object[3];
saveData[0] = base.Save(world, entity);
saveData[1] = m_WaitTime;
saveData[2] = Time.time - m_DestinationReachedTime;
return saveData;
}
/// <summary>
/// Loads the previous task state.
/// </summary>
/// <param name="saveData">The previous task state.</param>
/// <param name="world">The DOTS world.</param>
/// <param name="entity">The DOTS entity.</param>
public override void Load(object saveData, World world, Entity entity)
{
var saveDataArray = (object[])saveData;
base.Load(saveDataArray[0], world, entity);
m_WaitTime = (float)saveDataArray[1];
m_DestinationReachedTime = Time.time - (float)saveDataArray[2];
}
/// <summary>
/// Resets the task values back to their default.
/// </summary>
public override void Reset()
{
base.Reset();
m_Bounds = WanderBounds.None;
m_BoundsCenter = Vector3.zero;
m_BoundsRadius = 10;
m_BoundsSize = new Vector3(10, 10, 10);
m_WanderDistance = new RangeFloat(5, 10);
m_WanderDegrees = new RangeFloat(-30, 30);
m_WanderDegreesIncrease = 5;
m_WaitAtDestinationDuration = new RangeFloat();
m_DestinationSelectionDistance= new RangeFloat(1, 3);
m_DestinationRetries = 1;
#if UNITY_EDITOR
m_DrawInvalidDestinationRay = false;
#endif
}
}
}