I have now completed initial testing for Blackboards (http://en.wikipedia.org/wiki/Blackboard_system), Behaviour Trees (http://en.wikipedia.org/wiki/Behavior_Trees_(Artificial_Intelligence,_Robotics_and_Control)), Decision Trees (http://en.wikipedia.org/wiki/Decision_tree) and Finite State Machines (http://en.wikipedia.org/wiki/Finite-state_machine) in Skyline
The link below points to a folder on my OneDrive account that contains the generic scripts for each of these AI types:
http://1drv.ms/1JPcXlP
The Main Script I used for testing is this:
- Code: Select all
sky.include "decisiontree.lua";
sky.include "decisionbranch.lua";
sky.include "evaluators.lua";
sky.include "blackboard.lua";
sky.include "actions.lua";
sky.include "behaviortree.lua";
sky.include "behaviortreenode.lua";
sky.include "finitestatemachine.lua";
sky.include "finitestate.lua";
sky.include "finitestatetransition.lua";
myLogicType = 0;
function onInit ( objID )
-- Set up the Blackboard
gBlackboard = { };
bb_Initialise ( );
bb_Set ( 1, "enemyid", 2 );
bb_Set ( 1, "health", 100 );
bb_Set ( 1, "maxhealth", 100 );
bb_Set ( 1, "ammo", 33 );
bb_Set ( 1, "maxammo", 100 );
bb_Set ( 1, "target", 2 );
bb_Set ( 1, "height", 1.7 );
bb_Set ( 1, "heightstand", 1.7 );
bb_Set ( 1, "heightcrouch", 1.0 );
bb_Set ( 1, "ismoving", false );
bb_Set ( 1, "isfalling", false );
bb_Set ( 1, "maxspeed", 10.0 );
bb_Set ( 1, "maxspeedstand", 10.0 );
bb_Set ( 1, "maxspeedcrouch", 5.0 );
-- TODO: The next line is Skyline specific
-- bb_Set ( 1, "velocity", newType.vec3 ( 0.0, 0.0, 0.0 ) );
bb_Set ( 1, "animstatecurrent", "idle" );
bb_Set ( 1, "animstatenew", "" );
-- TODO: The next line is Skyline specific
--bb_Set ( 1, "bestfleeposition", newType.vec3 ( 0.0, 0.0, 0.0 ) );
bb_Set ( 1, "moveendtime", 0 );
bb_Set ( 1, "team", 1 );
bb_Set ( 2, "health", 50 );
bb_Set ( 2, "team", 2 );
objID = 1;
enemy = 2;
myActions = act_actions ( objID );
--myLogic = decisionTree ( objID );
--myLogicType = 0;
--myLogic = behaviourTree ( objID );
--myLogicType = 1;
myLogic = finiteStateMachine ( objID );
myLogicType = 2;
animStateNew = "";
end
-- |------------------------------------------------------------------------------
-- | Update: called every frame
-- |------------------------------------------------------------------------------
function onUpdate ( dt )
if ( myLogicType == 0 ) then
myLogic:DT_Update ( dt );
elseif ( myLogicType == 1 ) then
myLogic:BT_Update ( dt );
elseif ( myLogicType == 2 ) then
myLogic:FSM_Update ( dt );
end
tmpBool, animStateNew = bb_Get ( objID, "animationstatenew" );
local tmpBool1, tmpState = bb_Get ( objID, "animationstatecurrent" );
if ( tmpState ~= animStateNew ) then
-- TODO: The next line is Skyline specific
-- anim.playAnimation ( objID, animStateNew, 30, 1);
bb_Set ( objID, "animationstatecurrent", animStateNew );
end
end
function decisionTree ( objID )
-- *******************
-- DECISION TREE START
-- *******************
-- Set up the Decision Tree
local tree = dtNew ( );
local isAliveBranch = dbNew ( );
local criticalBranch = dbNew ( );
local moveFleeBranch = dbNew ( );
local enemyBranch = dbNew ( );
local ammoBranch = dbNew ( );
local shootBranch = dbNew ( );
local moveRandomBranch = dbNew ( );
local randomBranch = dbNew ( );
isAliveBranch:DB_AddChild ( criticalBranch );
isAliveBranch:DB_AddChild ( myActions.ACT_DieAction ( objID ) );
isAliveBranch:DB_SetEvaluator ( function ( )
if Evaluators_IsNotAlive ( objID ) then
return 2;
end
return 1;
end
);
criticalBranch:DB_AddChild ( moveFleeBranch );
criticalBranch:DB_AddChild ( enemyBranch );
criticalBranch:DB_SetEvaluator ( function ( )
if Evaluators_HasCriticalHealth ( objID ) then
return 1;
end
return 2;
end
);
moveFleeBranch:DB_AddChild ( myActions.ACT_MoveAction ( objID ) );
moveFleeBranch:DB_AddChild ( myActions.ACT_FleeAction ( objID ) );
moveFleeBranch:DB_SetEvaluator ( function ( )
if Evaluators_HasMovePosition ( objID ) then
return 1;
end
return 2;
end
);
enemyBranch:DB_AddChild ( ammoBranch );
enemyBranch:DB_AddChild ( moveRandomBranch );
enemyBranch:DB_SetEvaluator ( function ( )
if Evaluators_HasEnemy ( objID ) then
return 1;
end
return 2;
end
);
ammoBranch:DB_AddChild ( shootBranch );
ammoBranch:DB_AddChild ( myActions.ACT_ReloadAction ( objID ) );
ammoBranch:DB_SetEvaluator ( function ( )
if Evaluators_HasAmmo ( objID ) then
return 1;
end
return 2;
end
);
shootBranch:DB_AddChild ( myActions.ACT_ShootAction ( objID ) );
shootBranch:DB_AddChild ( myActions.ACT_PursueAction ( objID ) );
shootBranch:DB_SetEvaluator ( function ( )
if Evaluators_CanShootAgent ( objID ) then
return 1;
end
return 2;
end
);
moveRandomBranch:DB_AddChild ( myActions.ACT_MoveAction ( objID ) );
moveRandomBranch:DB_AddChild ( randomBranch );
moveRandomBranch:DB_SetEvaluator ( function ( )
if Evaluators_HasMovePosition ( objID ) then
return 1;
end
return 2;
end
);
randomBranch:DB_AddChild ( myActions.ACT_RandomMoveAction ( objID ) );
randomBranch:DB_AddChild ( myActions.ACT_IdleAction ( objID ) );
randomBranch:DB_SetEvaluator ( function ( )
if Evaluators_Random ( objID ) then
return 1;
end
return 2;
end
);
tree:DT_SetBranch ( isAliveBranch );
return tree;
-- *******************
-- DECISION TREE END
-- *******************
end
function behaviourTree ( objID )
-- *******************
-- BEHAVIOR TREE START
-- *******************
local tree = btNew ( objID );
local node;
local child;
node = tree.BT_CreateSelector ( );
tree.bt_node_ = node;
-- die action
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "is not alive", Evaluators_IsNotAlive );
node:BTN_AddChild ( child );
node = child;
node = node:BTN_GetParent ( );
child = tree.BT_CreateAction ( "die", myActions.ACT_DieAction ( objID ) );
node:BTN_AddChild ( child );
node = child;
-- flee action
node = node:BTN_GetParent ( );
node = node:BTN_GetParent ( );
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "has critical health", Evaluators_HasCriticalHealth);
node:BTN_AddChild ( child );
child = tree.BT_CreateAction ( "flee", myActions.ACT_FleeAction ( objID ) );
node:BTN_AddChild ( child );
-- reload/shoot/move/pursue actions
node = node:BTN_GetParent ( );
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "has enemy", Evaluators_HasEnemy );
node:BTN_AddChild ( child );
child = tree.BT_CreateSelector ( );
node:BTN_AddChild ( child );
node = child;
-- reload action
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "has no ammo", Evaluators_HasNoAmmo );
node:BTN_AddChild ( child );
child = tree.BT_CreateAction ( "reload", myActions.ACT_ReloadAction ( objID ) );
node:BTN_AddChild ( child );
-- shoot action
node = node:BTN_GetParent ( );
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "can shoot enemy", Evaluators_CanShootAgent );
node:BTN_AddChild ( child );
child = tree.BT_CreateAction ( "shoot", myActions.ACT_ShootAction ( objID ) );
node:BTN_AddChild ( child );
-- pursue action
node = node:BTN_GetParent ( );
child = tree.BT_CreateAction ( "pursue", myActions.ACT_PursueAction ( objID ) );
node:BTN_AddChild ( child );
-- move action
node = node:BTN_GetParent ( );
node = node:BTN_GetParent ( );
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "has move position", Evaluators_HasMovePosition );
node:BTN_AddChild ( child );
child = tree.BT_CreateAction ( "move to position", myActions.ACT_MoveAction ( objID ) );
node:BTN_AddChild ( child );
-- random action
node = node:BTN_GetParent ( );
child = tree.BT_CreateSequence ( );
node:BTN_AddChild ( child );
node = child;
child = tree.BT_CreateCondition ( "50/50 chance", Evaluators_Random );
node:BTN_AddChild ( child );
child = tree.BT_CreateAction ( "random move", myActions.ACT_RandomMoveAction ( objID ) );
node:BTN_AddChild ( child );
-- idle action
node = node:BTN_GetParent ( );
child = tree.BT_CreateAction ( "idle", myActions.ACT_IdleAction ( objID ) );
node:BTN_AddChild ( child );
node = child;
return tree;
-- *******************
-- BEHAVIOR TREE END
-- *******************
end
function finiteStateMachine ( objID )
-- *******************
-- FSM START
-- *******************
local fsm = fsmNew ( objID );
fsm:FSM_AddState ( "die", myActions.ACT_DieAction ( objID ) );
fsm:FSM_AddState ( "flee", myActions.ACT_FleeAction ( objID ) );
fsm:FSM_AddState ( "idle", myActions.ACT_IdleAction ( objID ) );
fsm:FSM_AddState ( "move", myActions.ACT_MoveAction ( objID ) );
fsm:FSM_AddState ( "pursue", myActions.ACT_PursueAction ( objID ) );
fsm:FSM_AddState ( "randomMove", myActions.ACT_RandomMoveAction ( objID ) );
fsm:FSM_AddState ( "reload", myActions.ACT_ReloadAction ( objID ) );
fsm:FSM_AddState ( "shoot", myActions.ACT_ShootAction ( objID ) );
-- idle action
fsm:FSM_AddTransition ( "idle", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "idle", "flee", Evaluators_HasCriticalHealth );
fsm:FSM_AddTransition ( "idle", "reload", Evaluators_HasNoAmmo );
fsm:FSM_AddTransition ( "idle", "shoot", Evaluators_CanShootAgent );
fsm:FSM_AddTransition ( "idle", "pursue", Evaluators_HasEnemy );
fsm:FSM_AddTransition ( "idle", "randomMove", Evaluators_Random );
fsm:FSM_AddTransition ( "idle", "idle", Evaluators_True );
-- move action
fsm:FSM_AddTransition ( "move", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "move", "flee", Evaluators_HasCriticalHealth );
fsm:FSM_AddTransition ( "move", "reload", Evaluators_HasNoAmmo );
fsm:FSM_AddTransition ( "move", "shoot", Evaluators_CanShootAgent );
fsm:FSM_AddTransition ( "move", "pursue", Evaluators_HasEnemy );
fsm:FSM_AddTransition ( "move", "move", Evaluators_HasMovePosition );
fsm:FSM_AddTransition ( "move", "randomMove", Evaluators_Random );
fsm:FSM_AddTransition ( "move", "idle", Evaluators_True );
-- random move action
fsm:FSM_AddTransition ( "randomMove", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "randomMove", "move", Evaluators_True );
-- shoot action
fsm:FSM_AddTransition ( "shoot", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "shoot", "flee", Evaluators_HasCriticalHealth );
fsm:FSM_AddTransition ( "shoot", "reload", Evaluators_HasNoAmmo );
fsm:FSM_AddTransition ( "shoot", "shoot", Evaluators_CanShootAgent );
fsm:FSM_AddTransition ( "shoot", "pursue", Evaluators_HasEnemy );
fsm:FSM_AddTransition ( "shoot", "randomMove", Evaluators_Random );
fsm:FSM_AddTransition ( "shoot", "idle", Evaluators_True );
-- flee action
fsm:FSM_AddTransition ( "flee", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "flee", "move", Evaluators_True );
-- die action
-- pursue action
fsm:FSM_AddTransition ( "pursue", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "pursue", "flee", Evaluators_HasCriticalHealth );
fsm:FSM_AddTransition ( "pursue", "shoot", Evaluators_CanShootAgent );
fsm:FSM_AddTransition ( "pursue", "idle", Evaluators_True );
-- reload action
fsm:FSM_AddTransition ( "reload", "die", Evaluators_IsNotAlive );
fsm:FSM_AddTransition ( "reload", "shoot", Evaluators_CanShootAgent );
fsm:FSM_AddTransition ( "reload", "pursue", Evaluators_HasEnemy );
fsm:FSM_AddTransition ( "reload", "randomMove", Evaluators_Random );
fsm:FSM_AddTransition ( "reload", "idle", Evaluators_True );
fsm:FSM_SetState ( "idle" );
return fsm;
-- *******************
-- FSM END
-- *******************
end
function globalFn ( callID, tTmp )
local tmpStr = tTmp.tStr;
local tmpTeam = tTmp.team;
local tmpPos = tTmp.position;
local tmpEnemyID = tTmp.enemy;
local tmpBool, tmpT = bb_Get ( objID, "team" );
if ( tmpTeam == tmpT ) then
if ( tmpStr == "RetreatPosition" ) then
-- TODO: Do stuff here based on the incoming message
elseif ( tmpStr == "PositionUpdate" ) then
-- TODO: Do stuff here based on the incoming message
elseif ( tmpStr == "EnemySelection" ) then
-- TODO: Do stuff here based on the incoming message
end
else
-- TODO: Non "Team" messages get handled here
end
end
The Main Script should be fairly easy to understand, but feel free to give me a shout if you don't understand something (there are also a couple of other things I was trying still in the code such as the Messaging).
I apologise in advance for the fact that not everything is commented as well as it should be
Finally, most of the code is taken from a fantastic book called "Learning Game AI Programming with Lua" by David Young. I have, however, spent many hours extracting the bits I needed and changing the code so that it worked in Skyline.
The original code is issued under the following licence:
Copyright (c) 2013 David Young dayoung@goliathdesigns.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
I have contacted David and told him what I was doing, and he didn't seem to have any issues, BUT, as with all these things, if you do provide any form of source code please include the above.
Shando