Fire Spray Issue

Hi all. So, I have been coding with the Shops and Abilities Course, but for some reason my Fire Spray Ability Stopped working completely. Yes, I can buy it from my in-game shop into my inventory, but the ability itself does not respond to my Key inputs. Where can I start fixing this from?

I have to assume that the Fire Spray itself has been dragged to the Action Bar?

Are there any errors in the console when you press the key to activate the Fire Spray?

Are other abilities working correctly?

Hi Brian. Yes, the Fire Spray was dragged into the Action Bar, no errors were being given, and the other abilities are working flawlessly :slight_smile: - this one specifically is not working for some reason

So what we’ll need to see is:

  • The Inspector for your Fire Spray Ability, showing which strategies are being used.
  • The Inspectors for each strategy being used (the scriptable objects themselves)
  • The code for each strategy being employed.

Since the other abilities are working correctly, I think we can rule out input issues or problems with the Ability.cs itself.

Hi Brian, here are the required screenshots:

Fire Spray Ability Inspector:

The Directional Targeting Strategy:

Directional Targeting

IsAliveFilter:

IsAliveFilter

And the Fireballs:

Fireballs SPE

And here are the Scripts:

Directional Targeting:

using System;
using UnityEngine;
using RPG.Control;

namespace RPG.Abilities.Targeting
{

    [CreateAssetMenu(fileName = "Directional Targeting", menuName = "Abilities/Targeting/Directional", order = 0)]

    public class DirectionalTargeting : TargetingStrategy
    {

        [SerializeField] LayerMask layerMask;
        [SerializeField] float groundOffset;    // the (vertical) minimum distance the ability must be above the ground



        public override void StartTargeting(AbilityData data, Action finished)
        {

            // We are inheriting (therefore 'overriding') from an Abstract Class (for now) 
            // named 'TargetingStrategy' hence we need to implement the function it contains too!

            // When an ability is fired, we want it to point at our Target (Enemy), using Raycasts, as shown below:
            RaycastHit raycastHit;
            Ray ray = PlayerController.GetMouseRay();
            if (Physics.Raycast(ray, out raycastHit, 1000, layerMask)) {

                // Requires a bit of mathematical trignometry (Shops and Abilities Course, Lecture 57)
                data.SetTargetedPoint(raycastHit.point + ray.direction * groundOffset / ray.direction.y);

            }

            // Debug.Log("Demo Targeting Started");
            finished();     // when we are done with our ability, we want to call 'finished' 
                            // (so that we can play our game like normal again)

        }
    }
}

Spawn Projectile Effect:

using System;
using RPG.Attributes;
using RPG.Combat;   // to access projectiles
using UnityEngine;

namespace RPG.Abilities.Effects {

[CreateAssetMenu(fileName = "Spawn Projectile", menuName = "Abilities/Effects/SpawnProjectileEffect", order = 0)]

public class SpawnProjectileEffect : EffectStrategy {

    [SerializeField] Projectile projectileToSpawn;  // projectile, to be spawned (ability)
    [SerializeField] float damage;  // damage of the projectile
    [SerializeField] bool isRightHand = true;   // checks if the ability will be launched from the players' right hand or left hand (based on what we are launching)
    [SerializeField] bool useTargetPoint = true;    // the starting point of our projectile is the instigator (player/enemy)

    public override void StartEffect(AbilityData data, Action finished) {

        // This function plays the effect of our ability, by first getting the fighters in our Area of Effect,
        // Instantiating a Projectile, and then aiming that projectile at our Targets

        Fighter fighter = data.GetUser().GetComponent<Fighter>();
        Vector3 spawnPosition = fighter.GetHandTransform(isRightHand).position;

        // Spawning a hurting projectile, for each target in our array of targets (if we are spawning from our instigator position):
        if (useTargetPoint) {
        
            SpawnProjectileForTargetPoint(data, spawnPosition);

        }

        else {

            SpawnProjectilesForTargets(data, spawnPosition);

        }

        finished();

    }

        private void SpawnProjectileForTargetPoint(AbilityData data, Vector3 spawnPosition)
        {

            // For an individual target:

            Projectile projectile = Instantiate(projectileToSpawn);
            projectile.transform.position = spawnPosition;
            projectile.SetTarget(data.GetTargetedPoint(), data.GetUser(), damage);

        }

        public void SpawnProjectilesForTargets(AbilityData data, Vector3 spawnPosition) {

            // For multiple targets our ability is aiming at:

            foreach (var target in data.GetTargets())
            {

                Health health = target.GetComponent<Health>();

                if (health)
                {

                    Projectile projectile = Instantiate(projectileToSpawn);
                    projectile.transform.position = spawnPosition;
                    projectile.SetTarget(health, data.GetUser(), damage);

                }

            }

    }


}

}

Do we need anything else? If so, let me know and I’ll paste the script here

What’s sticking out to me is that you have an IsAlive filter active. We don’t use a filter for the firespray in the course because we’re just firing at a point in space, not at a specific character. Try removing that filter so that there are no filters.

After that, if we’re still not firing, let’s add some Debugs
In DirectionalTargeting.StartTargeting within the if statement after setting the targeted point

Debug.Log($"Target point set to {data.GetTargetedPoint()}");
}
else Debug.Log($"No point was detected, not firing ray");

and in SpawnProjectileEffecst.StartEffect

Debug.Log($"SpawnProjectile: Starting effect shooting at {data.GetTargetedPoint()");

Hi Brian. Apologies for the very late response, as I was celebrating Eid with my family :slight_smile:

Regarding the debugging executed above, I managed to get a Target Point, however the SpawnProjectileEffects Script gave no debugging response. What might be the cause here?

I think I may have an idea now… let’s see your Ability.cs. I suspect you’re not firing effects if the list is empty…

Sure, here you go:

using GameDevTV.Inventories;
using RPG.Attributes;
using RPG.Core;
using System.Linq;
using UnityEngine;


namespace RPG.Abilities {

    [CreateAssetMenu(fileName = "Ability", menuName = "Abilities/Ability", order = 0)]

    public class Ability : ActionItem
    {

        [SerializeField] TargetingStrategy targetingStrategy;
        [SerializeField] FilterStrategy[] filterStrategies;
        [SerializeField] EffectStrategy[] effectStrategies;
        [SerializeField] float cooldownTime;    // the duration of cooling down our Ability, before its available for reuse to our user (Player)
        [SerializeField] float manaCost = 0;    // how much Mana is used to operate an Ability
        [SerializeField] bool noTargetRequired = false; // checks if our ability requires targets or not (if it does, specific abilities are not played)

        public override void Use(GameObject user, System.Action consumeCallback) {

            // 0. Check for Mana
            Mana mana = user.GetComponent<Mana>();

            if (mana.GetMana() < manaCost) {

                // if we dont have enough Mana, we cant operate the Ability!
                return;

            }
            
            // 1. Get the CooldownStore component:
            CooldownStore cooldownStore = user.GetComponent<CooldownStore>();

            // 2. Check if the Time Remaining > 0, reduce the timer, and get out of this function:
            if (cooldownStore.GetTimeRemaining(this) > 0) {

                return;

            }

            // 3. if there is no timer running, you can use the ability again:

            // This function overrides 'ActionItem.Use()' functionality
            AbilityData data = new AbilityData(user);
            data.SetConsumedCallback(consumeCallback);

            // Preparing to cancel anything we're doing, just so we can play our Ability:
            ActionSchedular actionSchedular = user.GetComponent<ActionSchedular>();
            // Playing whatever animation we have, setup for our ability:
            actionSchedular.StartAction(data);

            targetingStrategy.StartTargeting(data, 
            () => {TargetAcquired(data);}); // startTargeting is overridden by 'DemoTargeting.cs' from 'TargetingStrategy.cs'
            // TargetAcquired is a Lambda function, but we dont pass in the () when calling it because we are not calling the function
            // right now, just refering it, so we can call it later from 'TargetingStrategy.cs'

        }

        private void TargetAcquired(AbilityData data) {

            // Let the target go, if the player cancels his action:
            if (data.IsCancelled()) return;

            /* 
            // 0. When Acquiring a Target for our ability, the first step is to Consume the Mana
            Mana mana = data.GetUser().GetComponent<Mana>();
            if (!mana.UseMana(manaCost)) return; // consumes the mana, based on the chosen Ability (if Ability is chosen to be used)


            // 1. Get the Player (user) CooldownStore, and then start the 
            // cooldown timer for that, once the effect is played/Target is acquired:
            CooldownStore cooldownStore = data.GetUser().GetComponent<CooldownStore>();
            cooldownStore.StartCooldown(this, cooldownTime); */

            // To apply the bunch of filters, maybe if you want to classify enemies from bosses, enemies from friends, etc:
            
                foreach (var filterStrategy in filterStrategies) {

                    if (data.GetTargets() == null || !data.GetTargets().Any()) continue;
                    data.SetTargets(filterStrategy.Filter(data.GetTargets()));

                }

                if (noTargetRequired || (data.GetTargets() != null && data.GetTargets().Any())) {

                    // If we have no Target Required for our spells, or any targets at all, apply the Mana and Cooldown Time:

                // 0. When Acquiring a Target for our ability, the first step is to Consume the Mana
                Mana mana = data.GetUser().GetComponent<Mana>();
                if (!mana.UseMana(manaCost)) return; // consumes the mana, based on the chosen Ability (if Ability is chosen to be used)

                data.InvokeConsumedCallback();

                // 1. Get the Player (user) CooldownStore, and then start the 
                // cooldown timer for that, once the effect is played/Target is acquired:
                CooldownStore cooldownStore = data.GetUser().GetComponent<CooldownStore>();
                cooldownStore.StartCooldown(this, cooldownTime);

                foreach (var effect in effectStrategies) {

                    effect.StartEffect(data, EffectFinished);

                }

                }
            
            }

        private void EffectFinished() {

            // This function doesnt do anything, but it must exist if we are inheriting
            // from ActionItem.cs

        }

        }

    }

It’s funny how seeing the code can draw you back to look at the inspector again…

Yup, that fixed it, but now there’s a bit of a bug. The fire spray works well, my problem is it works even if we don’t have any targets. How do we limit the ability to only work with targets through code, preferably keeping it as a boolean option in the inspector? (That’s why I had the IsAliveFilter integrated, at first)

The Firespray shouldn’t even be considiering targets. It’s a DirectionalTargetingStrategy.

To make it require a target, then you would uncheck the No Targets Required, and write a strategy that only returns a target if the mouse is over an enemy.

Do you have any tips on how we would go around coding this? That’s what I tried doing by integrating the ‘IsAliveFilter’, but it failed :frowning:

Yeah, it would fail because no targets are returned in Directional Targeting.

What we need is a UnitsUnderCursorTargeting

namespace RPG.Abilities.Targeting
{
    [CreateAssetMenu(fileName = "UnitsUnderCursor", menuName = "Abilities/Targeting/UnitsUnderCursor", order = 0)]
    public class UnitsUnderCursorTargeting : TargetingStrategy

It will need a handy method for getting all the units under the cursor. Since our AbilityData SetTargets requires an IEnumerable<GameObject>, we’ll use that as our method header and grab everything under Physics.RaycastAll(PlayerController.GetMouseRay())

        private IEnumerable<GameObject> GetGameObjectsUnderCursor()
        {
            RaycastHit[] hits = Physics.RaycastAll(PlayerController.GetMouseRay());
            foreach (var hit in hits)
            {
                yield return hit.collider.gameObject;
            }
        }

Now the targeting strategy itself requires a StartTargeting method. We want to set the tarets to the GetGameObjectsUnderCursor(), and then invoke our callback method.

        public override void StartTargeting(AbilityData data, Action finished)
        {
            data.SetTargets(GetGameObjectsUnderCursor());
            finished?.Invoke();
        }

Now your filtering strategies will employ correctly

UnitsUnderCursorTargeting.cs
using System;
using System.Collections.Generic;
using RPG.Control;
using UnityEngine;

namespace RPG.Abilities.Targeting
{
    [CreateAssetMenu(fileName = "UnitsUnderCursor", menuName = "Abilities/Targeting/UnitsUnderCursor", order = 0)]
    public class UnitsUnderCursorTargeting : TargetingStrategy
    {
        public override void StartTargeting(AbilityData data, Action finished)
        {
            data.SetTargets(GetGameObjectsUnderCursor());
            finished?.Invoke();
        }
        
        private IEnumerable<GameObject> GetGameObjectsUnderCursor()
        {
            RaycastHit[] hits = Physics.RaycastAll(PlayerController.GetMouseRay());
            foreach (var hit in hits)
            {
                yield return hit.collider.gameObject;
            }
        }
    }
}

Hi again Brian, apologies for the extreme delay, as I got busy with other things:

The code seems to work as expected (requires turning off ‘No Target Required’, as you mentioned earlier) when it comes to target. However, I have run into a few issues:

  1. There is no rotation applied to the fire spray, so it only flies in a single direction without any updates about where the mouse was pointing to start with (regardless of where the mouse points)

  2. I was hoping to implement a 0.53 second delay for the animation, and possibly trigger an animation. How can we do this?

  3. It only flies for a very limited distance, before permanently disappearing from the game, hence it never actually gets to the target

How do we implement these into the game? Apologies if my questions are repetitive or overwhelming

If it helps in anyway, I have replaced my targeting strategy instead of ‘Directional Targeting’ to the new ‘Units Under Cursor’ Targeting

Not entirely sure what you mean here. It should fire in the direction of the enemy. If the projectile is not set to homing, then it won’t follow the enemy.

basically all I’m saying is that if we highlight the enemy and activate an ability, it will unleash the Fire Particle Effect, but every single time, regardless of where the enemy is, it fires at the exact same direction, and for an extremely limited distance only before permanently disappearing into the game world (hunting a screenshot down for this one will be quite tricky)

Paste in your effect that spawns projectiles.
By “exact same direction” do you mean no matter what, it’s the same direction from shot to shot but the wrong one?

using System;
using RPG.Attributes;
using RPG.Combat;   // to access projectiles
using UnityEngine;

namespace RPG.Abilities.Effects {

[CreateAssetMenu(fileName = "Spawn Projectile", menuName = "Abilities/Effects/SpawnProjectileEffect", order = 0)]

public class SpawnProjectileEffect : EffectStrategy {

    [SerializeField] Projectile projectileToSpawn;  // projectile, to be spawned (ability)
    [SerializeField] float damage;  // damage of the projectile
    [SerializeField] bool isRightHand = true;   // checks if the ability will be launched from the players' right hand or left hand (based on what we are launching)
    [SerializeField] bool useTargetPoint = true;    // the starting point of our projectile is the instigator (player/enemy)

    public override void StartEffect(AbilityData data, Action finished) {

        // This function plays the effect of our ability, by first getting the fighters in our Area of Effect,
        // Instantiating a Projectile, and then aiming that projectile at our Targets

        Fighter fighter = data.GetUser().GetComponent<Fighter>();
        Vector3 spawnPosition = fighter.GetHandTransform(isRightHand).position;

        // Spawning a hurting projectile, for each target in our array of targets (if we are spawning from our instigator position):
        if (useTargetPoint) {
        
            SpawnProjectileForTargetPoint(data, spawnPosition);

        }

        else {

            SpawnProjectilesForTargets(data, spawnPosition);

        }

        Debug.Log($"SpawnProjectile: Starting effect shooting at {data.GetTargetedPoint()}");
        finished();

    }

        private void SpawnProjectileForTargetPoint(AbilityData data, Vector3 spawnPosition)
        {

            // For an individual target:

            Projectile projectile = Instantiate(projectileToSpawn);
            projectile.transform.position = spawnPosition;
            projectile.SetTarget(data.GetTargetedPoint(), data.GetUser(), damage);

        }

        public void SpawnProjectilesForTargets(AbilityData data, Vector3 spawnPosition) {

            // For multiple targets our ability is aiming at:

            foreach (var target in data.GetTargets())
            {

                Health health = target.GetComponent<Health>();

                if (health)
                {

                    Projectile projectile = Instantiate(projectileToSpawn);
                    projectile.transform.position = spawnPosition;
                    projectile.SetTarget(health, data.GetUser(), damage);

                }

            }

    }


}

}

by “exact same direction”, I meant that no matter what direction my enemy is in, the projectile fires in a single direction (kind of like a ‘scalar’ which has one direction only, rather than a ‘vector’ that has different directions) every single time, regardless of what direction my enemy is at (i.e: it fires in a direction independent of where it’s supposed to aim for).

UseTargetPoint is false, I trust, since we’re aiming at the character under the cursor?

So let’s see our Projectile.cs and see what’s happening when we launch.

Privacy & Terms