• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

I can't beat my own simple ai?

Hello everyone,

i'm programming an simple ai for a dragon ball fangame i'm doing,he is very similar to another dbz fangame called DBZ Budokai X(actually,my game is a fangame of this fangame,double fangame),the problem is,it's impossible to land a hit in my ai,since he has perfect attack timing,in the exact frame that he can attack me,he does and i can't attack him since he attacks first,here's the code that i use for the AI:

switch(state)
{
case state_stand:
check_state()
break;

case state_hurt:
//nothing,these codes are at Alarm[2],scr_gethit e check_step
break;

case state_attack:
var attack;
attack=choose(1,2)
if attack=1 and can_atk=true
{
can_atk=false
frame=irandom_range(3,5)
alarm[1]=25
var attack;
attack=instance_create(x,y,obj_colission)
attack.team=team
attack.image_xscale=image_xscale
}
if attack=2 and can_atk=true
{
can_atk=false
frame=irandom_range(6,8)
alarm[1]=25
var attack;
attack=instance_create(x,y,obj_colission)
attack.team=team
attack.image_xscale=image_xscale
}
check_state()
break;

case state_flying:
//didn't make it yet
break;

case state_following:
move_towards_point(near.x,near.y,max_speed)
if distance_to_object(near)<25
{
check_state()
}
break;

case state_charging:
//didn't make that too
break;

case state_special:
//well... that too
break;

case state_ranged:
if can_atk=true
{
can_atk=false
frame=irandom_range(12,13)
alarm[1]=15
var ki;
ki=instance_create(x,y,obj_kiblast)
ki.team=team
if chara=spr_vegeta{ki.color=c_purple}
if chara=spr_goku{ki.color=c_blue}
if instance_exists(near)
{
ki.direction=point_direction(x,y,near.x,near.y)
}
else
{
ki.direction=0
}
ki.speed=12
}
check_state()
break;
}
and this is the script that changes the state,it's very simple:
if instance_exists(near)
{

if distance_to_object(near)<25
{
state=state_attack
}
if distance_to_object(near)>25
{
var chance;
chance=choose(1,1,1,1,1,3)
if chance=1{state=state_following}
if chance=2{state=state_charging}
if chance=3{state=state_ranged}
}
}

the problem is as i already said,it's impossible to land a hit on the bot since he instantly attack the player when he is in range,the only way to defend is by dodging since i didn't make a blocking state yet,can someone say me a way to make it more natural instead of having "perfect reflexes" and attacking as soon as it's possible
 

JeffJ

Member
Funny timing, I'm actually working on an enemy right now uses some of this behavior. In my example, the player can throw a spell at the enemy, and the enemy can attempt to dodge. I actually have 3 different possible outcomes whenever a spell is thrown at the enemy;

1: The enemy doesn't react at all and just gets hit
2: The enemy does react, but he's too late, so he attempts to dodge it by jumping over it, but he gets hit anyway (in his feet)
3: The enemy reacts in time and actually jumps over the spell

This makes him seem a lot more "human" in that flawed way.

I'll tell you how I do this - you can apply the same theory.

The enemy has a step event that is always detecting whether or not a spell (bullet/projectile/whatever) is present, and does some collision checking far enough ahead. Then, I do a simple choose(1,2,3)

Followed by a switch:

switch(chooseresult)
{
case 1:
{
//Don't move - get hit by the spell
break;
}
case 2:
{
//Try to jump, but make it happen too late based on how far away the spell is
break;
}
case 3:
{
//Do the flawless dodging you're doing right now
break;
}
}

Hope that helps!
 
Thanks! it was a bit diferent since he was attacking me instead of defending/dodging like yours,but the theory was the same,i just made a "choose" that has 50% chance of attacking and 50% chance of waiting 1 milissecond(even though its just 1 milissecond it's suficient to the player to attack the bot) and i added a 4-frame delay between changing states so he don't do it instantaneously,now he is still fast but not suficient to be impossible to beat
 

JeffJ

Member
There you go - glad I could help!

Simple things like chance based actions and intentional delays here and there will go a long way in creating a perfectly flawed AI.

Have fun!
 

Yal

šŸ§ *penguin noises*
GMC Elder
It's WAY easier to make an AI that's impossible to beat than one that's hard but still fair, this is a pretty well-known (but also pretty oftenly forgotten or ignored, sadly) truth in AI design. Most developers make perfect AI first, then introduce stuff that makes the AI intentionally do bad moves occasionally. It's not perfect (the AI can have very erratic behavior if it keeps goofing several times in a row and then doing stuff perfectly several times in a row) but it's probably the easiest, most general way to do stuff. Another things you could consider:

Have the AI decide on what to do in advance, then wait a little while before doing the stuff. This is how humans do unless they're playing on pure reflex. This basically gives the AI reaction time, so it always tries to do the right thing but it can be too late, e.g. if the player starts an attack at optimal range the AI can get hit before it can react but if the player is more far away the AI blocks in time.
If the player is doing TOO well against the AI, you can reduce the reaction time a bit: this mimics a human player starting to expect the player doing the same moves again and defending before they actually get attacked because "they saw them coming".
Also, a general gameplay thing that could be worth keeping in mind: make sure that an action never only has one proper response, that reduces depth in the gameplay. A very common thing in fighting games is to have both blocks and dodges work for all moves, but with different benefits and drawbacks to each: dodges are harder to time and removes all damage but changes your positioning, blocks are easier to time but lets through a little bit of damage and often locks you into defensive position for a bit so you can't counter, and high/low moves can break through your guard... that kind of stuff.
Anyway, the point with THAT rant is that if there's more than one possible move the AI can counter with for every situation, you should have them pick randomly to be less predictable. If you want the AI to be adaptive, it should keep track of whether the action was successful or not (e.g. if the player guardbroke them when they tried guarding instead of dodging) and then start using the action that was successful the most times.
 

Tsa05

Member
Once upon a years and years ago I posted a Gaussian random script for gml. I recommend looking into such a thing!

Gaussian random numbers are a type of random where you can specify the average and the deviation. So, you can compute perfect timing, and then pop that into a Gauss random function, and you'll get a value that is not quite exactly accurate. The amount of innacuracy is defined by the deviation value.

So, if perfect timing is 5 frames, you can say "give me a random number averaging around 5, with a deviation of 1." The majority of the time (I don't recall exactly--85%ish?), the Gauss random function would return a number between 4 and 6. Many of those would round to 5. Pretty accurate, right? You could use a smaller deviation to miss less often, or a larger deviation to niss more often. The average chosen value will be 5, but the computer will be wrong more often if it's got a wider range to choose between.

Numbers outside the deviation are possible, but far, far less likely. And numbers more than 2 times the deviation are practically impossible--but you should still clamp() the results just in case.
 
Top