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
- This topic has 6 replies, 3 voices, and was last updated 1 week, 2 days ago by
Alp Apustaja.
-
AuthorPosts
-
June 24, 2025 at 6:40 pm #18356::
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?
June 24, 2025 at 6:46 pm #18357::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 theGetSpawnAngle()
method in theProjectileWeapon
class. Let’s go through a few steps to help you debug this issue:-
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. -
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. -
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);
-
Look at Attack Method: Since the
Attack
method callsGetSpawnAngle
, check howGetSpawnAngle
is being triggered from theAttack
method. Make sure any parameters or dependencies required inGetSpawnAngle
are correctly set before the method is called. -
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.
-
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
andAttack
methods, I can provide more specific guidance.June 24, 2025 at 7:01 pm #18358::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
June 24, 2025 at 7:06 pm #18359::Based on the code provided, the most likely cause of the
NullReferenceException
is in theGetSpawnAngle()
method. Specifically, themovement
object appears to be null.The line
return Mathf.Atan2(movement.lastMovedVector.y, movement.lastMovedVector.x) * Mathf.Rad2Deg;
suggests that themovement
reference is not properly initialized before being accessed.To resolve this, you should:
- Ensure the
movement
component is correctly assigned in the initialization of the weapon - Add a null check before accessing
movement.lastMovedVector
Could you confirm how the
movement
component is being set up for this weapon?June 24, 2025 at 7:22 pm #18361::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
June 24, 2025 at 7:25 pm #18362::Based on the code and description, the issue likely stems from the
movement
reference in theProjectileWeapon
class being null.To resolve this, you need to ensure that the
movement
component is properly set up. Could you clarify:- How are you setting the
movement
reference for the weapon? - Are you using a specific movement script for the player character?
- 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?
June 24, 2025 at 7:30 pm #18363::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
June 24, 2025 at 7:35 pm #18364::The issue is in the
Initialise
method of theWeapon
class. TheGetComponent()
method is trying to find thePlayerMovement
component on the current object, but it seems this is not working correctly.Instead, modify the
Initialise
method to get thePlayerMovement
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 beforeInitialise
is called. Can you confirm how the weapon is being attached to the player?June 25, 2025 at 1:55 am #18368June 29, 2025 at 6:31 pm #18418June 29, 2025 at 6:35 pm #18419::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
-
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: