Merge pull request #275 from SHADE-DP/ScriptingAI

Fixed AI for the Gameplay Scene
This commit is contained in:
XiaoQiDigipen 2022-11-24 21:44:38 +08:00 committed by GitHub
commit 49b66e5c26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 8928 additions and 158 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
Name: MainGameWithAIFixed
ID: 89830755
Type: 5

View File

@ -40,15 +40,7 @@ namespace SHADE_Scripting.AIBehaviour.BehaviourTree
//awake and update functions //awake and update functions
//the only segment in the entire AI that is dependent on the engine //the only segment in the entire AI that is dependent on the engine
private bool test = false;
protected override void awake() protected override void awake()
{
AwakeCall();
}
protected override void start()
{ {
_root = CreateTree(); _root = CreateTree();
_root.InitialiseNode(this); _root.InitialiseNode(this);
@ -59,7 +51,7 @@ namespace SHADE_Scripting.AIBehaviour.BehaviourTree
_root?.Evaluate(); _root?.Evaluate();
Tick(); Tick();
} }
protected abstract void AwakeCall();
protected abstract void Initialise(); protected abstract void Initialise();
protected abstract void Tick(); protected abstract void Tick();
} }

View File

@ -28,14 +28,13 @@ public partial class Homeowner1 : BehaviourTree
private BehaviourTreeEvents _events { get; set; } private BehaviourTreeEvents _events { get; set; }
public override BehaviourTreeEvents events { get => _events; } public override BehaviourTreeEvents events { get => _events; }
[Tooltip("The player the AI should chase and attempt to capture")]
public GameObject player;
//PATROL FIELDS/////////////////////////////////////////////////////////////// //PATROL FIELDS///////////////////////////////////////////////////////////////
[SerializeField] [SerializeField]
[Tooltip("The list of waypoints for the AI to cycle around")] [Tooltip("The list of waypoints for the AI to cycle around")]
private List<Vector3> waypoints = new List<Vector3>(); private GameObject waypointsPool;
private List<GameObject> waypoints;
[SerializeField] [SerializeField]
[Tooltip("The AI will patrol at this speed")] [Tooltip("The AI will patrol at this speed")]
@ -79,32 +78,18 @@ public partial class Homeowner1 : BehaviourTree
//AI tree //AI tree
public partial class Homeowner1 : BehaviourTree public partial class Homeowner1 : BehaviourTree
{ {
Transform _thisTransform = null;
RigidBody _thisRigidbody = null;
GameObject _playerObject;
private LeafPatrol leafPatrol;
protected override void AwakeCall()
{
_thisTransform = GetComponent<Transform>();
if (!_thisTransform)
Debug.LogError("EMPTY TRANSFORM");
_thisRigidbody = GetComponent<RigidBody>();
if (!_thisRigidbody)
Debug.LogError("EMPTY RIGIDBODY");
if (!player)
Debug.Log("PLAYER MISSING!");
//_playerObject = GameObject.Find("Player").GetValueOrDefault();
}
//Called at the start //Called at the start
protected override void Initialise() protected override void Initialise()
{ {
_events = new Homeowner1Events(this); _events = new Homeowner1Events(this);
events.Initialise(); events.Initialise();
//Initialise the waypoints here
if (waypointsPool)
{
waypoints = (List<GameObject>)waypointsPool.GetChildren();
SetData("waypoints", waypoints);
}
} }
//Called every tick //Called every tick
@ -112,8 +97,8 @@ public partial class Homeowner1 : BehaviourTree
{ {
events.Tick(); events.Tick();
//Footsteps SFX, move them somewhere else soon
float velocity = GetComponent<RigidBody>().LinearVelocity.GetMagnitude(); float velocity = GetComponent<RigidBody>().LinearVelocity.GetMagnitude();
leafPatrol.waypoints = waypoints;
footstepTimeRemaining -= velocity * Time.DeltaTimeF; footstepTimeRemaining -= velocity * Time.DeltaTimeF;
if (footstepTimeRemaining < 0.0f) if (footstepTimeRemaining < 0.0f)
@ -128,22 +113,20 @@ public partial class Homeowner1 : BehaviourTree
//The tree is called from the root every tick //The tree is called from the root every tick
protected override BehaviourTreeNode CreateTree() protected override BehaviourTreeNode CreateTree()
{ {
leafPatrol = new LeafPatrol("Patrol", _thisTransform, waypoints, patrolSpeed, turningSpeed, _thisRigidbody);
//Start from the root, structure it like this to make it look like a tree //Start from the root, structure it like this to make it look like a tree
BehaviourTreeNode root = new BehaviourTreeSelector("Root", new List<BehaviourTreeNode> BehaviourTreeNode root = new BehaviourTreeSelector("Root", new List<BehaviourTreeNode>
{ {
/* new BehaviourTreeSequence("Alerted", new List<BehaviourTreeNode> new BehaviourTreeSequence("Alerted", new List<BehaviourTreeNode>
{ {
new LeafSearch("SearchFOV", _thisTransform, eyeOffset, sightDistance), new LeafSearch("SearchFOV", GetComponent<Transform>(), eyeOffset, sightDistance),
new BehaviourTreeSequence("CatchPlayer", new List<BehaviourTreeNode> new BehaviourTreeSequence("CatchPlayer", new List<BehaviourTreeNode>
{ {
new LeafChase("Chasing", _thisTransform, _thisRigidbody, chaseSpeed, turningSpeed, distanceToCapture, captureTime), new LeafChase("Chasing", GetComponent<Transform>(), GetComponent<RigidBody>(), chaseSpeed, turningSpeed, distanceToCapture, captureTime),
new LeafAttack("Attacking") new LeafAttack("Attacking", GameObject.Find("Player").GetValueOrDefault())
}) })
}),*/ }),
leafPatrol new LeafPatrol("Patrol", GetComponent<Transform>(), patrolSpeed, turningSpeed, GetComponent<RigidBody>())
}); });
return root; return root;
} }
} }

View File

@ -28,22 +28,13 @@ public partial class LeafAttack : BehaviourTreeNode
//FUNCTIONS //FUNCTIONS
public partial class LeafAttack : BehaviourTreeNode public partial class LeafAttack : BehaviourTreeNode
{ {
public LeafAttack(string name) : base (name) public LeafAttack(string name, GameObject p) : base (name)
{ {
//player = p; player = p;
} }
public override BehaviourTreeNodeStatus Evaluate() public override BehaviourTreeNodeStatus Evaluate()
{ {
{
if (!player)
{
player = GameObject.Find("Player").GetValueOrDefault();
Debug.Log("HERE2");
if (!player) { return BehaviourTreeNodeStatus.FAILURE; }
}
}
//Debug.LogWarning("LeafAttack"); //Debug.LogWarning("LeafAttack");
//Fail if no target in blackboard? //Fail if no target in blackboard?

View File

@ -22,10 +22,10 @@ public partial class LeafPatrol : BehaviourTreeNode
{ {
//Waypoints and movement //Waypoints and movement
private Transform transform; private Transform transform;
public List<Vector3> waypoints; private List<GameObject> waypoints;
private RigidBody rb; private RigidBody rb;
private float patrolSpeed = 1.0f; private float patrolSpeed;
private float turningSpeed = 5.0f; private float turningSpeed;
private float retreatTimer = 0.0f; private float retreatTimer = 0.0f;
private int currentWaypointIndex = 0; private int currentWaypointIndex = 0;
private bool retreatState = false; private bool retreatState = false;
@ -42,10 +42,9 @@ public partial class LeafPatrol : BehaviourTreeNode
//Constructor, establish values here //Constructor, establish values here
//Despite inheriting from BehaviourTreeNode, we don't have children to this //Despite inheriting from BehaviourTreeNode, we don't have children to this
//node, and hence we do not need to inherit its constructors //node, and hence we do not need to inherit its constructors
public LeafPatrol(string name, Transform t, List<Vector3> wps, float patrolSpeed, float turnSpeed, RigidBody rb) : base(name) public LeafPatrol(string name, Transform t, float patrolSpeed, float turnSpeed, RigidBody rb) : base(name)
{ {
transform = t; transform = t;
waypoints = wps;
this.patrolSpeed = patrolSpeed; this.patrolSpeed = patrolSpeed;
turningSpeed = turnSpeed; turningSpeed = turnSpeed;
this.rb = rb; this.rb = rb;
@ -59,6 +58,10 @@ public partial class LeafPatrol : BehaviourTreeNode
{ {
//Debug.LogWarning("LeafPatrol"); //Debug.LogWarning("LeafPatrol");
onEnter(BehaviourTreeNodeStatus.RUNNING); onEnter(BehaviourTreeNodeStatus.RUNNING);
if(GetNodeData("currentWaypointIndex") == null)
{
SetNodeData("currentWaypointIndex", 0);
}
if (isWaiting) DelayAtWaypoint(); if (isWaiting) DelayAtWaypoint();
else MoveToWaypoint(); else MoveToWaypoint();
@ -80,27 +83,25 @@ public partial class LeafPatrol : BehaviourTreeNode
return; return;
} }
Vector3 remainingDistance = Vector3.Zero; waypoints = (List<GameObject>)GetNodeData("waypoints");
Debug.Log($"{waypoints.Count}"); Vector3 targetPosition = waypoints[currentWaypointIndex].GetComponent<Transform>().GlobalPosition;
if (currentWaypointIndex > 0)
{
Vector3 targetPosition = waypoints[currentWaypointIndex]; //Reach waypoint by X and Z being near enough
//Do not consider Y of waypoints yet
//Reach waypoint by X and Z being near enough Vector3 remainingDistance = targetPosition - transform.GlobalPosition;
//Do not consider Y of waypoints yet remainingDistance.y = 0.0f;
remainingDistance = targetPosition - transform.GlobalPosition;
remainingDistance.y = 0.0f;
}
//Reached waypoint, cycle //Reached waypoint, cycle
if (remainingDistance.GetSqrMagnitude() < 0.1f) if (remainingDistance.GetSqrMagnitude() < 0.1f)
{ {
//Cycle waypoints //Cycle waypoints
++currentWaypointIndex; ++currentWaypointIndex;
if (currentWaypointIndex >= waypoints.Count) if (currentWaypointIndex >= waypoints.Count())
currentWaypointIndex = 0; currentWaypointIndex = 0;
//Write to blackboard
SetNodeData("currentWaypointIndex", currentWaypointIndex);
waitCounter = 0.0f; waitCounter = 0.0f;
isWaiting = true; isWaiting = true;
} }
@ -115,7 +116,7 @@ public partial class LeafPatrol : BehaviourTreeNode
//Get the difference vector to the waypoint //Get the difference vector to the waypoint
//Debug.Log("Current Waypoint " + waypoints[currentWaypointIndex].x.ToString() + " " + waypoints[currentWaypointIndex].y.ToString() + " " + waypoints[currentWaypointIndex].z.ToString()); //Debug.Log("Current Waypoint " + waypoints[currentWaypointIndex].x.ToString() + " " + waypoints[currentWaypointIndex].y.ToString() + " " + waypoints[currentWaypointIndex].z.ToString());
//Debug.Log("AI is at " + transform.GlobalPosition.x.ToString() + " " + transform.GlobalPosition.y.ToString() + " " + transform.GlobalPosition.z.ToString()); //Debug.Log("AI is at " + transform.GlobalPosition.x.ToString() + " " + transform.GlobalPosition.y.ToString() + " " + transform.GlobalPosition.z.ToString());
Vector3 normalisedDifference = waypoints[currentWaypointIndex] - transform.GlobalPosition; Vector3 normalisedDifference = targetPosition - transform.GlobalPosition;
normalisedDifference.y = 0.0f; //Do not move vertically normalisedDifference.y = 0.0f; //Do not move vertically
normalisedDifference /= normalisedDifference.GetMagnitude(); normalisedDifference /= normalisedDifference.GetMagnitude();
//Debug.Log("Normalised Difference x " + normalisedDifference.x.ToString() + " z " + normalisedDifference.z.ToString()); //Debug.Log("Normalised Difference x " + normalisedDifference.x.ToString() + " z " + normalisedDifference.z.ToString());

View File

@ -21,11 +21,10 @@ using System.Threading.Tasks;
//VARIABLES HERE //VARIABLES HERE
public partial class LeafSearch : BehaviourTreeNode public partial class LeafSearch : BehaviourTreeNode
{ {
private GameObject player;
private Transform transform; private Transform transform;
private Vector3 eyeOffset; private Vector3 eyeOffset;
private float sightDistance; private float sightDistance;
private GameObject? player; //To be searched for and marked
} }
//FUNCTIONS HERE //FUNCTIONS HERE
@ -33,111 +32,143 @@ public partial class LeafSearch : BehaviourTreeNode
{ {
public LeafSearch(string name, Transform t, Vector3 eo, float sDist) : base(name) public LeafSearch(string name, Transform t, Vector3 eo, float sDist) : base(name)
{ {
Debug.Log($"===============================PLAYER: {t}");
// player = p;
transform = t; transform = t;
eyeOffset = eo; eyeOffset = eo;
sightDistance = sDist; sightDistance = sDist;
player = null;
}
//Helper, find the nearest unobstructed waypoint to return to when chase is over
private void reevaluateWaypoint()
{
Debug.Log("Reevaluating Waypoints");
List<GameObject> waypoints = (List<GameObject>)GetNodeData("waypoints");
if (waypoints == null)
{
SetNodeData("currentWaypointIndex", 0);
return;
}
int nearestWaypointIndex = 0;
for (int i = 0; i < waypoints.Count; ++i)
{
if ((transform.GlobalPosition - waypoints[i].GetComponent<Transform>().GlobalPosition).GetSqrMagnitude() <
(transform.GlobalPosition - waypoints[nearestWaypointIndex].GetComponent<Transform>().GlobalPosition).GetSqrMagnitude())
{
nearestWaypointIndex = i;
}
}
SetNodeData("currentWaypointIndex", nearestWaypointIndex);
} }
public override BehaviourTreeNodeStatus Evaluate() public override BehaviourTreeNodeStatus Evaluate()
{ {
{
if (!player)
{
player = GameObject.Find("Player").GetValueOrDefault();
if (!player) { Debug.Log("HERE1"); return BehaviourTreeNodeStatus.FAILURE; }
}
}
//Debug.LogWarning("LeafSearch"); //Debug.LogWarning("LeafSearch");
onEnter(BehaviourTreeNodeStatus.RUNNING); onEnter(BehaviourTreeNodeStatus.RUNNING);
//Fail if unable to find a player //Search for player
//Get player's transform player = GameObject.Find("Player");
Transform plrT = player.GetComponent<Transform>();
//DELETE THIS //Automatically fail if no player is found
//Debug.Log("X " + MathF.Sin(transform.LocalEulerAngles.y).ToString() + " Z " + MathF.Cos(transform.LocalEulerAngles.y).ToString()); if (player == null)
//Debug.Log("Looking at: " + transform.LocalRotation.y.ToString() + " To player is: " + temporary.ToString());
//Debug.Log("Look difference is: " + (transform.LocalRotation.y - differenceDirection.y).ToString());
//Debug.Log("Dot: " + Quaternion.Dot(differenceDirection, transform.GlobalRotation));
//Fail if too far from vision range
if ((plrT.GlobalPosition - transform.GlobalPosition).GetMagnitude() > sightDistance)
{ {
//Debug.Log("Failure: Too far");
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true)
{
Debug.Log("AI play unalert hmm");
}
SetNodeData("isAlert", false); SetNodeData("isAlert", false);
status = BehaviourTreeNodeStatus.FAILURE; status = BehaviourTreeNodeStatus.FAILURE;
onExit(BehaviourTreeNodeStatus.FAILURE); onExit(BehaviourTreeNodeStatus.FAILURE);
return status; return status;
} }
else
//Fail if player is out of FOV
//TODO currently a simple dot product against negative is done, this makes it essentially be a semicircle in front at which AI can see
Vector3 difference = plrT.GlobalPosition - transform.GlobalPosition;
difference.y = 0.0f; //Disregard Y axis
Vector3 lookDirection = new Vector3(MathF.Sin(transform.LocalEulerAngles.y), 0.0f, MathF.Cos(transform.LocalEulerAngles.y));
//Debug.Log("Dot: " + Vector3.Dot(difference, lookDirection));
if (Vector3.Dot(difference, lookDirection) < 0.0f)
{ {
//Debug.Log("Failure: Out of FOV"); //Fail if unable to find a player
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) //Get player's transform
Transform plrT = player.GetValueOrDefault().GetComponent<Transform>();
//DELETE THIS
//Debug.Log("X " + MathF.Sin(transform.LocalEulerAngles.y).ToString() + " Z " + MathF.Cos(transform.LocalEulerAngles.y).ToString());
//Debug.Log("Looking at: " + transform.LocalRotation.y.ToString() + " To player is: " + temporary.ToString());
//Debug.Log("Look difference is: " + (transform.LocalRotation.y - differenceDirection.y).ToString());
//Debug.Log("Dot: " + Quaternion.Dot(differenceDirection, transform.GlobalRotation));
//Fail if too far from vision range
if ((plrT.GlobalPosition - transform.GlobalPosition).GetMagnitude() > sightDistance)
{ {
Debug.Log("AI play unalert hmm"); //Debug.Log("Failure: Too far");
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true)
{
Debug.Log("AI play unalert hmm");
reevaluateWaypoint();
}
SetNodeData("isAlert", false);
status = BehaviourTreeNodeStatus.FAILURE;
onExit(BehaviourTreeNodeStatus.FAILURE);
return status;
} }
SetNodeData("isAlert", false);
status = BehaviourTreeNodeStatus.FAILURE; //Fail if player is out of FOV
onExit(BehaviourTreeNodeStatus.FAILURE); //TODO currently a simple dot product against negative is done, this makes it essentially be a semicircle in front at which AI can see
Vector3 difference = plrT.GlobalPosition - transform.GlobalPosition;
difference.y = 0.0f; //Disregard Y axis
Vector3 lookDirection = new Vector3(MathF.Sin(transform.LocalEulerAngles.y), 0.0f, MathF.Cos(transform.LocalEulerAngles.y));
//Debug.Log("Dot: " + Vector3.Dot(difference, lookDirection));
if (Vector3.Dot(difference, lookDirection) < 0.0f)
{
//Debug.Log("Failure: Out of FOV");
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true)
{
Debug.Log("AI play unalert hmm");
reevaluateWaypoint();
}
SetNodeData("isAlert", false);
status = BehaviourTreeNodeStatus.FAILURE;
onExit(BehaviourTreeNodeStatus.FAILURE);
return status;
}
//LocalRotation is between -1 and 1, which are essentially the same.
//0 and -1/1 are 180 deg apart
//Quaternion differenceDirection = Quaternion.FromToRotation(Vector3.Forward, plrT.GlobalPosition - transform.GlobalPosition);
//Debug.Log("Looking at: " + transform.LocalRotation.y.ToString() + " To player is: " + differenceDirection.y.ToString());
//Draw a ray, succeed if ray is unobstructed
Vector3 eyePosition = transform.GlobalPosition + eyeOffset;
Ray sightRay = new Ray(eyePosition, plrT.GlobalPosition - eyePosition);
RaycastHit sightRayHit = Physics.Raycast(sightRay);
//As of November 2022, RaycastHit contains only the FIRST object hit by
//the ray in the Other GameObject data member
//Diren may likely add ALL objects hit by the ray over December
if (sightRayHit.Hit && sightRayHit.Other != player)
{
//Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId);
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true)
{
Debug.Log("AI play unalert hmm");
reevaluateWaypoint();
}
SetNodeData("isAlert", false);
status = BehaviourTreeNodeStatus.FAILURE;
onExit(BehaviourTreeNodeStatus.FAILURE);
return status;
}
else if (sightRayHit.Hit && sightRayHit.Other == player)
{
//Debug.Log("Ray hit player");
}
//All checks for now succeeded
//Debug.Log("Success: Homeowner has sighted player");
//Write player's transform into the blackboard
SetNodeData("target", plrT);
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false)
{
Debug.Log("AI Play Alerted Yell here");
}
SetNodeData("isAlert", true);
status = BehaviourTreeNodeStatus.SUCCESS;
onExit(BehaviourTreeNodeStatus.SUCCESS);
return status; return status;
} }
//LocalRotation is between -1 and 1, which are essentially the same.
//0 and -1/1 are 180 deg apart
//Quaternion differenceDirection = Quaternion.FromToRotation(Vector3.Forward, plrT.GlobalPosition - transform.GlobalPosition);
//Debug.Log("Looking at: " + transform.LocalRotation.y.ToString() + " To player is: " + differenceDirection.y.ToString());
//Draw a ray, succeed if ray is unobstructed
Vector3 eyePosition = transform.GlobalPosition + eyeOffset;
Ray sightRay = new Ray(eyePosition, plrT.GlobalPosition - eyePosition);
RaycastHit sightRayHit = Physics.Raycast(sightRay);
//As of November 2022, RaycastHit contains only the FIRST object hit by
//the ray in the Other GameObject data member
//Diren may likely add ALL objects hit by the ray over December
if (sightRayHit.Hit && sightRayHit.Other != player)
{
//Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId);
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true)
{
Debug.Log("AI play unalert hmm");
}
SetNodeData("isAlert", false);
status = BehaviourTreeNodeStatus.FAILURE;
onExit(BehaviourTreeNodeStatus.FAILURE);
return status;
}
else if (sightRayHit.Hit && sightRayHit.Other == player)
{
//Debug.Log("Ray hit player");
}
//All checks for now succeeded
//Debug.Log("Success: Homeowner has sighted player");
//Write player's transform into the blackboard
SetNodeData("target", plrT);
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false)
{
Debug.Log("AI Play Alerted Yell here");
}
SetNodeData("isAlert", true);
status = BehaviourTreeNodeStatus.SUCCESS;
onExit(BehaviourTreeNodeStatus.SUCCESS);
return status;
} }
} }