- Home /
AI states switching before functions in state finishes
Hi, I wrote the following code, and for some reason or the other, it will just keep switching between the follow and find state. The find state has a pathfinding function in it that should be executed to find a path, and then move the enemy along that path. For some reason it won't do this, and instead switches between the two states every frame or so.
#pragma strict
import System.Collections.Generic;
var startNode: node;
var targetNode: node;
var curNode: node;
var player: Transform;
var map: AstarGrid;
map = Terrain.activeTerrain.GetComponent(AstarGrid);
var openList = new List.<node>();
var closedList = new List.<node>();
var pathList = new List.<node>();
var neighbors: node[];
neighbors = new node[4];
var mapScale: float;
var speed: float = 5;
enum AIState
{
idle = 0,
attack = 1,
follow = 2,
find = 3,
death =4
}
var state: AIState = AIState.follow;
function Awake()
{
player = GameObject.FindWithTag("Player").transform;
}
function Start()
{
mapScale = Terrain.activeTerrain.GetComponent(AstarGrid).nodeSize;
InitStartFinish(transform,player);
}
function Update()
{
switch(state)
{
case AIState.follow:
var layerMask = 1 << 17;
layerMask = ~layerMask;
if (!Physics.Linecast(transform.position, player.position, layerMask))
{
transform.LookAt(Vector3(player.position.x,transform.position.y,player.position.z));
if(Vector3.Distance(transform.position,player.position)>3)
{
transform.Translate(Vector3.forward*speed*Time.deltaTime);
}
Debug.DrawLine (transform.position, player.position, Color.green);
print("following");
}
else
{
state = AIState.find;
}
break;
case AIState.find:
if (Physics.Linecast(transform.position, player.position, layerMask))
{
FindPath(curNode);
print("pathfinding");
}
else
{
state = AIState.follow;
}
break;
case AIState.idle:
print("Idling");
break;
case AIState.attack:
print("attacking");
break;
case AIState.death:
print("dieing");
break;
}
}
//Set pixel based positions for start & end nodes.
function InitStartFinish(cur: Transform, target: Transform)
{
var tempNode: node;
tempNode = new node();
tempNode.pos.x = Mathf.Round(cur.position.x/mapScale);
tempNode.pos.y = Mathf.Round(cur.position.z/mapScale);
for(var i:int=0;i<map.nodes.Length;i++)
{
if(map.nodes[i].pos == tempNode.pos)
{
startNode = map.nodes[i];
}
}
tempNode.pos.x = Mathf.Round(target.position.x/mapScale);
tempNode.pos.y = Mathf.Round(target.position.z/mapScale);
for(i=0;i<map.nodes.Length;i++)
{
if(map.nodes[i].pos == tempNode.pos)
{
targetNode = map.nodes[i];
}
}
curNode = startNode;
openList.Add(curNode);
CalcF(curNode);
}
//Cleans pathfinding arrays and such for the start of a new search.
function clearPath()
{
map.InitGrid();
openList = new List.<node>();
closedList = new List.<node>();
pathList = new List.<node>();
}
function FindPath(cur: node)
{
InitStartFinish(transform,player);
while(curNode != targetNode)
{
//remove current node from open and add to closed
openList.Remove(cur);
closedList.Add(cur);
//find neighbor nodes
neighbors[0] = map.nodes[cur.ID-map.gridSize];
neighbors[1] = map.nodes[cur.ID+1];
neighbors[2] = map.nodes[cur.ID+map.gridSize];
neighbors[3] = map.nodes[cur.ID-1];
//for each neighboring node, check if it is walkable, not on either open or closed list, if so add to openlist, set parent to current node, and calculate F scores
for(var i:int=0;i<neighbors.Length;i++)
{
if(neighbors[i].walkable == true)
{
if(!closedList.Contains(neighbors[i]))
{
if(!openList.Contains(neighbors[i]))
{
openList.Add(neighbors[i]);
neighbors[i].parent = cur;
CalcF(neighbors[i]);
}
//if neighbor is in openlist, check G scores to see if current path is shorter or not. If so change parent to current node and recalculate F scores
if(openList.Contains(neighbors[i]))
{
if(cur.G+1< neighbors[i].G)
{
neighbors[i].parent = cur;
CalcF(neighbors[i]);
}
}
}
}
}
//Find lowest F in openList
curNode = openList[0];
for(i=0;i<openList.Count;i++)
{
if(openList[i].F < openList[0].F)
{
curNode = openList[i];
}
}
}
if(curNode == targetNode)
{
TracePath();
}
}
//calculates F score for a node
function CalcF(node: node)
{
//CalcG
if(node.parent==null)
{
node.G = 0;
}
else
{
node.G=1+node.parent.G;
}
//CalcH
node.H = Mathf.Abs(node.pos.x-targetNode.pos.x) + Mathf.Abs(node.pos.y-targetNode.pos.y);
//CalcF
node.F = node.G + node.H;
}
//traces path when pathfinding is complete, follows it and draws it on the map.
function TracePath()
{
var temp: node;
temp = targetNode;
for(var i:int=0;i<closedList.Count;i++)
{
Terrain.activeTerrain.GetComponent(AstarGrid).map.SetPixel(closedList[i].pos.x, closedList[i].pos.y, Color.yellow);
Terrain.activeTerrain.GetComponent(AstarGrid).map.Apply();
}
while(temp != startNode)
{
pathList.Add(temp);
Terrain.activeTerrain.GetComponent(AstarGrid).map.SetPixel(temp.pos.x, temp.pos.y, Color.red);
Terrain.activeTerrain.GetComponent(AstarGrid).map.Apply();
temp = temp.parent;
}
pathList.Reverse();
for(i=0;i<pathList.Count && Vector3.Distance(pathList[i].RLPos, transform.position)<1;i++)
{
transform.LookAt(Vector3(pathList[i].RLPos.x,transform.position.y,pathList[i].RLPos.z));
}
transform.position = Vector3.MoveTowards(transform.position, pathList[i].RLPos, Time.deltaTime*speed);
}
public class node
{
var ID: int;
var parent: node;
var pos: Vector2 = Vector2(0,0);
var RLPos: Vector3;
var walkable: boolean = true;
var F: int = 0;
var G: int = 0;
var H: int = 0;
}
So I'm wondering, is my pathfinding function not completing, or is it getting cut off by something, or what? Any tips to fix/improve this?
After reading some of the code instantly I see that in your switch statement you should probably not have the state changing here in the else statement:
switch(state)
{
case AIState.follow:
var layer$$anonymous$$ask = 1 << 17;
layer$$anonymous$$ask = ~layer$$anonymous$$ask;
if (!Physics.Linecast(transform.position, player.position, layer$$anonymous$$ask))
{
transform.LookAt(Vector3(player.position.x,transform.position.y,player.position.z));
if(Vector3.Distance(transform.position,player.position)>3)
{
transform.Translate(Vector3.forward*speed*Time.deltaTime);
}
Debug.DrawLine (transform.position, player.position, Color.green);
print("following");
}
// $$anonymous$$aybe look at moving the following code outside of the update function
// and control it somewhere else
else
{
state = AIState.find;
}
Why would I not want that? I do want the state to change to find when there is something blocking the path, so I should change it there, right?
well maybe the else is too vague, that the conditions are changing to the other state too easily and the else should become an else if. $$anonymous$$ake the criteria more rigid :D
Hmm that might work, but still not too sure why the states would keep switching when there are absolutely no changes in position
Answer by Owen-Reynolds · Feb 26, 2013 at 04:08 PM
The code says to quickly flip between find/follow when the linecast hits/misses. So maybe the linecast is occasionally hitting something?
The line goes from trans.position to player.position -- is that from your feet to the player's feet? If so, a tiny rise would block it. Could toss in a RayCastHit and print the name of what you're hitting (ex: "in follow: hit tree1")
I'd probably not have Follow flip to Find by being blocked for a single frame -- what if a tree got in the way? Might only switch to Find if I haven't seen the player for 0.5 second or more.
In Find, you're running a pathfind every frame. The path isn't going to change that much as the player moves (unless doors are closing, but you could have those signal you. Or does the player teleport?) Could only do a find if that last one was >5 seconds ago, or you reach the end.
As of right now my enemies aren't moving at all and all of them have something blocking their path. That's one of the reasons I find it strange that even tho nothing moves the states keep switching.
$$anonymous$$y pathfinding function should also not be called every frame, right now it's supposed to get a starting and end point, find a path to that point(this is what the while loop is for) then trace the path and move along it.
That's what one single call of that function is supposed to do. I thought it would complete that cycle before checking the linecast again, but If it really is being called all the time, I wonder how I could make it a one time call, before linecasting again to see if there's a straight path to the target or not.
I do agree that I should probably not switch to find state right after an obstruction, but that shouldn't be causing this kind of problem, right?
Your answer
![](https://koobas.hobune.stream/wayback/20220613093433im_/https://answers.unity.com/themes/thub/images/avi.jpg)