AI Defence Systems

No idea. First thought is to determine the direction to the arrow

var directionToProjectile = (projectile.transform.position - transform.position).normalized;

Then pick a direction to dodge in

var directionToDodgeIn = Random.insideUnitCircle.normalized;

Then calculate the dot product and checking how close the result is to 1. If it’s within a threshold, try again

// threshold should be between -1 and 1. 0 to 1 is any forward direction, -1 to 0 is any backward direction
// 0 is exactly sideways. 0.5 is _not_ 45° that would be around 0.7. Math, amirite
Vector3 ChooseDodgeDirection(Vector3 projectilePosition, float threshold)
{
    var directionToProjectile = (projectilePosition - transform.position).normalized;
    var dot = 0f;
    var directionToDodgeIn = -directionToProjectile; // default to directly away
    do
    {
        var flatDirection = Random.insideUnitCircle;
        directionToDodgeIn = new Vector3(flatDirection.x, 0f, flatDirection.y).normalized;
        dot = Vector3.Dot(directionToProjectile, directionToDodgeIn);
    } while (dot >= threshold);
    return directionToDodgeIn;
}

No idea if that will work, but it’s the first thing that popped into my head.


Edits

  • Made a small adjustment above to determine the direction to dodge in
  • Fixed the ‘out of scope’ variable
  • Random direction is on (x,y). Fixed it to (x,z)
  • Removed the first fix again - I think it’s wrong

this all goes in ‘Projectile.cs’?

No, the projectile isn’t dodging

To be honest, I would change the calculation to a coroutine. If it fails to find a good dodge direction, it will wait a frame before trying again (I may even make it wait a little longer). That way, indecisiveness may cause it to take too long to dodge and get it hit anyway.

Another thing is that it might dodge away from the arrow, moving the arrow out of the detection trigger and then the arrow could enter it again causing a new dodge

All I wanted was a way to avoid dodging in the path that the arrow is coming from, without going nuts on who dodges where… :sweat_smile:

Right now, here’s how I handle dodging in ‘Projectile.OnTriggerEnter()’:

// The following if statement is for cases when the enemy needs to dodge a projectile:
            if (other.TryGetComponent<IncomingRangedAttackDetector>(out var detector))
            {
                if (detector.parentStateMachine.gameObject == instigator)
                {
                    Debug.Log($"This is my projectile, ignore");
                }
                else
                {
                    Debug.Log($"An outsider is trying to attack me, I should defend myself");

                    Vector3 randomDirection = Vector3.zero;
                    int randomRoll = Random.Range(0, 4);
                    switch (randomRoll)
                    {
                        case 0:
                            randomDirection = new Vector3(-1, 0, 0);
                            break;

                        case 1:
                            randomDirection = new Vector3(1, 0, 0);
                            break;

                        case 2:
                            randomDirection = new Vector3(0, -1, 0);
                            break;

                        case 3:
                            randomDirection = new Vector3(0, 1, 0);
                            break;
                    }
                    detector.parentStateMachine.SwitchState(new EnemyDodgingState(detector.parentStateMachine, randomDirection));
                }
                return;
            }

the whole thing is connected to a dodging blend tree that has 4 animations, depending on which direction you’re going for

My solution isn’t going to do that, really. You may just dodge into the arrow anyway, even if you dodge sideways. I’m only determining where I am in relation to the arrow. I have no idea where the arrow is going. If the arrow comes into my detection bubble, but it was going to miss me to the left anyway, I may dodge to the left and directly into the path of the arrow. To determine the direction the arrow is going you would have to interrogate the arrow to determine its flight direction, or you will have to observe multiple frames of the arrow to determine where it’s going

so… let’s just leave it as is then? I honestly think this looks like a class of physics waiting to happen, and god knows about the performance issues that may happen as a result. Maybe we (or just I) should focus our resources for the time being on getting the melee dodging and blocking to happen, because after that I will need to find a way to get enemies to wield shields as well, so they can also defend themselves

and after all of that, getting the enemies to work via their eyes instead of the circle they currently use will be important, otherwise this just won’t make much sense :slight_smile:

In the end, I’ll make this all probabilistic, and directly proportional to the enemy’s combat level, so less experienced enemies have a smaller chance of defending themselves

Actually, the arrow has a forward vector which tells you which direction it’s going, but it could get rough when you want to figure out where the arrow will end up (what with trajectory and stuff). My math is not good enough to tell you to ‘project this vector onto that one and then determine the intersection of the arrow direction and the chosen dodge direction’ blah-blah. No idea

Fair enough. Let’s wait for Brian and hear his say on this when he’s back. We can put this part aside for now

I mean come on, we got dodging arrows to work. This, to me at least, is a miracle in and of itself, and I am proud that we have achieved it :smiley:

WAIT… How about we get a little smart? If the instigator is in the forward direction of their victim (Victim is the one whose about to get hit), the victim can dodge sideways, and if not, he can do all 4 directions? That way, the chances of dodging the hit are higher when he sees you (because… well… he sees the arrow coming at him)

AND… Now I’m in a new, and probably unrelated problem. I never thought of equipping my NPCs with shields, so this problem was under the radar for a long time. So, I gave it a go in ‘RespawnManager.cs’:

            // If the enemy has a shield that's supposed to be in his hand, make him wear it:
            if (spawnedEnemy.GetComponent<Fighter>().GetCurrentShieldConfig() != null) 
            {
                ShieldConfig enemyShieldConfig = spawnedEnemy.GetComponent<Fighter>().GetCurrentShieldConfig();
                spawnedEnemy.GetComponent<Fighter>().AttachShield(enemyShieldConfig);
            }

But for some reason (again, I can’t identify where the logic error is from, yet), my enemy does not get the defence and percentage bonus he is supposed to get from holding the shield for some reason (I needed this to help get a reaction attack for any shield holder against any sword abuse). Where can I check to try and solve this?

Edit: Ahh nevermind, I just allowed the enemy to have a parameter for the shield config, and I was missing an if statement in ‘TryHit’ to compensate for that. Here’s what I ended up writing:

                        // the enemy
                        else 
                        {
                            if (GetCurrentShieldConfig() != null) 
                            {
                                Debug.Log($"{other.gameObject.name} is an enemy with a shield");
                                defence = otherBaseStats.GetStat(Stat.Defence) + (GetCurrentShieldConfig().GetDefenseBonus() * (1 + GetCurrentShieldConfig().GetPercentageBonus()/100));
                            }
                            else 
                            {
                                Debug.Log($"{other.gameObject.name} has no shield");
                                defence = otherBaseStats.GetStat(Stat.Defence);
                            }
                        }

Edit 2: This is becoming a serious problem… I don’t know what’s wrong here:

// player/enemy
                    if (other.TryGetComponent(out BaseStats otherBaseStats))
                    {
                        float defence;
                        // if its the player (the only in-game character with a skillStore)
                        if (other.TryGetComponent(out SkillStore skillStore))
                        {
                            defence = otherBaseStats.GetStatBySpecifiedLevel(Stat.Defence, skillStore.GetSkillLevel(Skill.Defence));
                        }
                        // the enemy
                        else 
                        {
                            if (GetCurrentShieldConfig() != null)
                            {
                                Debug.Log($"{other.gameObject.name} is an enemy with a shield");
                                defence = otherBaseStats.GetStat(Stat.Defence) + (GetCurrentShieldConfig().GetDefenseBonus() * (1 + GetCurrentShieldConfig().GetPercentageBonus()/100));
                            }
                            else
                            {
                                Debug.Log($"{other.gameObject.name} has no shield");
                                defence = otherBaseStats.GetStat(Stat.Defence);
                            }
                        }
                        damage /= 1 + defence/damage;
                        // Randomize the Damage:
                        // damage *= UnityEngine.Random.Range(0f, 1.25f);
                        // if (UnityEngine.Random.Range(0,100) > 95) damage *= 2.0f;
                    }

This is part of my ‘Fighter.OnTakenHit()’, and my problem here is, is that whether AN ENEMY has a shield or not, when my player punches him, DEPENDS ON WHETHER THE PLAYER HIMSELF IS WIELDING A SHIELD OR NOT

The logic is completely twisted here, and I desperately need help to identify what’s going on, and how to fix it… If it helps, here’s the entire ‘TryHit()’ function:

    // For the brand new Attack Array System (part of the Point-And-Click -> Third Person System implementation)
    // this function is called by animations to perform hits, based on the hand/foot/weapon, doing the hit:
    void TryHit(int slot)
    {
        // if no current attack (or follow up) exists, return null:
        if (currentAttack == null) return;

        // radius of the damage of the weapon:
        float damageRadius = 0.5f;

        // To trigger this animation, the event in the animation takes the 1 and 2 values in 'Int' slot
        Vector3 transformPoint;
        switch (slot) 
        {
            case 0: transformPoint = currentWeapon.value.DamagePoint;   // weapon damage
            damageRadius = currentWeapon.value.DamageRadius;
            break;
            case 1: transformPoint = rightHandTransform.position;   // for right-handed cases
            break;
            case 2: transformPoint = leftHandTransform.position;    // for left-handed cases
            break;
            // case 3: for right foot, but after you got the animation intact and the transform setup
            // case 4: for left foot. Again, after the animation is intact and the transform is setup
            default: transformPoint = rightHandTransform.position;  // for cases when things make no sense
            break;
        }
        Debug.Log($"Attacking with slot {slot}, position {transformPoint}");

        // This list ensures that the health component of the player is accessed once only. This problem
        // came up when I introduced a second Capsule Collider on my player, so he can mount animals using
        // Malbers' Scripts (basically, 2 colliders on the player = 2x damage dealt to him... 
        // we needed to get rid of that by ensuring the health component is accessed once, through this list):
        List<Health> alreadyHit = new List<Health>();

            foreach (Collider other in Physics.OverlapSphere(transformPoint, damageRadius))
            {
                if (other.gameObject == gameObject) continue;   // don't hit yourself

                if (other.TryGetComponent(out Health otherHealth) && !otherHealth.IsDead())
                {
                    // If one of the players' colliders (he has a Character Controller and a Capsule Collider
                    // on him) is hit, don't take damage again (fixes the 2 colliders-on-player-taking-damage problem):
                    if (alreadyHit.Contains(otherHealth)) continue;
                    alreadyHit.Add(otherHealth);
                    
                    float damage = GetDamage();
                    damage *= currentAttack.DamageModifier;

                    // player/enemy
                    if (other.TryGetComponent(out BaseStats otherBaseStats))
                    {
                        float defence;
                        // if its the player (the only in-game character with a skillStore)
                        if (other.TryGetComponent(out SkillStore skillStore))
                        {
                            defence = otherBaseStats.GetStatBySpecifiedLevel(Stat.Defence, skillStore.GetSkillLevel(Skill.Defence));
                        }
                        // the enemy
                        else 
                        {
                            if (GetCurrentShieldConfig() != null)
                            {
                                Debug.Log($"{other.gameObject.name} is an enemy with a shield");
                                defence = otherBaseStats.GetStat(Stat.Defence) + (GetCurrentShieldConfig().GetDefenseBonus() * (1 + GetCurrentShieldConfig().GetPercentageBonus()/100));
                            }
                            else
                            {
                                Debug.Log($"{other.gameObject.name} has no shield");
                                defence = otherBaseStats.GetStat(Stat.Defence);
                            }
                        }
                        damage /= 1 + defence/damage;
                        // Randomize the Damage:
                        // damage *= UnityEngine.Random.Range(0f, 1.25f);
                        // if (UnityEngine.Random.Range(0,100) > 95) damage *= 2.0f;
                    }

                    // if the player is invulnerable, ignore trying to damage him, and continue to the next cycle of damaging enemies in the radius, which was hurt by the sword:
                    if (other.CompareTag("Player") && other.GetComponent<Health>().GetInvulnerable())
                    {
                        continue; // later on, integrate logic here to accept a percentage of damage, before continuing to the next Foreach loop
                    }

                    // (TEMP - GetComponent<Health>().IsDead()) Temporarily here, to ensure ghost enemies deal no damage and mess with the NPC hunting for enemies system:
                    if (otherHealth.IsDead() || GetComponent<Health>().IsDead()) return;
                    otherHealth.TakeDamage(gameObject, damage, currentWeaponConfig.GetSkill());
                    TryApplyHitForce(other, transform.position);
                }
            }
        }

Edit 3: that was another dumb mistake from my side that lead to an NRE… Fixed it :slight_smile:

Ahh… this shield thing set me up to the trap of “I found a mathematical glitch in my armour defence point counter, because I’m not always getting the promised points of defence”. @Brian_Trotter whenever possible, please let me know where did we calculate the armour defence points again? It would be nice to just directly calculate everything in ‘Fighter.TryHit()’ tbh, or at least investigate where the mathematical problem is coming from, because tbh, I am a little lost there (in other words, the mathematics isn’t adding up for some reason for the armour)

If it helps, check the ‘TryHit’ function above, please.

You may also find this important, I’m not sure… it’s from my ‘BaseStats.cs’ script:

        public float GetStatBySpecifiedLevel(Stat stat, int level) {
            return progression.GetStat(stat, characterClass, level) + (GetAdditiveModifiers(stat) * (1 + GetPercentageModifiers(stat)/100));
        }

and here’s the entire script, if needed:

using UnityEngine;
using System;
using GameDevTV.Utils;
using RPG.Skills;

namespace RPG.Stats

{
    public class BaseStats : MonoBehaviour, IPredicateEvaluator
    {
        
        [Range(1, 99)]
        [SerializeField] int startingLevel;
        [SerializeField] CharacterClass characterClass;
        [SerializeField] Progression progression = null;
        [SerializeField] GameObject levelUpParticleEffect = null;
        [SerializeField] bool shouldUseModifiers = false;   // to disable enemies from using damage boosters (modifiers) - can be useful if you want bosses to boost themselves for example...

        LazyValue<int> currentLevel;

        // Out of Course Content:
        // LazyValue<int> currentNPCLevel;

        // Experience experience;
        SkillExperience skillExperience;

        public event Action onLevelUp;  // event, to be subscribed to in 'health.cs' (so we can regenerate partially (or all of) our health when we level up)

        private void Awake() {

            // experience = GetComponent<Experience>();
            skillExperience = GetComponent<SkillExperience>();
            currentLevel = new LazyValue<int>(CalculateLevel);

            // Out of Course Content:
            // currentNPCLevel = new LazyValue<int>(CalculateNPCLevel);

        }

        public void Start() {

            currentLevel.ForceInit();
            // Out of Course Content:
            // currentNPCLevel.ForceInit();

        }

        public float GetStatBySpecifiedLevel(Stat stat, int level) {
            return progression.GetStat(stat, characterClass, level) + (GetAdditiveModifiers(stat) * (1 + GetPercentageModifiers(stat)/100));
        }

        private void OnEnable() {
            // if (experience != null)
            if (skillExperience != null)
            {
                // experience.onExperienceGained += UpdateLevel;   // Subscribing to a delegate (event), just like we did in 'CinematicControlRemover.cs'
                skillExperience.skillExperienceUpdated += UpdateLevel;
            }
        }

        private void OnDisable() {

            // if (experience != null)
            if (skillExperience != null)
            {
                // experience.onExperienceGained -= UpdateLevel;   // Unsubscribing to a delegate (event), just like we did in 'CinematicControlRemover.cs'
                skillExperience.skillExperienceUpdated -= UpdateLevel;
            }

        }

        private void UpdateLevel()
        {
        
            int newLevel = CalculateLevel();
            // int newLevel = GetComponent<SkillStore>().GetCombatLevel() + 1;
            Debug.Log($"Current Level = {currentLevel.value}, new level = {newLevel}");
            if (newLevel > currentLevel.value) {

                Debug.LogError("LEVEL UP!");
                currentLevel.value = newLevel;
                LevelUpEffect();
                onLevelUp();    // Action call (i.e: no function), with Health.RegenerateHealth() as a subscriber
                                // (this ensures that we regenerate partially (or all of) our health when we level up, by placing it in the place where it occurs when we level up!)

            }
        
        }

        public void LevelUpEffect() {
            Instantiate(levelUpParticleEffect, transform);
        }

        public float GetStat(Stat stat)
        {
            // The weapon damage is added via 'GetAdditiveModifiers'
            return  (GetBaseStat(stat) + GetAdditiveModifiers(stat)) * (1 + GetPercentageModifiers(stat)/100);
        }
        
        private float GetBaseStat(Stat stat) {
            return progression.GetStat(stat, characterClass, GetLevel());
        }

        public int GetLevel() {
            return currentLevel.value;
        }


        /// <summary>
        /// Calculating the Player/Enemy level, based on experience
        /// </summary>
        /// <returns></returns>
        /* private int CalculateLevel()
        {
        
            Experience experience = GetComponent<Experience>();
        
            if (experience == null) return startingLevel;

            float currentXP = experience.GetPoints();
            int penultimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);
        
            for (int level = 1; level <= penultimateLevel; level++)
            {
        
                float XPToLevelUp = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);
                if (XPToLevelUp > currentXP)
                {
        
                    return level;
        
                }
        
            }

            return penultimateLevel + 1;
        
        } */

        /// <summary>
        /// Calculating the Player level, based on SkillStore (and if the script holder, like the enemies, don't have a SkillStore, set them to their assigned starting levels)
        /// </summary>
        private int CalculateLevel() {
            return TryGetComponent(out SkillStore skillStore) ? skillStore.GetCombatLevel() + 1 : startingLevel;
        }

        private float GetAdditiveModifiers(Stat stat) {

            // avoids enemies from using modifiers
            if (!shouldUseModifiers) return 0;

            float total = 0;

            foreach (IModifierProvider provider in GetComponents<IModifierProvider>()) {

                foreach (float modifier in provider.GetAdditiveModifiers(stat)) {

                    total += modifier;

                }

            }

            return total;

        }

        private float GetPercentageModifiers(Stat stat)
        {

            // avoids enemies from using modifiers
            if (!shouldUseModifiers) return 0;

            float total = 0;

            foreach (IModifierProvider provider in GetComponents<IModifierProvider>())
            {

                foreach (float modifier in provider.GetPercentageModifiers(stat))
                {

                    total += modifier;

                }

            }

            return total;

        }

        // Enum-based Evaluate of 'IPredicate' function:
        public bool? Evaluate(EPredicate predicate, string[] parameters) {

            // This function checks if we have a specific combat level, so we can execute specific action based on that

            if (predicate == EPredicate.HasLevel) {

                // TryParse() TRIES to convert a value from a string to an integer:
                if (int.TryParse(parameters[0], out int testLevel)) {

                    // if you successfully converted the string to an integer, and the player level is higher than the required level, then
                    // the player level > required 'HasLevel' returns a true boolean:
                    return currentLevel.value >= testLevel;

                }

            }

            return null;

        }

    }

}

My main problem here is, not a single piece of armor delivers 100% on what it promised. They all either get close or do nothing at all… Hence why I’m so confused

From research, I know it’s not from ‘Progression.cs’. It’s something to do with the armour modifiers…

I don’t have all the context, sorry, but I think a part of the problem you encounter is inherent in your damage formula:

I tried to reproduce it in a spreadsheet:

As you see, whenever defense is equal to original damage, you still get half the original damage out. I don’t know how you wanted the value ranges to scale, but you could also try it out on paper or spreadsheet first.

1 Like

ahh, so it’s the formula… It’s the basic formula from the RPG Courses, and I honestly just might end up searching for a better one if that’s the case. Maybe something a little more linear? I don’t know :sweat_smile:

I honestly thought I was lacking behind programming-wise until you showed up. Thank you :slight_smile:

And I just noticed something that was driving me nuts:

        public float GetStatBySpecifiedLevel(Stat stat, int level) {
            return progression.GetStat(stat, characterClass, level) + (GetAdditiveModifiers(stat) * (1 + GetPercentageModifiers(stat)/100));
        }

This is the formula that I’m currently using to add ALL the modifiers and calculate whatever needs to be calculated out of them, to reduce the damage accordingly.

This formula essentially says if the additive modifier is zero, the percentage modifier is going to end up giving me a zero as well. It was giving me a huge headache as to why my gloves, that only have a percentage modifier, were not giving me any damage reduction

1 Like

Ok, inventory is still ahead of me (and my buddy Godot), maybe the formula was great for the values used in the course. It all depends on what numbers you want in your game. But I think it’s the same as with progression values: Spreadsheets like Rick’s are your friend, rather than testing it all out in your running game.

I’ll give it a test run in the morning then, whatever I can dream up at night. For now I’m best off to get some rest because it’s almost 1 AM here

For tonight, I’m just glad I fixed my morning bug, and then got my enemies to be able to avoid each other’s (yup, they’re fighting against one another) arrows :laughing:

1 Like

I am slowly starting to see the long term drawbacks of my approach… I will do my best to change it and make it centralized to the player instead of keeping it on the projectile, and then we can move on to the melee detector counterpart, and I’ll work on modifying my code to fix that

[Future Edit: For the time being, let’s just stick to getting the melee detector to work first]

But… What’s the best way to do this?

And the melee variant is a nightmare to work on, because I have no idea how to get the parent of the weapon (similar to Ranged, I’m trying to get the trigger detection through the weapon itself, unless I get some guidance on how to get the trigger itself to act instead)… I mean Ranged was no different but I was already almost done with that before it even started anyway :sweat_smile:

I’m going crazy trying to figure out how in the world do you get the parent of the sword holder, so we know how to classify who enters the trigger detector

This is what I’m currently trying to do, in ‘Weapon.cs’:

        private void OnTriggerEnter(Collider other) 
        {
            if (other.TryGetComponent<IncomingMeleeAttackDetector>(out var detector)) 
            {
                if (detector.parentStateMachine.gameObject == this.GetComponentInParent<Fighter>()) 
                {
                    Debug.Log($"Self-hit, ignore");
                }
            }
        }
    }

and my ‘IncomingMeleeAttackDetector’ (a new box collider I introduced to trigger the dodging of melee attacks) for some reason is acting like a physical collider block against ranged hits… (Edit: I turned off the interactions between the melee detector and projectiles to get rid of this problem, but I still can’t think of a way to get the enemy to play any sort of defence mechanisms yet… The debugger above didn’t work)

After all these hours, I came to the realization that I needed a sphere collider trigger on my sword… :sweat_smile:

Edit 2: Ahh, those box collider sizings are frustrating tbh…

SOOOOOOOOOOOOOOOOOOOO… This whole ‘Melee Defence’ system became such a headache for me, with all the box colliders and what not, and the “Oh it might not hit, oh it might not, Oh I might need a coroutine here, Oh I need a box collider” blah blah blah blah blah was becoming such a huge headache for me, I got forced to actually think for myself once again… And then I recall what @Brian_Trotter once taught me about Animation Event Relays, and all of a sudden it hit me like a truck!

Why don’t we, instead of making all those colliders, the boxes and the weapon colliders and what not, and just mess around with probabilities of hit-or-miss (AND BOY OH BOY WILL THE COMPLAINTS BE TERRIBLE ABOUT THESE BUGS!), JUST AVOID ALL OF THIS MAJOR HEADACHE, And use the ‘Animation Event Relay’ Script, from the RPG to Third Person Merge?

This way, all we have to do is call a function at a brief moment of an animation, from ‘somewhere’ (I still don’t know where that ‘somewhere’ is, yet… but finding that doesn’t sound as bad as the alternative approach), and essentially get the enemies to play their defence mechanic to defend themselves at a crucial moment of the weapon’s animation, depending on the weapon itself? So… now the melee defence relies on the animation rather than the big headache of colliders that I have to deal with otherwise

It’ll probably be placed in ‘Fighter.cs’. It seems like a good position to get the data of the opponent’s ‘LastAttacker’ creation of ours (in ‘EnemyStateMachine.cs’, this is the variable that determines who the opponent of each NPC actually is), which I will need (because it’s who the data ends up following), and it exists on all NPCs in the game, making it a hotspot for calling this animation event from. I’m not sure yet, this is just a wild guess

I threw in a debugger in a test function, and it worked… Genius idea, if I were to say so myself :stuck_out_tongue_winking_eye:

AND… I got the basic Enemy Dodge Melee Attacks to work through the Animation Event System, and it works really really well for what it’s supposed to do

Now I just gotta create other NPC defence mechanics, like… idk, Parrying arrows or hits for example? That’ll be a ton of fun to play with, xD (probably something for tomorrow. Find a way to reflect hits, so that the instigator is the one who gets the damage instead of the victim, be it for ranged or melee attacks)

ONE LAST QUESTION THOUGH… Can we introduce with an ‘Attack’, in ‘Attack.cs’, a ‘direction’ variable, and use that to hard-code the direction of which the enemies dodge the player’s hits, in ‘Fighter.cs’? Something for me to control the dodging system a bit better

OK so… Whilst setting up my ‘melee dodging state’, I ran into an extremely interesting problem, and this one took me a few hours to figure out. Here’s what happened:

[Problem]
Because all enemies use the same animator, and my entire dodging system relies on Animation Events, enemies that are nearby, in an invisible ‘Physics.OverlapSphere’ circle (with a small radius, so nearby fights are unaffected by these two fighting it out), and about to strike one another, they both assume they’re about to get hit, and essentially end up in a ‘dodge dance’, which means they’re constantly dodging each other, to the point that none of them actually damages the other

[My Solution - I’ll let Brian confirm it for me whenever he’s feeling better]:

After countless failed attempts, I ended up creating a ‘static class’, with a single static variable. I named my class ‘DodgeManager’ (I can’t think of a better name), with a single static variable called ‘HasDodged’), as follows:

public static class DodgeManager
{
    // This script contains a static variable, called 'HasDodged', which is accessible from any script
    // (and is frozen until changed, as it's static), with the sole purpose of making sure that if this
    // variable is set to true, no other NPC can do a dodge in the targeter's range (i.e: eliminate the problem
    // of 2 NPCs dodging each other out of assumptions):
    public static bool HasDodged = false;
}

This script is static, so it doesn’t necessarily have to be attached to any gameObject.

This is a simple boolean that gets activated and deactivated, but it’s static because I want it to be read universally through all scripts, because I activated it in ‘Fighter.OpponentDefenceSetup()’, the function where I do all the dodging in ‘Fighter.cs’ (the function is independent of the script, but it’s a convenient spot to get a lot of useful information).

Anyway, the static variable is checked for at the start of the ‘OpponentDefenceSetup()’ (i.e: if it’s true, the function doesn’t run, because someone in this fight is already dodging, eliminating the issue I have of two dodgers not fighting it out), and if true, the function doesn’t even start.

If false, though, it gets set to true after the dodging state is entered, and is then falsified when the dodging state is complete, in ‘EnemyDodgingState.Exit()’

Hence, solving the entire problem

It was a very interesting introduction for me to static variables, or at least a good memory jog, that I thought it would be interesting to bring it up.

Tomorrow I’ll move on to Parrying and Shield Defence States. I don’t know what to expect, but I hope it’s fun and doable. For now, I’ll just run multiple tests to ensure that all works as expected

Privacy & Terms