Showing posts with label traits. Show all posts
Showing posts with label traits. Show all posts

Saturday, August 10, 2013

Programming Aggressiveness, Building Personality Traits Into Game AI (Part 2)

Howdy! Welcome to Part 2 of Building Personality Traits Into Game AI. Part 1 can be found here. The personality trait that I will be covering in this section is aggressiveness. Similar to the bravery trait, I'm looking for a function that will return a value of either true or false. This returned value will indicate whether or not a unit is aggressive enough to attack an enemy.

Drawing Board: Idea #1 - Skewed Randoms

Originally I had thought of using skewed random chance to determine if a unit was aggressive enough to attack--meaning that when an enemy entered a unit's sight, a number between 1 and 100 would be randomly generated and then be compared to the unit's aggressiveness trait (also a number between 1 and 100). If the number was less than the unit's aggressiveness trait, then the unit would be aggressive enough to attack the enemy.

The problem with this method is that the AI behavior is a little too random--too inconsistent. The player would have trouble figuring out how exactly the aggressiveness trait affected a unit's behavior. So I decided to do some research.

Drawing Board: Idea #2 - Interpersonal Space Multiplier

The most applicable (and interesting) thing that I found while researching was the concept of Proxemics, pioneered by Edward T. Hall.

Take a look at the following diagram regarding interpersonal space:


Edward T. Hall's personal reaction bubbles, showing radius in feet and meters.

This is what Wikipedia has to say about interpersonal space:

Interpersonal space is the psychological "bubble" that exists when one person stands too close to another. Research has revealed that there are four different zones of interpersonal space:
  • Intimate distance ranges from touching to about 18 inches (46 cm) apart, and is reserved for lovers, children, close family members, friends, and pet animals.
  • Personal distance begins about an arm's length away; starting around 18 inches (46 cm) from the person and ending about 4 feet (122 cm) away. This space is used in conversations with friends, to chat with associates, and in group discussions.
  • Social distance ranges from 4 to 8 feet (1.2 m - 2.4 m) away from the person and is reserved for strangers, newly formed groups, and new acquaintances.
  • Public distance includes anything more than 8 feet (2.4 m) away, and is used for speeches, lectures, and theater. Public distance is essentially that range reserved for larger audiences.
To summarize:
  • Intimate space <= 1.5 ft
  • Personal Space <= 4 ft
  • Social Space <= 8 ft
  • Public Space <= 25 ft

How is this useful?


How can I relate the concept of interpersonal space to my game? My AI units have a limited sight range (each unit can only see a certain distance around itself) that essentially creates a sight "bubble" around each unit. See where I'm going with this? What if I utilized the different levels of interpersonal space to help determine whether or not a unit is aggressive enough to attack an enemy? Taking the most literal translation of the diagram, I made each section of interpersonal space correlate to a certain distance from the unit. Each section would be relative to a certain percentage of a unit's sight range.

If I were to equate the outer radius (25ft) of the diagram to the outer radius of a unit's sight bubble, I would get the following ratios:
  • Intimate space:
    1.5 ft / 25 ft -> 0.06
    At or closer than 6% of a unit's sight range
     
  • Personal Space:
    4 ft / 25 ft -> 0.16
    At or closer than 16% of a unit's sight range
     
  • Social Space:
    8 ft / 25 ft -> 0.32
    At or closer than 32% of a unit's sight range
     
  • Public Space:
    25 ft / 25 ft -> 1
    At or closer than 100% of a unit's sight range
     
Currently I'm using a distance of 100 for a unit's initial sight range so:
  • Intimate Space <= 6
  • Personal Space <= 16
  • Social Space <= 32
  • Public Space <= 100
For reference, each unit's body has an initial radius of 6. This causes an issue. This means that intimate space would be inside of a unit's body! Considering that we will not have a fixed body size (units can grow) or sight range, this could get difficult to manage. I'm going to scrap this idea for now. Yes, there is a workaround for this issue, but I've already done a considerable amount of work beforehand in determining the best way (for me) to evaluate aggressiveness, and I've found a better, less complicated solution. However, this is still a valuable concept so I'm going to finish explaining my train of thought.

The idea was to add a multiplier (dependent on which bubble an enemy was located in) to how likely a unit was to be aggressive. I tried to translate each bubble into a value based on the premise of how someone might react towards a stranger within that bubble.

Here are my interpretations of aggressiveness in each bubble:
  • Intimate Space:
    The hug zone. This space starts at about elbow's length away. This is a space where someone would be very uncomfortable if a stranger were to get within. This space represents an area where physical damage can easily be inflicted and an attack would be hard to dodge.
    Multiplier: 2
     
  • Personal Space:
    This is about arm's length away. This space represents an area where physical damage can easily be inflicted, but an attack would be fairly easy to dodge.
    Multiplier: 1.5
     
  • Social Space:
    This is an acceptable space for strangers to be located in, but this space also implies that there is likely some form of interaction between everyone within. This is the space where someone might start to get defensive.
    Multiplier: 1.25
     
  • Public Space:
    This is an acceptable space for strangers to be in and does not imply any sort of direct interaction.
    There is no multiplier for this space.
     
The final piece of this concept involves a comparison. Presuming we could generate a value, between 1 and 100, based on proximity, we would take that value and multiply it by the interpersonal space multiplier. If the resulting value was greater than or equal to the aggressiveness trait (also a value between 1 and 100), then the unit would be considered aggressive enough to attack an enemy and the function would return true. Let's continue on to the next idea.

Drawing Board: Idea #3 - Territory Multiplier

Animals have been known to be more aggressive in areas that they consider to be their territory. This concept is fairly simple. What if I used a multiplier (dependent on where, in regards to the unit's territory, the unit and enemy are located) in determining whether or not a unit is aggressive enough to attack?

Scenarios and interpretations translated into multipliers:
  • The unit is in the unit's territory while the enemy is not:
    This could be equated to being in your house, or on your lawn, and watching someone ride a bike down the middle of the street. This might prompt a unit to be slightly more aggressive because it is in its own territory, but not too aggressive because the enemy is not in the unit's territory.
    Multiplier: 1.25
     
  • Both the unit and the enemy are in the unit's territory: This could be equated to having a stranger enter your property while you are on the premises. You aren't too concerned about the stranger because you can keep your eye on them and can potentially stop any form of property damage/theft they might intend to do. However, there is still a stranger on your property so you will be more aggressive than normal.
    Multiplier: 1.5
     
  • The enemy is in the unit's territory while the unit is not:
    This could be equated to being at your neighbor's house and watching someone walk up to your down. There is an unattended stranger on your property. If the stranger were to attempt any property damage or theft, it would be very difficult for you to stop them. You might be very aggressive in this situation.
    Multiplier: 2
     
  • Neither the unit nor the enemy is in the unit's territory:
    This would be like seeing a stranger in public.
    There is no multiplier for this scenario.
     
Final Implemented Idea:

I wanted something inherently simple for implementation so I settled with a form of proximity based aggressiveness. The aggressiveness trait would correlate with a certain percentage of a unit's sight range. So a unit with a value of 50 for aggressiveness would be aggressive enough to attack any enemies that are at or closer than 50% of the unit's sight range. This idea utilizes a linear progression between the aggressiveness trait value and how aggressive a unit actually is. However, this idea still retains dynamism due to varying sight ranges and aggressiveness trait values. If it wasn't apparent, this idea is a modified version of the Interpersonal Space Multiplier.

Here is the function:
float percentOfSightEnemyIsAt = Vector2.Distance(Position, enemy.Position) / Sight; 

if (percentOfSightEnemyIsAt <= Personality.Aggressiveness / 100f) 
{ 
 return (true); 
} 
else 
{ 
 return (false); 
}

This function is designed so that an aggressiveness trait value of 100 will always result in a unit being aggressive enough to attack an enemy within the unit's sight range. While this is an elegant solution to defining aggressiveness, this function seems to be a little too simple. The aggressiveness behavior would greatly benefit from some additional logic. I'm going to try and work the Territory Multiplier concept into this function.

Here is the final function:
float territoryMultiplier = 1; 

if (isUnitInTerritory) 
{ 
 territoryMultiplier = 2f; 

 if (isEnemyInTerritory) 
 { 
  territoryMultiplier = 3f; 
 } 
} 

float percentOfSightEnemyIsAt = Vector2.Distance(Position, enemy.Position) / Sight; 

if (percentOfSightEnemyIsAt <= (territoryMultiplier * Personality.Aggressiveness) / 100f) 
{ 
 return (true); 
}
else 
{ 
 return (false); 
}

As you can see, the territory multiplier increases the range at which a unit will be aggressive. This leads to some interesting behavior.

Here's a video demoing this behavior:


That's all I have for today! I know that there are other ways this could have been done, so feel free to share any of your thoughts!

Sunday, August 4, 2013

Programming Bravery, Building Personality Traits Into Game AI (Part 1)

Hello there ladies and gentlemen!  Today I am going to explain how to program bravery into AI!

Take a look at this function I wrote:
        public Boolean BraveEnoughToAttack(AI_SingleUnit enemy)
        {

            //how many attacks it would take to kill the enemy
            float howManyAttacksToKill = (float)Math.Ceiling((float)enemy.Health / DamageDealing.MeleeDamage);

            //how long would it take to kill the enemy
            TimeSpan timeTillKill = TimeSpan.FromSeconds(howManyAttacksToKill * (1f / Attacking.AttackSpeed));


            //how many attacks from the enemy it would take to kill me
            float howManyAttacksTillDeath = (float)Math.Ceiling((float)Health / enemy.DamageDealing.MeleeDamage);

            //how long would it take for the enemy to kill me
            TimeSpan timeTillDeath = TimeSpan.FromSeconds(howManyAttacksTillDeath * (1f / enemy.Attacking.AttackSpeed));

            if (timeTillKill < timeTillDeath)
            {
                //should win
                return (true);
            }
            else //should lose or tie
            {
                //How many of me would it take to kill them?

                //given this equation:
                //newTimeToKill = timeTillKill * 1/numberOfMe
                //newTimeTillDeath = timeTillDeath * numberOfMe

                //newTimeToKill = newTimeTillDeath
                //timeTillKill * 1/numberOfMe = timeTillDeath * numberOfMe
                //timeTillKill = timeTillDeath * numberOfMe^2
                //timeTillKill / timeTillDeath = numberOfMe^2
                //sqrt(timeTillKill/timeTillDeath) = numberOfMe

                //because timeTillKill > timeTillDeath, numberOfMe will always be greater than sqrt(1)
                float numberOfMeItWouldTakeToKill = (float)Math.Sqrt(timeTillKill.TotalSeconds/timeTillDeath.TotalSeconds);

                if (1 - (1f / numberOfMeItWouldTakeToKill) <= Personality.Bravery / 100f)
                {
                    return (true);
                }
                else
                {
                    return (false);
                }
            }
        }

So let's get explaining!  The purpose of this function is to determine whether or not a unit is brave enough to consider attacking an enemy.  A unit is either brave enough or it isn't, so the function returns either true or false.  True being brave enough; false being not brave enough.

Let's explain some variables!

howManyAttacksToKill
//how many attacks it would take to kill the enemy
float howManyAttacksToKill = (float)Math.Ceiling((float)enemy.Health / DamageDealing.MeleeDamage);

This is the number of attacks it would take the unit to kill the enemy. This is calculated by dividing the amount of health the enemy has by the amount of damage the unit can do in one hit. We round the value up because, when it comes to hitting, there are no halfsies. A unit either hits the enemy or it doesn't.

timeTillKill
//how long would it take to kill the enemy
TimeSpan timeTillKill = TimeSpan.FromSeconds(howManyAttacksToKill * (1f / Attacking.AttackSpeed));

This is how long it would take the unit to kill the enemy.  This is calculated by multiplying the number of attacks it would take the unit to kill the enemy by the attack speed of the unit.

howManyAttacksTillDeath
//how many attacks from the enemy it would take to kill me
float howManyAttacksTillDeath = (float)Math.Ceiling((float)Health / enemy.DamageDealing.MeleeDamage);

This is the number of attacks it would take the enemy to kill the unit. This is calculated by dividing the amount of health the unit has by the amount of damage the enemy can do in one hit. Once again, we round the value up because we don't hit like little sissies.

timeTillDeath
 //how long would it take for the enemy to kill me
TimeSpan timeTillDeath = TimeSpan.FromSeconds(howManyAttacksTillDeath * (1f / enemy.Attacking.AttackSpeed));

This is how long it would take the enemy to kill the unit.  This is calculated by multiplying the number of attacks it would take the enemy to kill the unit by the attack speed of the enemy.

Let's look at the first conditional statement:
if (timeTillKill < timeTillDeath)
{
     //should win
     return (true);
}

In this case, the unit has calculated that it will be able to kill the enemy faster than the enemy can kill the unit, so we say that the unit is brave enough to attack the enemy.

In order to really explain this, I think I need to define "bravery" first.
Google says that bravery is "courageous behavior or character." 
So what exactly does "courageous" mean then? 
Google says that courageous means "not deterred by danger or pain." 
Those are some pretty "human" definitions. They need to be translated so that they are more relevant to my AI. My AI is not programmed to sense pain, so let's talk about danger. To the unit, a dangerous enemy would be an enemy that, given a fair fight, would kill the unit if it were to engage in combat. As long as our unit thinks it will be able to kill the enemy, then it's not really being "brave" because it doesn't think there is any danger. So, given a supposedly guaranteed victory, the unit is brave enough to engage because it doesn't even have to consider being brave. To the unit, there is nothing to worry about. We return true.

Let's look at the next part of the conditional statement:
else //should lose or tie
{
     //How many of me would it take to kill them?

     //given this equation:
     //newTimeToKill = timeTillKill * 1/numberOfMe
     //newTimeTillDeath = timeTillDeath * numberOfMe

     //newTimeToKill = newTimeTillDeath
     //timeTillKill * 1/numberOfMe = timeTillDeath * numberOfMe
     //timeTillKill = timeTillDeath * numberOfMe^2
     //timeTillKill / timeTillDeath = numberOfMe^2
     //sqrt(timeTillKill/timeTillDeath) = numberOfMe

     //because timeTillKill > timeTillDeath, numberOfMe will always be greater than sqrt(1)
     float numberOfMeItWouldTakeToKill = (float)Math.Sqrt(timeTillKill.TotalSeconds/timeTillDeath.TotalSeconds);

     if (1 - (1f / numberOfMeItWouldTakeToKill) <= Personality.Bravery / 100f)
     {
          return (true);
     }
     else
     {
          return (false);
     }
}

In this case, the unit has calculated that the enemy would kill the unit faster than the unit would kill the enemy (a losing situation) or that they would both kill each other at the same time (a neutral situation).  This is where I really need to define "bravery".  I need to make bravery measurable.  So how do I do that?  I think it's fair to say that, in a fight with a stronger opponent, the stronger the opponent is, the more brave it would be to engage in combat.  So it would be more brave to fight someone who is three times stronger than you, rather than someone who is two times stronger than you.  I need to quantify how much stronger the enemy is in comparison to the unit.  Let me break it down for you.

How many units would it take to kill the enemy faster than the enemy could kill the units?

This problem can be solved with either an equation or through trial and error.  With the amount of times this code would be executing in my game, trial and error is simply not an option.  I need to come up with an equation.

Since the conditional compares how long it would take the unit to kill the enemy versus how long it would take the enemy to kill the unit, I am going to keep the comparison between kill times.

To start, I've already defined when the unit will kill the enemy faster than the enemy will kill the unit: when timeTillKill is greater than timeTillDeath.  I need to figure out when the new timeTillKill will be greater than the new timeTillDeath. For the sake of simplicity, it's okay to just figure out when the new timeTillKill will equal the new timeTillDeath because that is where the threshold of victory lies.

So, the equation can be defined as: 
newTimeTillKill = newTimeTillDeath

Now I need to define newTimeTillKill and newTimeTillDeath.  This is what I came up with:

newTimeTillKill = oldTimeTillKill / numberOfMe
newTimeTillDeath = oldTimeTillDeath * numberOfMe

I realize that these definitions are not perfect and are only an approximation, but these are the only definitions I could come up with that returned a solvable equation.  If you can think of a more accurate solution, please let me know.  On a side note, we do not need a perfectly accurate definition because bravery is a relatively subjective matter.

Solving for numberOfMe:
newTimeTillKill = newTimeTillDeath

oldTimeTillKill / numberOfMe = oldTimeTillDeath * numberOfMe

oldTimeTillKill = oldTimeTillDeath * numberOfMe2

oldTimeTillKill / oldTimeTillDeath  = numberOfMe2

sqrt(oldTimeTillKill / oldTimeTillDeath) = numberOfMe

Final equation:
numberOfMe = sqrt(timeTillKill / timeTillDeath)

So, now that we have an idea of how much stronger the enemy is, we need to evaluate whether or not a unit is brave enough to consider attacking the enemy.  In my game, DATABITS, the bravery attribute is a number between 1 and 100 and could be considered a percentage.  50 would mean 50% bravery.  Because I want the bravery attribute to account for enemies that are between (inclusive) 1 and (exclusive) infinity times stronger, I need to have some kind of parabolic relationship in my evaluation of bravery.  This is where the next conditional statement comes in.

if (1 - (1f / numberOfMeItWouldTakeToKillEnemy) <= Personality.Bravery / 100f)
{
     return (true);
}
else
{
     return (false);
}

By taking the inverse of numberOfMeItWouldTakeToKillEnemy, we're returning how strong the unit is in comparison to the enemy.  So if it takes three units to kill the enemy, the unit can be considered to be as strong as 33.3% of the enemy.  This is a nice number to have, but because a higher bravery attribute means that a unit is more brave, we need to subtract the calculated value from 100% when comparing the value to a unit's bravery attribute.  This way, it takes 77.7% bravery to fight an enemy that is three times stronger than the unit.  Because we know that this code will only be reached in neutral or losing conditions, we know that the variable numberOfMeItWouldTakeToKillEnemy will always be greater than or equal to one.

That's all I have for today!  I know that there are various other ways this could be done, so feel free to share any of your thoughts!

...Part 2...