🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

How enemy AI decide to dodge or block from player's melee attack?

Started by
13 comments, last by gelu23 2 years, 7 months ago

I'm wondering how enemy AI decide to dodge, block or other defensive move from players melee attack in game.

I just guessed simply that AI detects players attack movement and choose defensive movement.

How does enemy AI choose that movement?

I also wonder how to adjust AI's dodge or block skillfulness. What is general method for this in common game?

Advertisement

shins94 said:

I just guessed simply that AI detects players attack movement and choose defensive movement.

That seems like a pretty good guess, although the “movement” part could likely just be checking if the player is currently attacking - it might be an attack event subscribed to by the AI, which then triggers a block. Add a delay depending on AI skill, of course.

SuperVGA said:

That seems like a pretty good guess, although the “movement” part could likely just be checking if the player is currently attacking - it might be an attack event subscribed to by the AI, which then triggers a block. Add a delay depending on AI skill, of course.

I also thought about addition delay to AI. But in that case, a skilled AI can block or dodge every attack of player. Is it a common way to implement?

shins94 said:

Is it a common way to implement?

I don't know - I've actually never implemented something like that. Perhaps someone who has done a lot of it could chip in here.

you can implement it using Behaviour Trees (bt);

it's like what scenegraph is to geometry organisation, behaviour trees (and family) are to ai;

bt is a hierarchy node structure where leaves are where you define your actions: “melees”, “attack”, etc… and anything above the leaves could be anything that either defines a behaviour selector (decision nodes) or a behaviour sequence (a pre-defined set of actions);

now that's the general drill on bt, so if u have a class node in your code, u are 1 step good to go ? ;

BTs can be coded in various flavours:

  • event-driven BTs (this allows your code to jump/switch nodes based on something that has happened in the game and thus switch logic instead of reusing the last logic repeatedly)
  • blackboarded BTs, these are BTs that have a node to which the most frequent or recent behaviours are stored, this way the code doesn't have to search the whole tree over and over to repeat some necessary required and current actions

you can of course mix BTs and FSMs (state machine), or if your game has simple requirements of AI then just use one or the other;

quick note: Deep Learning (Machine Learning) technique is also another topic beginning to take prominence in this area but BTs, FSM should still be around… thank u mum ?;

watch this :

have fun ?

@ddlox Thanks your reply. My question was not detail. If I use BT for dodge and block action, Behaviour selector should select one of them. I wonder how the selector choose that action programmatically. I think certain formula is necessary to decide those action from Blackboard information.

And this formula also makes AI sometimes not choose dodge or block actions to get hit from player attack depending on AI's skillfulness. I've searched about how other games implemented this. It is not clear.

Ah ok, well u should have said that you're already using bts… say sorry ?

ok, it goes like this (I'm going to explain this as though you're using Unreal Engine but don't know how it's coded in there), however with this explanation you can translate this knowledge to your own engine (if this is what you're doing):

  • the pseudo code that you're about to see is HEAVILY (and I mean HEAVILY) simplified, but it is modelled around how Unreal Engine 4 works (may Tim Sweeney et al forgive me for my mistakes -lol-)
  • the point is, u have tasks (i call them actions here to match yr OP), the blackboard, the ai controller, a selector node, a sequence node, and root, like in UE
  • (note: UE also has AI perception, stimuli, basically things that “emit” certain properties that can be tracked OR used as sources to allow a selector to make informed decisions) ← I'm not talking about these here ok
  • in the pseudo code below, the blackboard is shared by all these things so that all these things can "see" what's on the blackboard, right!
  • if your character charlie is in a melee and is dodging , i update the blackboard as well with 'dodging', then anything that sees it on the blackboard will know that charlie is dodging
  • the key thing about selector is that they run through each of its children and checks if a child is interested in the current state of an item on the blackboard.
// in Unreal Engine, grandma Selector says this: 
"Selector Nodes execute their children from left to right. They stop executing when one of their children succeeds. If a Selector's child succeeds, the Selector succeeds. If all the Selector's children fail, the Selector fails."

so the selector's job is to query its children (sequence of children) about certain states, in other words, the selector asks every child whether it wants to eat porridge or not! if one child says no, then grandma asks the next child, and so on and if there is one who says yes, then that child is fed, the search stops and the ones after this child don't get fed! Some family this is ! ?

if no child matches the queried state then the selector would move on to the next sequence.

so let's see this in pseudo code, NOTE: I haven't tested it, i'm writing from the top of my head, but it shows how selector works, u will need to tidy up and improve hugely; if u put this in a game u'll be a millionaire ?

// pseudo


enum action
{
 dodge,
 block
};

// the blackboard that all can see
class bb
{ 
 action a;
}

// the node of the bt
class node
{
 bb* b;
 vector<node*> children
 action a;
 
 node(bb* _b){ b = _b; }
 add_child(node* child){ children.add(child) }
}

// fwd decl for sequence (which are eseentially the type of children a selector has)
class selector;

// a selector
class selector: public node
{ 
 bool select_child()
 {
   foreach (child in children)
   {
   		assert(typeof(child) == sequence)
     	if (child->a == b->a)
       	{
       	  // run action a for this child if it matches the state of blackboard b
       	  child->do_action(  )
       	  return true;
       	} 
   }
   return false;
 }
}

class sequence: public node
{
// i leave this as an exercise for u to do :)
}

// the behaviour tree 
class bt : class node
{
 node* root; 
 
 // bt creates root and adds a selector to the root
 bt(bb* b)
 {
 	root = create_root(b);
 	
 	selector* sel = create_selector(b);
 	root->add_child(sel); 
 }
 
 node* create_root(bb* b){ return new node(b) }
 node* create_selector(bb* b) { return new selector(b) }
 
 // here is where bt tells a selector to do its job of selecting which sequence to use
 bool check( node* n )
 {
 	...
    
    if (typeof(n) == selector)
 		if (n->select_child( ))
 			return true;
 	...	
 	// recursively run through the entire btree (there are ways to avoid entire traversal, left it to u)
 	for (child in n->children)
 		return check(child)
 		
 	return false;
}

// the ai controller who has it all, knows it all
// it is through this class object that state/actions can be set 
class ai_controller
{
 bt* my_bt;
 bb* my_bb;
 
 ai_controller()
 {
 // create the blackboard
  my_bb = create_bb()
  // create the bt and tell it about the bb so that all the tree knows about it
  my_bt = create_bt(my_bb)
 }
 
 bb* create_bb() { return new bb() }
 bt* create_bt(bb* b) { return new bt(b) }
 
 // create some sequences which will store wanted actions/tasks
 void set_up_sequence()
 {
    selector* sel = my_bt->root->get_child();
    
    sequence* dodger = new node(action::dodge);
 	sel->add_child(dodger);
 	sequence* blocker = new node(action::blocker)
 	sel->add_child(blocker);
 	...
 }
 
}

// here comes the hot stepper
class character
{
 ai_controller aic;
}

void main()
{
	character charlie;
...
  in game loop()
  {
  	in game update( )
 	{
 		 ...
 		 // at this point the game logic finds that charlie needs to dodge, so:
 		 // set action "dodge" to blackboard through charlier's ai controller 
 		charlie.aic.my_bb->a = action::dodge; 
 		george.aic.my_bb->a = action::run; 
 		... etc...
 		charlie and george are not sharing their bb or bt...
 		
 		// update charlie's AI behaviour 
 		charlie.aic.my_bt->check( charlie.aic.my_bt->root );
 		// update george's AI behaviour 
 		george.aic.my_bt->check( george.aic.my_bt->root );
 	}
 	
 	in game render ( )
 	{
 	... renders updated behaviours...
 	}
 	
 	.. in the next game logic maybe charlie is chasing the wind... :)
 }
 
}

there u go, that's kinda the gist of it all…

so if u got it, then it should get u going;

there's room for improvements, etc.. et.e…c;

that's it … all the best ?

@ddlox Thank you for your detail explanation again.

But I'm curious about

// at this point the game logic finds that charlie needs to dodge,:

How does game logic find that the AI agent needs to dodge?

Of course, the game logic gets information from blackboard and decides agent's action.

Which information of blackboard is used to find that the agent now needs to dodge by the game logic?

And the game logic should consider the agent skillfulness for dodge action I think.

Sorry for my iterative question.

i was hoping u were going to tell me ?, remember this ?:

class sequence: public node
{
// i leave this as an exercise for u to do :)
}

and this ?

 void set_up_sequence()
 {
    selector* sel = my_bt->root->get_child();
    
    sequence* dodger = new node(action::dodge);
 	sel->add_child(dodger);
 	sequence* blocker = new node(action::blocker)
 	sel->add_child(blocker);
 	...
 }

that logic:

shins94 said:
How does game logic find that the AI agent needs to dodge?

it's in the sequence class that u can code this intelligence;

now if u see:

  • i created 2 sequence objects and then added them as children to the selector;
    if u do it this way, it means that your dodger sequence (and the blocker sequence) will each more likely contain more intelligence than the simple fact of just dodging (and blocking respectively); that means the character needs more intelligence to distinguish when to dodge or when to block;
  • u could do another way by creating 1 sequence object and adding those 2 actions/tasks (blocking and dodging) to this sequence object; if u do it this way, it means that this 1 sequence object is enough for this character's intelligence to tell the difference between blocking and dodging; (I will show u pseudo for this here below)

shins94 said:
Which information of blackboard is used to find that the agent now needs to dodge by the game logic?

you could create an enum variable such as “enemyNeedToDodgeOrBlock” on the blackb; then in the enemy's sequence class, u create a function testIfDodgeOrBlockNeeded( ) that can update this variable based on this enemy's abilities and current situation (player is attacking or not: this player's action could also go to its own bboard), so in pseudo, it may be coded like this:

// pseudo
// so add to enemy's BB
enum action enemyNeedToDodgeOrBlock

// then in sequence
class sequen...
{
public:
 float enemySkillLevel = 0 to 10 (for example from stupid to genius)
 
 void testIfDodgeOrBlockNeeded()
 {
    auto player = getPlayer()
 	action pa = player->getBB()->myCurrentAction;
 	if (pa == action::attacking)
 	{
 		if (enemySkillLevel <= 4)
 			getBB()->enemyNeedToDodgeOrBlock= action::block
 		else
 			getBB()->enemyNeedToDodgeOrBlock= action::dodge
 	}
 	else
 	{
 	...
 	}
 }
}  

Now remember last time i warned u that many things were simplified, it is the same here, i have simplified this so that u can understand;

(in practice, the player can advertise his/her action (as an event) and one of the enemy's AI perception properties can capture this advertised event and act accordingly. In this abovementionned pseudo, i'm pretending that the enemy can get player's info through getPlayer( ) for simplicity);

then the next thing to do, in the same sequence class, is to run through each child and check which child needs to respond to enemyNeedToDodgeOrBlock update, again pseudo wise:

 class sequen...
{
	// remember: "u could do another way by creating 1 sequence object and adding those 2 actions"
	// so make sure u have added those 2 actions to be children of this class
	// i leave it as an exercise for u to complete :)
	
	 bool runActionsOrTasks()
 	{
 		...
    
	
 			// get enemys bb 
 			bb* b = getBB()
 			for (child in getChildren())
 				if (child->action == b->enemyNeedToDodgeOrBlock)
 					return child->playAnimation(b->enemyNeedToDodgeOrBlock)
  	
 		
...
 		return false;
	}
}

again, for clarity i have simplified it, but the idea is that the sequence class checks each of its children to see which one matches the BB's variable condition, so if this sequence had 2 children (1 child to block and the 2nd one to dodge) and if bb→enemyNeedToDodgeOrBlock is set to dodge then 2nd child takes action;

notice that the sequence is in the character's AI controller, so this sequence knows what character it belongs to and so the character will animate accordingly;

ok that's it now… u have enough to get going ?

i hope this helps;

all the best… have fun ?

@shins94 It is interesting to see that someone have raised this question. I'm in the process of developing similar AI behavior for my game project and was thinking of a unique way to implement this. To keep it simple I was thinking of using some sort of a random function to switch between different behaviors (dodging, attacking, blocking).

This topic is closed to new replies.

Advertisement