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. Enemy

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:

  • Voronoi Graphics: Drawing shapes across voronoi edges
  • Markov Chains
  • AI For Games: Week 11
  • AI For Games: Week 10
  • AI For Games: Week 9