210 lines
7.9 KiB
C#
210 lines
7.9 KiB
C#
/*********************************************************************
|
|
* \file LeafSearch.cs
|
|
* \author Ryan Wang Nian Jing
|
|
* \brief Leaf node implementation for AI searching for player
|
|
*
|
|
*
|
|
* \copyright Copyright (c) 2022 DigiPen Institute of Technology. Reproduction
|
|
or disclosure of this file or its contents without the prior written
|
|
consent of DigiPen Institute of Technology is prohibited.
|
|
*********************************************************************/
|
|
|
|
using SHADE;
|
|
using SHADE_Scripting.AIBehaviour.BehaviourTree;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
//VARIABLES HERE
|
|
public partial class LeafSearch : BehaviourTreeNode
|
|
{
|
|
private Transform transform;
|
|
private Vector3 eyeOffset;
|
|
private float sightDistance;
|
|
private GameObject? player; //To be searched for and marked
|
|
}
|
|
|
|
//FUNCTIONS HERE
|
|
public partial class LeafSearch : BehaviourTreeNode
|
|
{
|
|
public LeafSearch(string name) : base(name)
|
|
{
|
|
//Debug.Log("LeafSearch ctor");
|
|
}
|
|
|
|
//Helper, find the nearest unobstructed waypoint to return to when chase is over
|
|
public void reevaluateWaypoint()
|
|
{
|
|
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);
|
|
}
|
|
|
|
//Helper for handling being alert
|
|
|
|
//Helper for handling stopping of chases
|
|
private void handleChaseStop()
|
|
{
|
|
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true)
|
|
{
|
|
Audio.PlaySFXOnce2D("event:/Homeowner/humming");
|
|
reevaluateWaypoint();
|
|
}
|
|
SetNodeData("isAlert", false);
|
|
}
|
|
|
|
public override BehaviourTreeNodeStatus Evaluate()
|
|
{
|
|
//Debug.LogWarning("LeafSearch");
|
|
onEnter(BehaviourTreeNodeStatus.RUNNING);
|
|
|
|
//Get data
|
|
if (GetNodeData("transform") == null ||
|
|
GetNodeData("eyeOffset") == null ||
|
|
GetNodeData("sightDistance") == null)
|
|
{
|
|
status = BehaviourTreeNodeStatus.FAILURE;
|
|
onExit(BehaviourTreeNodeStatus.FAILURE);
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
transform = (Transform)GetNodeData("transform");
|
|
eyeOffset = (Vector3)GetNodeData("eyeOffset");
|
|
sightDistance = (float)GetNodeData("sightDistance");
|
|
}
|
|
|
|
//Search for player
|
|
player = GameObject.Find("Player");
|
|
|
|
//Automatically fail if no player is found
|
|
if (player == null)
|
|
{
|
|
SetNodeData("isAlert", false);
|
|
status = BehaviourTreeNodeStatus.FAILURE;
|
|
onExit(BehaviourTreeNodeStatus.FAILURE);
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
//Fail if unable to find a player
|
|
//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("Failure: Too far");
|
|
//handleChaseStop();
|
|
status = BehaviourTreeNodeStatus.FAILURE;
|
|
onExit(BehaviourTreeNodeStatus.FAILURE);
|
|
return status;
|
|
}
|
|
|
|
//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");
|
|
//handleChaseStop();
|
|
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;
|
|
BoxCollider playerCollider = player.GetValueOrDefault().GetComponent<Collider>().GetCollisionShape<BoxCollider>(0);
|
|
if (playerCollider == null)
|
|
{
|
|
//Debug.Log("Failure: Player has no collider");
|
|
status = BehaviourTreeNodeStatus.FAILURE;
|
|
onExit(BehaviourTreeNodeStatus.FAILURE);
|
|
return status;
|
|
}
|
|
//Ray destination to target the centre of the player's collider instead of transform position
|
|
//Since transform position is often the raccoon's base and the ray needs to hit somewhere higher to be more reliable
|
|
Vector3 rayDestination = plrT.GlobalPosition + plrT.GlobalScale * playerCollider.PositionOffset;
|
|
Ray sightRay = new Ray(eyePosition, rayDestination - eyePosition);
|
|
RaycastHit sightRayHit = Physics.Raycast(sightRay, false)[0];
|
|
//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)
|
|
{
|
|
//TODO sometimes the ray doesn't hit the player even if he's in plain sight because the ray hits the floor the player is on instead???
|
|
//Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId);
|
|
//handleChaseStop();
|
|
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)
|
|
{
|
|
SetNodeData("isAlert", true);
|
|
Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon");
|
|
Audio.PlaySFXOnce2D("event:/Music/stingers/player_detected");
|
|
Audio.StopAllSounds();
|
|
Audio.PlayBGMOnce2D("event:/Music/player_detected");
|
|
}
|
|
else
|
|
{
|
|
if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false)
|
|
{
|
|
Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon");
|
|
Audio.PlaySFXOnce2D("event:/Music/stingers/player_detected");
|
|
Audio.StopAllSounds();
|
|
Audio.PlayBGMOnce2D("event:/Music/player_detected");
|
|
}
|
|
SetNodeData("isAlert", true);
|
|
}
|
|
|
|
status = BehaviourTreeNodeStatus.SUCCESS;
|
|
onExit(BehaviourTreeNodeStatus.SUCCESS);
|
|
return status;
|
|
}
|
|
}
|
|
} |