Weird issue with recognising projectiles

So the melee weapons work. If I try to get new weapons via pickups, the weapons change out, but the projectile remains null. It’s the most infuriating thing. No matter what I try, I can’t get the projectiles to be acknowledged as anything but null.

I can see that the weapons are set up right. They each have what they need in the inspector.

I test based on whether the [SerializeField] Projectile projectile is null. If it’s null, it’s not a ranged weapon. If the projectile field is populated, then I have it set to use that non-null field decide what to fire.

I have it set in Update() to say isRangedWeapon = (projectile != null), which is to say if the projectile is not null, then != null would return as true, making isRangedWeapon make an update every frame to whether its currentWeapon has a projectile and is therefore ranged, and then printing it out… from print("IsRangedWeapon: " + IsRangedWeapon) I get nothing but "IsRangedWeapon: " false and even when I make a serializable field equal to the getter for isRangedWeapon, it never registers the weapons I’m using as having projectiles, despite every weapon needing a projectile (bows, wands) having one!

The Projecile class works when it’s launched, but projectiles aren’t being spawned because isRangedWeapon always resolves to false and so ranged weapons are treated as melee weapons.

Melee weapons work fine. It’s just this projectile being null issue. Here’s my relevant code.
Fighter.cs:

using UnityEngine;
using RPG.Movement;
using RPG.Core;
using System;
using System.Collections.Generic;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction
    {
        [SerializeField] float timeBetweenAttacks = 1f;
        [SerializeField] Transform rightHandTransform = null;
        [SerializeField] Transform leftHandTransform = null;
        [SerializeField] Weapon defaultWeapon = null;
        [SerializeField] Weapon currentWeapon;
        [SerializeField] bool isRangedWeapon;
        Health target;
        float timeSinceLastAttack = Mathf.Infinity;
        Mover mover;
        Animator animator;
        Dictionary<string, Weapon> saveObject = new Dictionary<string, Weapon>();

        private void Start() {
            animator = GetComponent<Animator>();
            mover = GetComponent<Mover>();
            currentWeapon = defaultWeapon;
            EquipWeapon(currentWeapon);
        }

        private void Update()
        {
            isRangedWeapon = currentWeapon.IsRangedWeapon;

            timeSinceLastAttack += Time.deltaTime;

            if (target == null) return;
            if (target.IsDead()) return;

            if (!GetIsInRange())
            {
                
                mover.MoveTo(target.transform.position, 1f);
            }
            else
            {
                mover.Cancel();
                AttackBehaviour();
            }
        }

        public void EquipWeapon(Weapon weapon)
        {
            weapon.Spawn(rightHandTransform, leftHandTransform, animator);
            currentWeapon = weapon;

        }


        private void AttackBehaviour()
        {
            transform.LookAt(target.transform);
            if (timeSinceLastAttack > timeBetweenAttacks)
            {
                // This will trigger the Hit() event.
                TriggerAttack();
                timeSinceLastAttack = 0;
            }
        }

        private void TriggerAttack()
        {
            GetComponent<Animator>().ResetTrigger("stopAttack");
            GetComponent<Animator>().SetTrigger("attack");
        }

        // Animation Event
        void Hit(){
            if(target == null) { return; }
            if (isRangedWeapon){ 
                currentWeapon.LaunchProjectile(rightHandTransform, leftHandTransform, GetComponent<Health>(), target);
            }
            else
            {
                print("IsRangedWeapon: "+ currentWeapon.IsRangedWeapon);
                target.TakeDamage(currentWeapon.WeaponDamage);  
            }
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.transform.position) < currentWeapon.WeaponRange;
        }

        public bool CanAttack(GameObject combatTarget)
        {
            if (combatTarget == null) { return false; }
            Health targetToTest = combatTarget.GetComponent<Health>();
            return targetToTest != null && !targetToTest.IsDead();
        }

        public void Attack(GameObject combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.GetComponent<Health>();
        }

        public void Cancel()
        {
            StopAttack();
            target = null;
            mover.Cancel();
        }

        private void StopAttack()
        {
            GetComponent<Animator>().ResetTrigger("attack");
            GetComponent<Animator>().SetTrigger("stopAttack");
        }

    }
}using UnityEngine;
using RPG.Movement;
using RPG.Core;
using System;
using System.Collections.Generic;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction
    {
        [SerializeField] float timeBetweenAttacks = 1f;
        [SerializeField] Transform rightHandTransform = null;
        [SerializeField] Transform leftHandTransform = null;
        [SerializeField] Weapon defaultWeapon = null;
        [SerializeField] Weapon currentWeapon;
        [SerializeField] bool isRangedWeapon;
        Health target;
        float timeSinceLastAttack = Mathf.Infinity;
        Mover mover;
        Animator animator;
        Dictionary<string, Weapon> saveObject = new Dictionary<string, Weapon>();

        private void Start() {
            animator = GetComponent<Animator>();
            mover = GetComponent<Mover>();
            currentWeapon = defaultWeapon;
            EquipWeapon(currentWeapon);
        }

        private void Update()
        {
            isRangedWeapon = currentWeapon.IsRangedWeapon;

            timeSinceLastAttack += Time.deltaTime;

            if (target == null) return;
            if (target.IsDead()) return;

            if (!GetIsInRange())
            {
                
                mover.MoveTo(target.transform.position, 1f);
            }
            else
            {
                mover.Cancel();
                AttackBehaviour();
            }
        }

        public void EquipWeapon(Weapon weapon)
        {
            weapon.Spawn(rightHandTransform, leftHandTransform, animator);
            currentWeapon = weapon;

        }


        private void AttackBehaviour()
        {
            transform.LookAt(target.transform);
            if (timeSinceLastAttack > timeBetweenAttacks)
            {
                // This will trigger the Hit() event.
                TriggerAttack();
                timeSinceLastAttack = 0;
            }
        }

        private void TriggerAttack()
        {
            GetComponent<Animator>().ResetTrigger("stopAttack");
            GetComponent<Animator>().SetTrigger("attack");
        }

        // Animation Event
        void Hit(){
            if(target == null) { return; }
            if (isRangedWeapon){ 
                currentWeapon.LaunchProjectile(rightHandTransform, leftHandTransform, GetComponent<Health>(), target);
            }
            else
            {
                print("IsRangedWeapon: "+ currentWeapon.IsRangedWeapon);
                target.TakeDamage(currentWeapon.WeaponDamage);  
            }
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.transform.position) < currentWeapon.WeaponRange;
        }

        public bool CanAttack(GameObject combatTarget)
        {
            if (combatTarget == null) { return false; }
            Health targetToTest = combatTarget.GetComponent<Health>();
            return targetToTest != null && !targetToTest.IsDead();
        }

        public void Attack(GameObject combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.GetComponent<Health>();
        }

        public void Cancel()
        {
            StopAttack();
            target = null;
            mover.Cancel();
        }

        private void StopAttack()
        {
            GetComponent<Animator>().ResetTrigger("attack");
            GetComponent<Animator>().SetTrigger("stopAttack");
        }

    }
}

Weapon.cs:

using System;
using RPG.Core;
using UnityEngine;

namespace RPG.Combat{
    [CreateAssetMenu(fileName = "Weapon", menuName = "RPG/Make New Weapon", order = 0)]
    public class Weapon : ScriptableObject {
        [SerializeField] AnimatorOverrideController animatorOverride = null;
        [SerializeField] GameObject equippedWeaponPrefab = null;
        [SerializeField] Projectile projectile = null;
        [SerializeField] float weaponRange = 0f;
        [SerializeField] float weaponDamage = 0f;
        [SerializeField] bool isRightHanded = true;
        bool isRangedWeapon = true;
        GameObject weapon = null;
        public GameObject EquippedWeaponPrefab{get{return equippedWeaponPrefab;}}
        public float WeaponRange{get{return weaponRange;}}
        public float WeaponDamage{get{return weaponDamage;}}
        public bool IsRangedWeapon{get{return isRangedWeapon;}}

        const string weaponName = "Weapon";

        private void Update() {
            isRangedWeapon = (projectile != null);
        }

        public void Spawn(Transform rightHandTransform, Transform leftHandTransform, Animator animator)
        {

            DestroyOldWeapon(rightHandTransform, leftHandTransform);

            if (equippedWeaponPrefab != null) {
                Transform handTransform = GetTransform(rightHandTransform, leftHandTransform);
                weapon = Instantiate(equippedWeaponPrefab, handTransform);
                weapon.name = weaponName;
            }
            
            AnimatorOverrideController overrideParentController = animator.runtimeAnimatorController as AnimatorOverrideController;
            
            if (animatorOverride != null)
            {
                animator.runtimeAnimatorController = animatorOverride as RuntimeAnimatorController;
            }
            else if (overrideParentController != null){
                animator.runtimeAnimatorController = overrideParentController.runtimeAnimatorController;
            }
        }

        private void DestroyOldWeapon(Transform rightHandTransform, Transform leftHandTransform)
        {
            Transform oldWeapon = rightHandTransform.Find(weaponName);
            if (oldWeapon == null){
                oldWeapon = leftHandTransform.Find(weaponName);
            }
            if (oldWeapon == null) return;

            oldWeapon.name = "Cleaning...";
            
            Destroy(oldWeapon.gameObject);
        }

        private Transform GetTransform(Transform rightHandTransform, Transform leftHandTransform)
        {
            Transform handTransform;
            if (isRightHanded) handTransform = rightHandTransform;
            else handTransform = leftHandTransform;
            return handTransform;
        }

        public void LaunchProjectile(Transform rightHand, Transform leftHand, Health source, Health target){
            Projectile thisArrow = Instantiate(projectile, rightHand.position, Quaternion.identity);
            thisArrow.DifferentiateTarget(source, target);
            thisArrow.SetDamageMod(weaponDamage);
        }
    }
}

If you’ve made it this far, thank you for reading!

Scriptable objects do not get an Update() message, so this code never runs. If you want to use the projectile to determine if it’s a ranged weapon, you can change your SO’s property to do the check

//bool isRangedWeapon = true; // <- remove this
public bool IsRangedWeapon{get{return projectile != null;}} // change the getter to check the projectile

Also remove the Update() method

2 Likes

That was it! Thank you so much! It’s a little bit off that I didn’t get red squigglies when implementing a class that didn’t belong in the scriptable object, but I’m glad that you replied so quickly! After 10 hours of working through this course I was a little bit tired for troubleshooting! :sweat_smile:

Well, you created a method called Update() which is totally valid and there’s no reason for the IDE to put red squigglies underneath it. It’s just that Unity will not call that method because it’s a scriptable object.

1 Like

You can put an Update() method in a ScriptableObject, nothing invalid about that at all, and no compiler will note it. About the only warning you might get is that some IDEs may tell you that Update() is never called. JetBrains, for example, will never flag Update() as “never called” in a MonoBehaviour because it knows that MonoBehaviours send an Update() message.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms