Sep 13, 2014 at 5:25 AM
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:
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