I am building my first game in which the player and a pig (the enemy !) are searching for mushrooms in a field. The more mushrooms you grab, the more points you win.
My problem is to build a decent AI for the pig : At first, I tried raycasting. This works, except that if the pig raycasts a mushroom, it runs in its direction to eat it but of course, if there is a mushroom nearby (not in front of the pig), it doesn’t grab that one (which, i suppose, a real pig would do).
My second idea was to add OnTriggerEnter on the mushrooms : if the pig triggers a mushroom nearby, the mushroom is “eaten”. But then, I have to tell the pig to change its direction, go and eat the mushroom, then keep on rushing on the mushroom that was raycasted before… Or else, you see a mushroom disappear is the pig runs nearby, which either is not realistic or it happens only when the pig is very close to the mushroom, but then it’s not that useful.
My next options are :
try to raycast using a loop function (as if the pig would look around, choose the nearest collider), but how to get that working, and get the pig facing it’s future target ?
try to use physics.overlapSphere, getting all the colliders in an array, evaluate the distance from the pig, then choose the nearest (math problem : considering what the pig is facing, how can i ignore the mushrooms that are close, but behind it ?)
Am I right or am I missing a simpler way to get things working ?
Thanks in advance for your help…
I thought about how to do it, and this is what i came up with:
//Amount of degrees left and right
public float range = 90f;
//Oscillating variable
private float oscillate;
//Is raycast going clockwise?
private bool clockWise = true;
//Array for all the hits
private RaycastHit[] hitArray = new RaycastHit[75];
//Array index counter
private int arrayCounter = 0;
void Update()
{
//Oscillation
oscillate = Mathf.PingPong(Time.time, 2) - 1;
//If we reach the end of the oscillation:
if(oscillate > 0.95)
{
//Making sure it only fires once...
if (clockWise)
{
//Check hit array
CheckHitArray();
}
//...because we set it false here
clockWise = false;
}
//If we reach the beginning of the oscillation:
if(oscillate < -0.95)
{
//Making sure it only fires once...
if (!clockWise)
{
//Check hit array
CheckHitArray();
}
//..because we set it false here
clockWise = true;
}
//Change the angle of the object, according to the oscillation
transform.localEulerAngles = new Vector3(0, oscillate * range, 0);
//Create a new ray and raycasthit
Ray ray = new Ray(transform.position, transform.TransformDirection(Vector3.forward));
RaycastHit hit;
//If we hit something
if(Physics.Raycast(ray, out hit, 10f))
{
//Add it to the array
hitArray[arrayCounter] = hit;
//add 1 to the counter
arrayCounter++;
}
//Show the ray (not a must but easy)
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward));
}
//What is the closest hit ?
private GameObject closestHit;
//The distance to the closest hit, 1000 because we just want it to be a lot
private float closestHitDistance = 1000;
private void CheckHitArray()
{
//Loop through the array
for (int i = 0; i < arrayCounter; i++)
{
//Check distance
float distance = Vector3.Distance(hitArray*.collider.gameObject.transform.position, transform.position);*
//If distance is smaller than previous distances (thats why the 1000 is there) if (distance < closestHitDistance) { //Set new distance and object closestHitDistance = distance; closestHit = hitArray*.collider.gameObject;* } } //When done with loop, send closest one to method SetTarget(closestHit); //Reset all the values hitArray = new RaycastHit[75]; arrayCounter = 0; closestHit = null; closestHitDistance = 1000; }
//This can be something you want, this is just for demonstrating purposes private void SetTarget(GameObject target) { Debug.Log(target.name); } Its an object, that has a field of view now set on 180 degrees, so no looking behind you!! It moves the raycast back and forth and detects all the objects in its “field”. It then selects the closest object. By creating different methods yourself, you can control what your piggie is doing: 1. Look for mushrooms with raycast. 2. If we found the closest mushroom, stop looking with raycast. 3. Go to the mushroom 4. When we have eaten the mushroom, start looking with raycast again. This is something you can easily do yourself, i think! Good luck, hope this will get you in the right direction.
Concerning this part of your question: “(math problem : considering what the pig is facing, how can i ignore the mushrooms that are close, but behind it ?)”
Performance wise concidering the options you provided, physics.overlapSphere would be the best option. Therefore, to solve your math problem, use Vector3.[Angle()][1]. The 2 values you need for that are the direction you are looking at and the direction towards the mushroom, both of them are easilly obtainable.
using UnityEngine;
public class AIExample : MonoBehaviour
{
public GameObject closestMushroom; // I make this public so you can see it change in the inspector
public LayerMask mushroomlayer; // The layers in which the mushrooms are placed, to make sure the pig chases only mushrooms and not humans or his own tail...
public float distance = 5f; // How far away the pig can see mushrooms
[Range(0, 180)]
public float maxAngle = 45; // basically, the field of view of the pig
void Update()
{
Ray ray = new Ray(transform.position, transform.forward);
Collider[] hits = Physics.OverlapSphere(transform.position, distance, mushroomlayer);
Collider bestHit = null;
float bestHitDistance = float.MaxValue;
for (int i = 0; i < hits.Length; i++)
{
Vector3 directionToMushroom = hits*.transform.position - transform.position;*
// Check if the mushroom is inside the pigs fov*
if (Vector3.Angle(transform.forward, directionToMushroom) < maxAngle)*
{*
// Check if the mushroom is the closest mushroom or not*
if (bestHit == null || directionToMushroom.sqrMagnitude < bestHitDistance)*
* if (bestHit != null)* * closestMushroom = bestHit.gameObject;* * }* } In this example the angle can be changed to give te pig a larger or smaller fov. (180 being the max as a angle larger then 180 (behind he pig) is impossible. With small tweaks you should be able to change his ai further. For example, now it focuses the closest item in sight. But it could easilly be changed to the one with the lowest angle (in front of him) by storing bestHitAngle instead of bestHitDistance.
_*[1]: https://docs.unity3d.com/ScriptReference/Vector3.Angle.html*_
Hi, thank you everyone, I will try your scripts which look interesting. I finally came to the conclusion that raycasting might not be needed, and I used the example provided for FindObjectsWithTag here : Unity - Scripting API: GameObject.FindGameObjectsWithTag
It works perfectly, though I have to figure how not taking care of the mushrooms that are behind the pig.
Another problem is… my pig is now getting TOO clever !!!