Enemy Programming Results

Sep 13, 2014 at 5:25 AM
Senior Member
"Wahoo! Upgrade!"
Join Date: Aug 11, 2014
Location:
Posts: 57
So that last topic died pretty fast. Its not far down on the page, but here's the link for any new readers:

http://www.cavestory.org/forums/threads/5586/

I tested out some of my hypotheses and thought I would make a followup post. Some of the stuff I proposed worked out ok, and some of it was crap. I did end up coding it as a finite state machine, but not an explicit one. The way it works is that there are a number of "code modules" that all perform individual actions. These are coded mostly in C++ with a thin link to lua, or coded wholly in lua when they involve some kind of interaction with other enemies.

These code modules are what I called in my last post as "states", though it turns out that's a very poor description because the actor could be in many "states" at the same time. So I took to calling them "agents". An agent doesn't represent a single entity in the world, but rather something attached to an entity that has the power to cause change. What you can then do is make many agents, each one representing a small simple action. Combinations of agents running all at once can produce some pretty complex behavior.

To control what combinations of agents are switched on and off, and controlling various other pieces of code, I ended up using coroutines to express real "states". An entity can be in one state at a time, that is, running one coroutine at a time. The entry point of the coroutine usually unpacks and ready's all of the resources and turns on all agents needed for that state, and closes them when the state is exited.



EDIT: yoookay, this video is not showing up for me... here's the raw link https://www.youtube.com/watch?v=rwyuaHtKieU

Everything you see here was programmed with this technique, and the soldier running on screen is being controlled by AI, not me, just to be clear. The coroutines presented below are the states that run the soldier. Obviously each function call has a pretty obnoxious amount of detail behind it, but the real decision making for the character, which I find to be the really hard part of enemy programming, is pretty much all contained in these small functions.

If anyone wants to see more of the code, I'm certainty happy to share, but I don't want to turn this page into a a huge code dump yet if no one asked for it. Also I'm still looking for ways to improve the method, so it people have suggestions, I'm all ears.

I should mention: one specific problem that I am indeed having is that I want to spit each sufficiently complex character class into 2 parts: a toolbox for agent activation and a decision engine just for running these threads. I've been doing that so far, and it seems to make the code a lot cleaner, but I'm getting some real bad cognitive dissonance from trying to follow good software engineering rules and creating 2 separate classes that are %100 coupled.

Anyway, here's that soldier code:

Code:
function av_soldier:navigate()
  --enable all resources for following a simple path
  self:set_move_agents_enable(true)
  --loop in the current state until we can spot an enemy
  local next_state = nil
  while next_state == nil and yield() do
  	--follow the current requested waypoint and sync resources to nav agent
        self:follow_nagivation_path()
        --break if are in a position to shoot an enemy
	if self:i_spotted_an_enemy() and not self.nav:get_on_jump() then
	     next_state = self.aim_at_enemy
        end
  end
  --wrap up all navigaton resources and hop to the next state
  self:set_move_agents_enable(false)
  return go_to(self, next_state)
end

function av_soldier:aim_at_enemy()
        --*sometimes*, have the unit voice what kind of enemy has been encountered
	self:auto_voice_enemy_contact()
	local next_state
	while next_state == nil and yield() do
		--find the proper facing direction and sync to that direction
		self:follow_nagivation_path()
		self:aim_at_spotted_enemy()
		--determine the next action by new information or lack thereof
		if self:should_fire_at_enemies() then
      		    next_state = self.fire
		elseif self:enemies_out_of_line_of_fire() then
      		    next_state = self.navigate
		end
		--must clear out the enemy watch list every frame or we get pile ups
		self.watch:clear()
	end
	--we sometimes pause before firing to simulate slower initial aiming
	self:auto_warmup()
	return go_to(self, next_state)
end

function av_soldier:fire()
	--enable all shooting resources and guns
	self:set_shoot_agents_enable(true)
	--expand los so enemies dont constantly jump in and out when hitstunned
	local pl, pw = self:expand_los()
	--only loop for as long as we have sight on targets 
	while self:enemies_in_current_direction() and yield() do
		--we have to refresh the enemy list to moniter what comes and goes
  		self.watch:clear()
 		self.watch:OnTouch()
 		--moniter our interval timer fire agent and shoot when the clock hits 0
    	        if self:animation_indicates_fire() then
		     self:shoot_in_current_direction()
		end
	end
	--retract the los back to normal state and close all fire resources 
	self:retract_los(pl, pw)
	self:close_fire_resources()
	return go_to(self, self.aim_at_enemy)
end
 
Sep 13, 2014 at 6:42 PM
Senior Member
"Wahoo! Upgrade!"
Join Date: Aug 11, 2014
Location:
Posts: 57
ah, thanks. I don't know what I did wrong there... I used the youtube tags and placed the link between. Is there some other way I have to do it?
 
Sep 13, 2014 at 8:40 PM
Senior Member
"Wahoo! Upgrade!"
Join Date: Aug 11, 2014
Location:
Posts: 57
OH, now I see. Fixed. Thanks for the help.
 
Top