AI For Games: Week 5
Developing the AI
The AI will take up the same scene structure as our player, the differences will appear in the script.
The script
Godot returns paths along a navigation map in the form of an array of vectors. To make our enemy move along paths we simply have it interpolate between each point until it reaches the end of the path.
Spotting the player
Next up, we’ll have the enemy look to see if they can see the player. For this we just draw a ray between the enemy and the player, if it hits nothing then it can see the player. This is almost done, but we want the enemy to only be able to see in front of it, so we get the angle between the two points and add the rotation of the enemy, if its within 120 degrees of the enemies front then the enemy can see the player.
Now we can increment a counter to determine the level of detection the enemy is in. If it has seen the player for a second then the enemy will look at the player, if it can see the player for longer the enemy will try to shoot the player (meaning if it sees it for a few seconds the player will die). Finally, if the enemy has detected the player, it will store the position the player was in before the enemy stops being able to see the player, the enemy will then move to that position.
These behaviours make the enemy seem more realistic, without this code the enemy would just know instantly where the player is. This can also be called “Artificial Stupidity”
Multiple steering behaviours & Finite State Machine
So my system implements a finitie state machine, they do go hand in hand. The Finite state machine consists of enums stating what the enemy is doing, if it’s SUSPICIOUS or CALM. The steering behaviours are what the enemy is doing, be it investigating the last known location of the player, attempting to kill it or simply patrolling between the nodes. The finite state machine is dictated by wether or not the enemy can see the player.
Here’s the important code for the finite state machine:
# Finite state machine
if canSee:
if state == CALM:
# increase detection based on distance to player
detection += delta * (15 - ppos.distance_to(pos))
# increase size of '?' image under rook
alert.region_rect.size.y = int(detection);
# if detected enough, look at player
if detection >= 4:
detection += delta*4
look_at(Vector3(ppos.x, 0, ppos.z), Vector3(0, 1, 0))
rotate_object_local(Vector3(0,1,0), deg2rad(180))
# up state to suspicious if seen for long enough
if detection >= 16:
state = SUSPICIOUS
path = null
pathProgress = 0
pathPoint = 0
alert.modulate = Color(1, 1, 0)
elif state == SUSPICIOUS:
# look at player
look_at(Vector3(ppos.x, 0, ppos.z), Vector3(0, 1, 0))
rotate_object_local(Vector3(0,1,0), deg2rad(180))
detection = 16
alert.region_rect.size.y = int(detection);
# always move last known location
lastKnown = ppos
lastloc.hide()
# dont pathfind
path = null
pathProgress = 0
pathPoint = 0
# aim and kill player
aimtimer += delta
if aimtimer > 2:
get_node("/root/testworld").queue_free()
else:
if state == CALM:
# create path if none, switching between 2 points
if path == null:
if endPoint:
path = get_node("/root/testworld/Navigation").get_simple_path(transform.origin, startPos, false)
else:
path = get_node("/root/testworld/Navigation").get_simple_path(transform.origin, endPath, false)
endPoint = !endPoint
# remove y axis
for i in range(0, path.size()):
path[i].y = 0;
#move along path
if pathProgress >= 1 and pathPoint < len(path)-1:
pathPoint+=1;
pathProgress = 0;
elif pathPoint >= len(path)-1:
pathProgress = 0
pathPoint = 0
path = null
pathProgress += 0.05
if path != null:
transform.origin = transform.origin.linear_interpolate(path[pathPoint], pathProgress)
# lower detection to 0
if detection > 0:
detection -= 2*delta
alert.region_rect.size.y = int(detection);
elif state == SUSPICIOUS:
# can't see, reset aim timer
aimtimer = 0
# look at last known location
look_at(Vector3(lastKnown.x, 0, lastKnown.z), Vector3(0, 1, 0))
rotate_object_local(Vector3(0,1,0), deg2rad(180))
lastloc.show();
# create path to last location if none
if path == null:
path = get_node("/root/testworld/Navigation").get_simple_path(transform.origin, lastKnown, false)
# remove y axis
for i in range(0, path.size()):
path[i].y = 0;
# move towards last location
if pathProgress >= 1 and pathPoint < len(path)-1:
pathPoint+=1;
pathProgress = 0;
elif pathPoint >= len(path)-1:
detection = 0
pathProgress += 0.05
transform.origin = transform.origin.linear_interpolate(path[pathPoint], pathProgress)
# lowerr detection
detection -= delta
alert.region_rect.size.y = int(detection);
if detection <= 0:
state = CALM
alert.modulate = Color(0, 1, 0)
lastloc.hide()
Enjoy Reading This Article?
Here are some more articles you might like to read next: