Forum begins after the advertisement:
[Part 15] ProjectileWeapon.cs
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 15] ProjectileWeapon.cs
- This topic has 13 replies, 2 voices, and was last updated 2 months, 1 week ago by Terence.
-
AuthorPosts
-
September 5, 2024 at 3:18 am #15754::
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ProjectileWeapon : Weapon { protected float currentAttackInterval; protected int currentAttackCount; protected Projectile currentProjectile; protected override void Update() { base.Update(); if (currentAttackInterval > 0) { currentAttackInterval -= Time.deltaTime; if (currentAttackInterval <= 0) { Attack(currentAttackCount); } } } public override bool CanAttack() { if (currentAttackCount > 0) { return true; } return base.CanAttack(); } protected override bool Attack(int attackCount = 1) { if (!currentStats.projectilePrefab) { Debug.LogWarning(string.Format("Projectile prefab has not been set for {0}", name)); currentCooldown = weaponData.baseStats.cooldown; return false; } if (!CanAttack()) { return false; } /* float spawnAngle = GetSpawnAngle(); Projectile prefab = Instantiate(currentStats.projectilePrefab, ownerStats.transform.position + (Vector3)GetSpawnOffset(spawnAngle), Quaternion.Euler(0, 0, spawnAngle)); */ float spawnAngle = Mathf.Atan2(playerController.lastMovedVector.y, playerController.lastMovedVector.x) * Mathf.Rad2Deg; Vector2 spawnOffSet = Quaternion.Euler(0, 0, spawnAngle) * new Vector2( Random.Range(currentStats.spawnVariance.xMin, currentStats.spawnVariance.xMax), Random.Range(currentStats.spawnVariance.yMin, currentStats.spawnVariance.yMax)); Projectile prefab = Instantiate(currentStats.projectilePrefab, ownerStats.transform.position + (Vector3)spawnOffSet, Quaternion.Euler(0, 0, spawnAngle)); prefab.weapon = this; prefab.ownerStats = ownerStats; if (currentCooldown <= 0) { currentCooldown += currentStats.cooldown; } attackCount--; if (attackCount > 0) { currentAttackCount = attackCount; currentAttackInterval = weaponData.baseStats.projectileInterval; } return true; } protected virtual float GetSpawnAngle() { return Mathf.Atan2(playerController.lastMovedVector.y, playerController.lastMovedVector.x) * Mathf.Rad2Deg; } protected virtual Vector2 GetSpawnOffset(float spawnAngle = 0) { return Quaternion.Euler(0, 0, spawnAngle) * new Vector2( Random.Range(currentStats.spawnVariance.xMin, currentStats.spawnVariance.xMax), Random.Range(currentStats.spawnVariance.yMin, currentStats.spawnVariance.yMax)); } }
Its my “ProjectileWeapon.cs”
I’m not sure what the problem is, but I’m having a problem with the projectile not firing.
Please let me know if you need anything else.September 5, 2024 at 1:41 pm #15757::Add the following to your
Attack()
andCanAttack()
functions, then see if the messages print on the Console. This will tell us whether theAttack()
function is firing, and which part of it the attack is getting “stuck” on.public override bool CanAttack() { print("CanAttack() attack count: " + currentAttackCount); if (currentAttackCount > 0) { return true; } return base.CanAttack(); } protected override bool Attack(int attackCount = 1) { print("Attack() fired."); if (!currentStats.projectilePrefab) { Debug.LogWarning(string.Format("Projectile prefab has not been set for {0}", name)); currentCooldown = weaponData.baseStats.cooldown; return false; } if (!CanAttack()) { print("Attack() failed."); return false; } print("Attack() succeeded!."); /* float spawnAngle = GetSpawnAngle(); Projectile prefab = Instantiate(currentStats.projectilePrefab, ownerStats.transform.position + (Vector3)GetSpawnOffset(spawnAngle), Quaternion.Euler(0, 0, spawnAngle)); */ float spawnAngle = Mathf.Atan2(playerController.lastMovedVector.y, playerController.lastMovedVector.x) * Mathf.Rad2Deg; Vector2 spawnOffSet = Quaternion.Euler(0, 0, spawnAngle) * new Vector2( Random.Range(currentStats.spawnVariance.xMin, currentStats.spawnVariance.xMax), Random.Range(currentStats.spawnVariance.yMin, currentStats.spawnVariance.yMax)); Projectile prefab = Instantiate(currentStats.projectilePrefab, ownerStats.transform.position + (Vector3)spawnOffSet, Quaternion.Euler(0, 0, spawnAngle)); prefab.weapon = this; prefab.ownerStats = ownerStats; if (currentCooldown <= 0) { currentCooldown += currentStats.cooldown; } attackCount--; if (attackCount > 0) { currentAttackCount = attackCount; currentAttackInterval = weaponData.baseStats.projectileInterval; } return true; }
Show me the messages generated on the Console window after running the game again.
September 6, 2024 at 2:54 am #15759::Attack() fired. UnityEngine.MonoBehaviour:print (object) ProjectileWeapon:Attack (int) (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:39) Weapon:Update () (at Assets/02. Scripts/Weapon/Weapon.cs:91) ProjectileWeapon:Update () (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:14) CanAttack() attack count: 0 UnityEngine.MonoBehaviour:print (object) ProjectileWeapon:CanAttack () (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:28) ProjectileWeapon:Attack (int) (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:47) Weapon:Update () (at Assets/02. Scripts/Weapon/Weapon.cs:91) ProjectileWeapon:Update () (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:14) Attack() succeeded!. UnityEngine.MonoBehaviour:print (object) ProjectileWeapon:Attack (int) (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:52) Weapon:Update () (at Assets/02. Scripts/Weapon/Weapon.cs:91) ProjectileWeapon:Update () (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:14)
using System.Collections; using System.Collections.Generic; using UnityEngine; My Weapon.cs public abstract class Weapon : Item { [System.Serializable] public struct Stats { public string name, description; [Header("Visuals")] public Projectile projectilePrefab; public Aura auraPrefab; public ParticleSystem hitEffect; public Rect spawnVariance; [Header("Values")] public float lifespan; public float damage, damageVariance, area, speed, cooldown, projectileInterval, knockback; public int number, piercing, maxInstances; public static Stats operator +(Stats stats1, Stats stats2) { Stats result = new Stats(); result.name = stats2.name ?? stats1.name; result.description = stats2.description ?? stats1.description; result.projectilePrefab = stats2.projectilePrefab ?? stats1.projectilePrefab; result.auraPrefab = stats2.auraPrefab ?? stats1.auraPrefab; result.hitEffect = stats2.hitEffect == null ? stats1.hitEffect : stats2.hitEffect; result.spawnVariance = stats2.spawnVariance; result.lifespan = stats1.lifespan + stats2.lifespan; result.damage = stats1.damage + stats2.damage; result.damageVariance = stats1.damageVariance + stats2.damageVariance; result.area = stats1.area + stats2.area; result.speed = stats1.speed + stats2.speed; result.cooldown = stats1.cooldown + stats2.cooldown; result.number = stats1.number + stats2.number; result.piercing = stats1.piercing + stats2.piercing; result.projectileInterval = stats1.projectileInterval + stats2.projectileInterval; result.knockback = stats1.knockback + stats2.knockback; return result; } public float GetDamage() { return damage + Random.Range(0, damageVariance); } } protected Stats currentStats; public WeaponData weaponData; protected float currentCooldown; protected PlayerController playerController; public virtual void Initialise(WeaponData weaponData) { base.Initialise(weaponData); this.weaponData = weaponData; currentStats = weaponData.baseStats; playerController = GetComponent<PlayerController>(); currentCooldown = currentStats.cooldown; } protected virtual void Awake() { if (weaponData) { currentStats = weaponData.baseStats; } } protected virtual void Start() { if (weaponData) { Initialise(weaponData); } } protected virtual void Update() { currentCooldown -= Time.deltaTime; if (currentCooldown <= 0f) { Attack(currentStats.number); //Line91 } } public override bool DoLevelUp() { base.DoLevelUp(); if (!CanLevelUp()) { Debug.LogWarning(string.Format("cannot level up {0} to Level {1}, max level of {2} already reached", name, currentLevel, weaponData.maxLevel)); return false; } currentStats += weaponData.GetLevelData(++currentLevel); return true; } public virtual bool CanAttack() { return currentCooldown <= 0; } protected virtual bool Attack(int attackCount = 1) { if (CanAttack()) { currentCooldown += currentStats.cooldown; return true; } return false; } public virtual float GetDamage() { return currentStats.GetDamage() * ownerStats.CurrentMight; } public virtual Stats GetStats() { return currentStats; } } protected virtual void Update() { currentCooldown -= Time.deltaTime; if (currentCooldown <= 0f) { Attack(currentStats.number); //Line91 } }
This part of the update appears in the console window.
September 6, 2024 at 3:08 am #15760::NullReferenceException: Object reference not set to an instance of an object. ProjectileWeapon.Attack (System.Int32 attackCount) (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:62) Weapon.Update () (at Assets/02. Scripts/Weapon/Weapon.cs:91) ProjectileWeapon.Update () (at Assets/02. Scripts/Weapon/WeaponEffects/ProjectileWeapon.cs:14) float spawnAngle = Mathf.Atan2(playerController.lastMovedVector.y, playerController.lastMovedVector.x) * Mathf.Rad2Deg; //Line 62
September 6, 2024 at 5:59 am #15761::lReferenceException: Object reference not set to an instance of an object Projectile.Start () (at Assets/02. Scripts/Weapon/WeaponEffects/Projectile.cs:20)
using System.Collections; using System.Collections.Generic; using Unity.VisualScripting.Antlr3.Runtime.Misc; using UnityEngine; [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 rigid; protected int piercing; protected virtual void Start() { rigid = GetComponent<Rigidbody2D>(); Weapon.Stats stats = weapon.GetStats(); //Line20 if (rigid.bodyType == RigidbodyType2D.Dynamic) { rigid.angularVelocity = rotationSpeed.z; rigid.velocity = 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(); } } public virtual void AcquireAutoAimFacing() { float aimAngle; EnemyStats[] targets = FindObjectsOfType<EnemyStats>(); 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 FixedUpdate() { if (rigid.bodyType == RigidbodyType2D.Kinematic) { Weapon.Stats stats = weapon.GetStats(); transform.position += transform.right * stats.speed * Time.fixedDeltaTime; rigid.MovePosition(transform.position); transform.Rotate(rotationSpeed * Time.fixedDeltaTime); } } protected virtual void OnTriggerEnter2D(Collider2D coll) { EnemyStats enemyStats = coll.GetComponent<EnemyStats>(); BreakableProps props = coll.GetComponent<BreakableProps>(); if (enemyStats) { Vector3 source = damageSource == DamageSource.owner && ownerStats ? ownerStats.transform.position : transform.position; enemyStats.TakeDamage(GetDamage(), source); Weapon.Stats weaponStats = weapon.GetStats(); piercing--; if (weaponStats.hitEffect) { Destroy(Instantiate(weaponStats.hitEffect, transform.position, Quaternion.identity), 5f); } } else if (props) { props.TakeDamage(GetDamage()); piercing--; Weapon.Stats weaponStats = weapon.GetStats(); if (weaponStats.hitEffect) { Destroy(Instantiate(weaponStats.hitEffect, transform.position, Quaternion.identity), 5f); } } if (piercing <= 0) { Destroy(gameObject); } } }
September 6, 2024 at 2:36 pm #15762::The issue seems to be with this line:
float spawnAngle = Mathf.Atan2(playerController.lastMovedVector.y, playerController.lastMovedVector.x) * Mathf.Rad2Deg; //Line 62
Because you don’t have a PlayerController component attached to your GameObject. We never created a PlayerController script in our tutorial, so I don’t know what that’s referring to.
September 6, 2024 at 9:06 pm #15763September 7, 2024 at 10:56 am #15765::The issue is that your
Initialise()
function is not calling. Try making the following change to yourPlayerInventory
script’sAdd()
function:// Finds an empty slot and adds a weapon of a certain type, returns // the slot number that the item was put in. public int Add(WeaponData data) { int slotNum = -1; // Try to find an empty slot. for(int i = 0; i < weaponSlots.Capacity; i++) { if (weaponSlots[i].IsEmpty()) { slotNum = i; break; } } // If there is no empty slot, exit. if (slotNum < 0) return slotNum; // Otherwise create the weapon in the slot. // Get the type of the weapon we want to spawn. Type weaponType = Type.GetType(data.behaviour); if (weaponType != null) { // Spawn the weapon GameObject. GameObject go = new GameObject(data.baseStats.name + " Controller"); Weapon spawnedWeapon = (Weapon)go.AddComponent(weaponType);
spawnedWeapon.Initialise(data);spawnedWeapon.transform.SetParent(transform); //Set the weapon to be a child of the player spawnedWeapon.transform.localPosition = Vector2.zero; spawnedWeapon.Initialise(data); spawnedWeapon.OnEquip(); // Assign the weapon to the slot. weaponSlots[slotNum].Assign(spawnedWeapon); // Close the level up UI if it is on. if (GameManager.instance != null && GameManager.instance.choosingUpgrade) GameManager.instance.EndLevelUp(); return slotNum; } else { Debug.LogWarning(string.Format( "Invalid weapon type specified for {0}.", data.name )); } return -1; }And remove the
Start()
andAwake()
functions onWeapon
. This should force theInitialise()
function to activate.September 8, 2024 at 12:07 am #15777::Thank you for your efforts. The problem is still not solved, but I’m sorry to take away your time anymore.
I’ll try to work on my own.
September 8, 2024 at 10:37 am #15778::Ok, hopefully you manage to find the issue. Let me elaborate on your issue a bit more. NullReferenceException errors occur when you try to use a variable that is unassigned or missing. For example:
Rigidbody2D rb; // No value assigned. // Gives an error because rb.velocity evaluates to null.velocity rb.velocity = new Vector(3, 2);
In your case, if we take one of your errors as an example:
protected virtual void Update() { currentCooldown -= Time.deltaTime; if (currentCooldown <= 0f) { Attack(currentStats.number); //Line91 } }
The error is occurring because
currentStats
is null, hence causing it to evaluate tonull.number
when you usecurrentStats.number
.currentStats
is assigned in theInitialise()
function inWeapon
.public virtual void Initialise(WeaponData weaponData) { base.Initialise(weaponData); this.weaponData = weaponData; currentStats = weaponData.baseStats; playerController = GetComponent
(); currentCooldown = currentStats.cooldown; } That is why I suspected the issue is with
Initialise()
. I hope this helps!September 9, 2024 at 2:21 am #15786::Invalid AABB inAABB
UnityEngine.Canvas:SendWillRenderCanvases ()Is this error related to the prefab I created?
September 9, 2024 at 3:07 am #15787::playerController = GetComponent<PlayerController>(); ->
playerController = GetComponentInParent<PlayerController>();It works when I change it!
September 9, 2024 at 11:15 pm #15802 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: