Forum begins after the advertisement:

 


[Part 15] After choosing knife character no projectile weapon are shot or appear

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [Part 15] After choosing knife character no projectile weapon are shot or appear

Viewing 11 posts - 1 through 11 (of 11 total)
  • Author
    Posts
  • #18356
    Kim Il Woo (FGW HCM)
    Level 5
    Participant
    Helpful?
    Up
    0
    ::
    NullReferenceException: Object reference not set to an instance of an object.
    ProjectileWeapon.GetSpawnAngle () (at Assets/Scripts/Weapons/Weapon Effect/ProjectileWeapon.cs:71)
    ProjectileWeapon.Attack (System.Int32 attackCount) (at Assets/Scripts/Weapons/Weapon Effect/ProjectileWeapon.cs:39)
    Weapon.Update () (at Assets/Scripts/Weapons/Weapon.cs:84)
    ProjectileWeapon.Update () (at Assets/Scripts/Weapons/Weapon Effect/ProjectileWeapon.cs:10)

    After I test the game at 1 hour 19 min of the part 15 video, after choosing the knife character the character can move but cannot shoot the starting weapon. Any Idea why?

    #18357
    Alp Apustaja
    Level 9
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    The NullReferenceException you’re encountering typically means that you’re trying to access a member of an object that hasn’t been instantiated. In your case, it seems to be related to the GetSpawnAngle() method in the ProjectileWeapon class. Let’s go through a few steps to help you debug this issue:

    1. Check for Null References: Look at line 71 in your ProjectileWeapon.cs file, which is where the error occurs. Identify any objects that are being used on that line and ensure that they are properly instantiated before being accessed.

    2. Initialization: Ensure that all necessary components are assigned in the inspector or instantiated in your code before you try to access them. For example, if you’re using a Transform, GameObject, or any other component, make sure they are not null.

    3. Debug.Log Statements: Add some Debug.Log statements before line 71 to help track down what exactly is null. For example:

      Debug.Log("Checking object: " + yourObject);
    4. Look at Attack Method: Since the Attack method calls GetSpawnAngle, check how GetSpawnAngle is being triggered from the Attack method. Make sure any parameters or dependencies required in GetSpawnAngle are correctly set before the method is called.

    5. Weapon Component: Make sure that the weapon component is properly attached to the knife character and that all required data (like projectiles, spawn points, etc.) is configured correctly.

    6. Check Character Setup: Since you mentioned it’s the knife character that has the issue, ensure that the knife character is properly set up with all its components and scripts, and that it has the necessary references to the weapon.

    If you can share the relevant code snippets for the GetSpawnAngle and Attack methods, I can provide more specific guidance.

    #18358
    Kim Il Woo (FGW HCM)
    Level 5
    Participant
    Helpful?
    Up
    0
    ::
    using UnityEngine;
    
    public class ProjectileWeapon : Weapon
    {
        protected float currentAttackInterval;
        protected int currenAttackCount;//number of time this attack will happen
    
        protected override void Update()
        {
            base.Update();
    
            //if the attack interval goes from above 0 to below, also call attack
            if (currentAttackInterval > 0)
            {
                currentAttackInterval -= Time.deltaTime;
                if (currentAttackInterval <= 0) Attack(currenAttackCount);
            }
        }
    
        public override bool CanAttack()
        {
            if (currenAttackCount > 0) return true;
            return base.CanAttack();
        }
    
        protected override bool Attack(int attackCount = 1)
        {
            //if no projectile prefab assigned, leave warning message
            if (!currentStats.projectilePrefab)
            {
                Debug.LogWarning(string.Format("projectile prefab has not been set for {0}", name));
                currentCooldown = data.baseStats.cooldown;
                return false;
            }
    
            if (!CanAttack()) return false;
    
            //calculate the angle and oofset of spawned projectile
            float spawnAngle = GetSpawnAngle();
    
            //spawn a copy of the projectile
            Projectile prefab = Instantiate(
                currentStats.projectilePrefab,
                owner.transform.position + (Vector3)GetSpawnOffSet(spawnAngle),
                Quaternion.Euler(0, 0, spawnAngle)
            );
    
            prefab.weapon = this;
            prefab.owner = owner;
    
            //reset the cooldown only if this attack was triggered by cooldown
            if (currentCooldown <= 0)
            {
                currentCooldown += currentStats.cooldown;
            }
    
            attackCount--;
    
            if (attackCount > 0)
            {
                currenAttackCount = attackCount;
                currentAttackInterval = data.baseStats.projectileInterval;
            }
    
            return true;
        }
    
        //get the direction of the projectile should face when spawning
        protected virtual float GetSpawnAngle()
        {
            return Mathf.Atan2(movement.lastMovedVector.y, movement.lastMovedVector.x) * Mathf.Rad2Deg;
        }
    
        //Gerate a random point to spawn the projectile on and rotates the facing of the point by spawnangle
    
        protected virtual Vector2 GetSpawnOffSet(float spawnAngle = 0)
        {
            return Quaternion.Euler(0, 0, spawnAngle) * new Vector2(
                Random.Range(currentStats.spawnVariance.xMin, currentStats.spawnVariance.yMax),
                Random.Range(currentStats.spawnVariance.yMin, currentStats.spawnVariance.xMax)
            );
        }
    }

    this is the code

    #18359
    Alp Apustaja
    Level 9
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    Based on the code provided, the most likely cause of the NullReferenceException is in the GetSpawnAngle() method. Specifically, the movement object appears to be null.

    The line return Mathf.Atan2(movement.lastMovedVector.y, movement.lastMovedVector.x) * Mathf.Rad2Deg; suggests that the movement reference is not properly initialized before being accessed.

    To resolve this, you should:

    1. Ensure the movement component is correctly assigned in the initialization of the weapon
    2. Add a null check before accessing movement.lastMovedVector

    Could you confirm how the movement component is being set up for this weapon?

    #18361
    Kim Il Woo (FGW HCM)
    Level 5
    Participant
    Helpful?
    Up
    0
    ::
    using Unity.VisualScripting;
    using UnityEngine;
    
    //component that attach  to all project prefab
    
    [RequireComponent(typeof(Rigidbody2D))]
    public class Projectile : WeaponEffect
    {
        public enum DamageSource { projectile, owner };
        public DamageSource damageSource = DamageSource.projectile;
        public bool hasAutoAim = false;
        public Vector3 rotationSpeed = new Vector3(0, 0, 0);
    
        protected Rigidbody2D rb;
        protected int piercing;
    
        protected virtual void Start()
        {
            rb = GetComponent<Rigidbody2D>();
            Weapon.Stats stats = weapon.GetStats();
            if (rb.bodyType == RigidbodyType2D.Dynamic)
            {
                rb.angularVelocity = rotationSpeed.z;
                rb.linearVelocity = transform.right * stats.speed;
            }
    
            float area = stats.area == 0 ? 1 : stats.area;
            transform.localScale = new Vector3(
                area * Mathf.Sign(transform.localScale.x),
                area * Mathf.Sign(transform.localScale.y), 1
            );
    
            piercing = stats.piercing;
    
            if (stats.lifespan > 0) Destroy(gameObject, stats.lifespan);
    
            if (hasAutoAim) AcquireAutoAimFacing();
        }
    
        //if projectile have homing, will automatically find a target
        public virtual void AcquireAutoAimFacing()
        {
            float aimAngle;
    
            //find all enemies on the screen
            EnemyStats[] targets = FindObjectsByType<EnemyStats>(FindObjectsInactive.Include, FindObjectsSortMode.None);
    
            //select random enemy (if there is 1), otherwise pick random angle
            if (targets.Length > 0)
            {
                EnemyStats selectedTarget = targets[Random.Range(0, targets.Length)];
                Vector2 difference = selectedTarget.transform.position - transform.position;
                aimAngle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
            }
            else
            {
                aimAngle = Random.Range(0f, 360f);
            }
    
            transform.rotation = Quaternion.Euler(0, 0, aimAngle);
        }
    
        protected virtual void FixedUpdated()
        {
            if (rb.bodyType == RigidbodyType2D.Kinematic)
            {
                Weapon.Stats stats = weapon.GetStats();
                transform.position += transform.right * stats.speed * Time.fixedDeltaTime;
                rb.MovePosition(transform.position);
                transform.Rotate(rotationSpeed * Time.fixedDeltaTime);
            }
        }
    
        protected virtual void OnTriggerEnter2D(Collider2D other)
        {
            EnemyStats es = other.GetComponent<EnemyStats>();
            BreakableProps p = other.GetComponent<BreakableProps>();
    
            //only collide with enemies or breakable stuff
            if (es)
            {
                //if there is an owner, and damage source is set to owner, calculate knockback using the owner instead of the projectile
                Vector3 source = damageSource == DamageSource.owner && owner ? owner.transform.position : transform.position;
    
                es.TakeDamage(GetDamage(), source);
    
                Weapon.Stats stats = weapon.GetStats();
                piercing--;
                if (stats.hitEffect)
                {
                    Destroy(Instantiate(stats.hitEffect, transform.position, Quaternion.identity), 5f);
                }
            }
            else if (p)
            {
                p.TakeDamage(GetDamage());
                piercing--;
    
                Weapon.Stats stats = weapon.GetStats();
                if (stats.hitEffect)
                {
                    Destroy(Instantiate(stats.hitEffect, transform.position, Quaternion.identity), 5f);
                }
            }
    
            if (piercing <= 0) Destroy(gameObject);
        }
    }

    This is the code for the projectile weapon. First i create a knife prefab i throw the code above into knife prefab inspector, then i create a scriptableobject knife (weapon data) then i throw it into the projectile prefab after that i create a scriptable object character then i throw the knife scriptableobject into the character starting waepon slot

    #18362
    Alp Apustaja
    Level 9
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    Based on the code and description, the issue likely stems from the movement reference in the ProjectileWeapon class being null.

    To resolve this, you need to ensure that the movement component is properly set up. Could you clarify:

    1. How are you setting the movement reference for the weapon?
    2. Are you using a specific movement script for the player character?
    3. Can you show the code for how the weapon is initialized and attached to the player?

    A clarifying question: Can you provide the code for how the player’s movement and weapon are set up in your character script?

    #18363
    Kim Il Woo (FGW HCM)
    Level 5
    Participant
    Helpful?
    Up
    0
    ::
    using UnityEngine;
    
    public class PlayerMovement : MonoBehaviour
    {
    
        [HideInInspector]
        public float lastHorizontalVector;
        [HideInInspector]
        public float lastVerticalVector;
        [HideInInspector]
        public Vector2 moveDir;
        [HideInInspector]
        public Vector2 lastMovedVector;
    
        //References
        Rigidbody2D rb;
        PlayerStats player;
    
        // Start is called once before the first execution of Update after the MonoBehaviour is created
        void Start()
        {
            player = GetComponent<PlayerStats>();
            rb = GetComponent<Rigidbody2D>();
            lastMovedVector = new Vector2(1, 0f); //set default for the project to shoot if the character dont move
        }
    
        // Update is called once per frame
        void Update()
        {
            InputManager();
        }
    
        //frame rate independent
        void FixedUpdate()
        {
            Move();
        }
    
        //take in the users input
        void InputManager()
        {
            if (GameManager.instance.isGameOver)
            {
                return;
            }
    
            float moveX = Input.GetAxisRaw("Horizontal");
            float moveY = Input.GetAxis("Vertical");
    
            moveDir = new Vector2(moveX, moveY).normalized;
    
            if (moveDir.x != 0)
            {
                lastHorizontalVector = moveDir.x;
                lastMovedVector = new Vector2(lastHorizontalVector, 0f); //last move x
            }
    
            if (moveDir.y != 0)
            {
                lastVerticalVector = moveDir.y;
                lastMovedVector = new Vector2(0f, lastVerticalVector); //last move y
            }
    
            if (moveDir.x != 0 && moveDir.y != 0)
            {
                lastMovedVector = new Vector2(lastHorizontalVector, lastVerticalVector); //while moving
            }
        }
    
        //move the character
        void Move()
        {
            if (GameManager.instance.isGameOver)
            {
                return;
            }
    
            rb.linearVelocity = new Vector2(moveDir.x * player.CurrentMoveSpeed, moveDir.y * player.CurrentMoveSpeed);
        }
    }

    this is the code for character movement

    using UnityEditor.Experimental.GraphView;
    using UnityEngine;
    using System.Collections.Generic;
    using System.Collections;
    
    public abstract class Weapon : Item
    {
        [System.Serializable]
        public struct Stats
        {
            public string name, description;
    
            [Header("Visual")]
            public Projectile projectilePrefab;
            //public Aura auraPrefab;
            public ParticleSystem hitEffect;
            public Rect spawnVariance;
    
            [Header("Value")]
            public float lifespan;
            public float damage, damageVariance, area, speed, cooldown, projectileInterval, knockback;
            public int number, piercing, maxInstances;
    
            public static Stats operator +(Stats s1, Stats s2)
            {
                Stats result = new Stats();
                result.name = s2.name ?? s1.name;
                result.description = s2.description ?? s1.description;
                result.projectilePrefab = s2.projectilePrefab ?? s1.projectilePrefab;
                //result.auraPrefab = s2.auraPrefab ?? s1.auraPrefab;
                result.hitEffect = s2.hitEffect == null ? s1.hitEffect : s2.hitEffect;
                result.spawnVariance = s2.spawnVariance;
                result.lifespan = s1.lifespan + s2.lifespan;
                result.damage = s1.damage + s2.damage;
                result.damageVariance = s1.damageVariance + s2.damageVariance;
                result.area = s1.area + s2.area;
                result.speed = s1.speed + s2.speed;
                result.cooldown = s1.cooldown + s2.cooldown;
                result.number = s1.number + s2.number;
                result.piercing = s1.piercing + s2.piercing;
                result.projectileInterval = s1.projectileInterval + s2.projectileInterval;
                result.knockback = s1.knockback + s2.knockback;
                return result;
            }
    
            public float GetDamage()
            {
                return damage + Random.Range(0, damageVariance);
            }
        }
    
        protected Stats currentStats;
        public WeaponData data;
        protected float currentCooldown;
        protected PlayerMovement movement;
    
        public virtual void Initialise(WeaponData data)
        {
            base.Initialise(data);
            this.data = data;
            currentStats = data.baseStats;
            movement = GetComponent<PlayerMovement>();
            currentCooldown = currentStats.cooldown;
        }
    
        protected virtual void Awake()
        {
            if (data) currentStats = data.baseStats;
        }
    
        protected virtual void Start()
        {
            if (data)
            {
                Initialise(data);
            }
        }
    
        protected virtual void Update()
        {
            currentCooldown -= Time.deltaTime;
            if (currentCooldown <= 0f)
            {
                Attack(currentStats.number);
            }
        }
    
    
        public override bool DoLevelUp()
        {
            base.DoLevelUp();
            if (!CanLevelUp())
            {
                Debug.LogWarning(string.Format("cannot level up 0 to level 1, max level 2 already reached", name, currentLevel, data.maxLevel));
            }
    
            currentStats += data.GetLevelData(++currentLevel);
            return true;
        }
    
        //check the weapon cooldown is up or not
        public virtual bool CanAttack()
        {
            return currentCooldown <= 0;
        }
    
        protected virtual bool Attack(int attackCount = 1)
        {
            if (CanAttack())
            {
                currentCooldown += currentStats.cooldown;
                return true;
            }
            return false;
        }
    
        //get the amount of damage that the weapon supposed to deal and factoring the weapon stat (include damage varaiance, characeter might) for a buff damage
        public virtual float GetDamage()
        {
            return currentStats.GetDamage() * owner.CurrentMight;
        }
    
        public virtual Stats GetStats() { return currentStats; }
    }

    this is the code for weapon

    #18364
    Alp Apustaja
    Level 9
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    The issue is in the Initialise method of the Weapon class. The GetComponent() method is trying to find the PlayerMovement component on the current object, but it seems this is not working correctly.

    Instead, modify the Initialise method to get the PlayerMovement from the owner:

    public virtual void Initialise(WeaponData data)
    {
        base.Initialise(data);
        this.data = data;
        currentStats = data.baseStats;
        movement = owner.GetComponent(); // Change this line
        currentCooldown = currentStats.cooldown;
    }

    This assumes that the owner property is correctly set before Initialise is called. Can you confirm how the weapon is being attached to the player?

    #18368
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    Have you fixed this issue @kimilwoofgwhcm?

    #18418
    Kim Il Woo (FGW HCM)
    Level 5
    Participant
    Helpful?
    Up
    1
    ::

    Thanks for asking, I have fix the problem

      1 anonymous person
    has upvoted this post.
    #18419
    Alp Apustaja
    Level 9
    AI Assistant
    (BETA)
    Helpful?
    Up
    1
    ::

    Great! I’m glad you were able to resolve the issue. Could you share what specific fix you implemented? This would help other developers who might encounter a similar problem in the future.

      1 anonymous person
    has upvoted this post.
Viewing 11 posts - 1 through 11 (of 11 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: