So I recently finished working on my GunController script which basically takes the selected weapon into hands and non used weapons into their holsters and everything worked completely fine until now. So when I reopened my project and pressed play, I instantly got these NullReferenceExeption errors for some unknown reason.
So I started digging a bit deeper and found that WeaponSupplier() function successfully filled the weapons array(see the console), but for some reason the activeWeapon never got assigned in the Start() function.
Later I found out that Start() function never actually called itself so I called it from WeaponSupplier() function since I know that function definitely works.
And this is where the non-sense part happens - compiler gave me a āNullReferenceExceptionā error for line 27 which is the Debug.Log line which says in the console that the indeed holds an object called Pistol.
Wuuuut??#@!
Iām like super confused right now and Iām hoping that some of you smarter people can help me out.
Here is the script and my console:
GunController.cs
using UnityEngine;
using System.Collections;
public class GunController : MonoBehaviour {
public Transform hands;
public Transform[] holsters = new Transform[4];
public WeaponStats[] weapons = new WeaponStats[4];
WeaponStats ActiveWeapon;
int weaponOrder = 0;
Weapon[] weaponsInUse = new Weapon[4];
Weapon gunInHands;
float fireRate;
float damage;
float velocity;
float accuracy;
float range;
//Sets the starting weapon
void Start()
{
ActiveWeapon = weapons[0];
Debug.Log("Start() Funciton - " + weapons[0].weapon.id);
EquipWeapon();
}
//Assigns weapons to their slots
public void WeaponSupplier(WeaponStats _primaryStats, WeaponStats _secondaryStats, WeaponStats _tirtiaryStats, WeaponStats _quaternaryStats)
{
weapons[0] = _primaryStats;
weapons[1] = _secondaryStats;
weapons[2] = _tirtiaryStats;
weapons[3] = _quaternaryStats;
Debug.Log("WeaponSupplier() Funciton - " + weapons[0].weapon.id);
Start();
}
//Equips the weapon
void EquipWeapon ()
{
for (int i = 0; i < weapons.Length; i++) {
if (weaponsInUse[i] != null)
{
Destroy(weaponsInUse[i].gameObject);
}
if (gunInHands != null)
{
Destroy(gunInHands.gameObject);
}
if (i != weaponOrder) {
weaponsInUse[i] = Instantiate(weapons[i].weapon, holsters[i].position, holsters[i].rotation) as Weapon;
weaponsInUse[i].transform.parent = holsters[i];
}
if (ActiveWeapon != null)
{
gunInHands = Instantiate(ActiveWeapon.weapon, hands.position, hands.rotation) as Weapon;
gunInHands.transform.parent = hands;
WeaponProperties(ActiveWeapon);
}
}
}
void WeaponProperties(WeaponStats weaponStats)
{
fireRate = weaponStats.fireRate;
damage = weaponStats.damage;
velocity = weaponStats.velocity;
accuracy = weaponStats.accuracy;
range = weaponStats.range;
}
void Update()
{
switch (Input.inputString)
{
case "1":
weaponOrder = 0;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "2":
weaponOrder = 1;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "3":
weaponOrder = 2;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "4":
weaponOrder = 3;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "e":
weaponOrder++;
if(weaponOrder > weapons.Length - 1)
{
weaponOrder = 0;
}
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "q":
weaponOrder--;
if (weaponOrder < 0)
{
weaponOrder = 3;
}
ActiveWeapon = weapons[weaponOrder];
break;
}
//Sets the fire mode for the weapon
switch (ActiveWeapon.weapon.type)
{
case "Pistol":
if (Input.GetMouseButtonDown(0))
{
Fire();
}
break;
case "Rifle":
if (Input.GetMouseButton(0))
{
Fire();
}
break;
}
}
void Fire()
{
gunInHands.Fire(fireRate, damage, velocity);
}
}
It sounds to me like you didnāt specify an execution order for your scripts (Edit ā Project Settings ā Script Execution Order). Have the GunController script execute after the āDefault Timeā. With this solution, you can remove the call to Start in the WeaponSupplier function.
Originally you were lucky GunControllerās Start was called after the script that calls the WeaponSupplier function, but the order can change anytime you open the project unless you specify it.
The other option is to rename the Start function to something Unity doesnāt call automatically, and continue calling it in the WeaponSupplier function.
It makes perfect sense to meā¦ Youāre populating your array in the WeaponsSupplier function, but youāre trying to access array element 0 in your Start function. You may think itās not being called, but it isā¦ itās being called before your WeaponSupplier function which is why you get the null reference exception. Weapons[0] is null.
Looking at your post and code againā¦ I think I know why you think it wasnāt executing. You thought that because ActiveWeapon was null, it wasnāt working. Youāre initializing your array with 4 slots, so they will all be nullā¦ when Start executes, it sets ActiveWeapon to nullā¦ When you call it again manually, it sets it to the correct weapon because youāre calling it after your WeaponsSupplier method fills your array. So, now that youāre trying to access a property on the method with Debug.Log, it is throwing an exception because itās null. If you call WeaponsSupplier from Start() before you set ActiveWeapon, it will work fine every time assuming that _primaryStats is not null.
You could also abstract away some of that functionality. Create your own class that implements IList, or inherits List. Override the Add, AddRange, Remove, Empty methods. Call it a RollingList or somethingā¦ and add Next and Previous methods on it so it could keep track of itās current index and automatically roll to 0 (or last index) when you call those methods.
Hereās a RollingCollection (a quick implementation). It implements ICollection and uses a backing list to store the values. It adds extra Next, Previous and SetIndex methods as well as a CurrentIndex (read only) property. You could reuse it in your weapon system and other places:
public class RollingCollection<T> : ICollection<T>
{
private readonly List<T> _backingList = new List<T>();
private int _index = 0;
public T this[int index]
{
get { return _backingList[index]; }
set { _backingList[index] = value; }
}
public int CurrentIndex { get { return _index; } }
public int Count
{
get { return _backingList.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public void Add(T item)
{
_backingList.Add(item);
}
public void Clear()
{
_backingList.Clear();
_index = 0;
}
public bool Contains(T item)
{
return _backingList.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_backingList.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator()
{
return _backingList.GetEnumerator();
}
public bool Remove(T item)
{
var result = _backingList.Remove(item);
if (_index >= _backingList.Count)
_index = _index--;
return result;
}
IEnumerator IEnumerable.GetEnumerator()
{
return _backingList.GetEnumerator();
}
#region Navigation Methods
public T Current()
{
return _backingList[_index];
}
public T Next()
{
_index++;
if (_index >= _backingList.Count)
_index = 0;
return _backingList[_index];
}
public T Previous()
{
_index--;
if (_index < 0)
_index = _backingList.Count - 1;
return _backingList[_index];
}
public void SetIndex(int newIndex)
{
if(newIndex < 0 || newIndex >= _backingList.Count)
throw new IndexOutOfRangeException("New index must be greater than or equal to 0 and less than the size of the collection.");
_index = newIndex;
}
#endregion
}
EDIT: Modified so Previous and Next return the next or previous item instead of indexā¦ added this[int index] to access items by their index exclusively, and added Current which will return the item at the ācurrentā rolling index.
Iāve never thought to comment on the literary value of the compiler output. Most of the time the compiler simply say āyou messed upā. There is no emotion, no passion, no story arc, no poetry. Sometimes there is a little bit of mystery, but in general the literary value of compiler output sucks.
To answer your question, Start is firing before you call WeaponSupplier. Which is exactly what you would expect.
To fix simply call WeaponSupplier in Start (or Awake) before you try and assign a weapon. Or move the functionality in Start off into some other function you can call after you call WeaponSupplier. Either way calling Start manually is a bad idea.
This isnāt a compiler error, by the way, regardless of literary quality. Itās a run-time error. You only get compiler errors when your code is, well, compiling, and you canāt enter play mode until you have valid code that can be run by Unity. Even if the code doesnāt actually do what you want.
Hi everyone! Danielās solution worked for me and I made WeaponLibary.cs execute before GunController.cs using the āScript Execution Orderā and I got rid of Start() function and Iām not sure why I had it in the first place anyway so thanks for pointing that out!
WeaponLibary is a script that gives all the WeaponStats to the WeaponSuppler() function, thatās also why I canāt call the WeaponSupplier function from GunController.cs (as some of you suggested), because it asks for you to have all the WeaponStats variables which are only being generated by WeaponLibary.
WeaponStats is a class that holds the Weapon variable and couple floats (which are the actual stats) and Weapon is basically a GameObject that has the Weapon.cs script attached.
So sorry for the confusion about how my scripts work! I never got to try out Dustinās cool script (Itās way too advanced for me so I barley understand what It says XD), but thanks for taking your time to help me out with all this, I really appreciate it.
I also tweaked the scripts so they can handle all sorts of situations and If you are interested I can post the scripts for you.
Big Thanks to all of you and your replies, man I learned a lot from this thread!
Itās basically what you have, only abstractedā¦ hereās your script rewritten to use itā¦disclaimer, I just rewrote here in the forums so I may have missed some bitsā¦ and Iām not sure what āweaponsInUseā would be used forā¦ but you can see that itās greatly simplified. All of the code that was dealing with tracking of the current weapon index, etc, has been delegated to the RollingCollection class. So now you just call Previous(), Next(), Current().
Oh cool! Now I see it makes sense to implement RollingCollection, it definitely makes the script look way more understandable and easier to read.
Also sorry for the confusing name weaponsInUse (I should probably name it something like: weaponsInRest) since they are not being used, but they are the leftover weapons that are not in hands and they have their āresting positionsā like on your back for exampleā¦ you know like when the character wants to grab a pistol, then he will put the rifle on his back and then take the pistol on hands. Thatās also another task that EquipWeapon() function does and that should need to be added back into the āsimplifiedā version of the script.
Here is the code I had when I posted the previous reply:
I did also change weapons to a list, because the 3rd and 4th weapon will be only extras, bet 1st and 2nd weapons are mandatory and I needed my code to be able to deal with those situations.
GunController.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GunController : MonoBehaviour {
public Transform hands;
public Transform[] holsters = new Transform[4];
public List<WeaponStats> weapons = new List<WeaponStats>();
WeaponStats ActiveWeapon;
int weaponOrder = 0;
Weapon[] weaponsInRest = new Weapon[4];
Weapon gunInHands;
float fireRate;
float damage;
float velocity;
float accuracy;
float range;
//Sets the starting weapon
void Start()
{
Debug.Log("Start() Funciton - " + weapons[0].weapon.id);
EquipWeapon();
}
//Assigns weapons to their slots
public void WeaponSupplier(WeaponStats _primaryStats, WeaponStats _secondaryStats, WeaponStats _tirtiaryStats, WeaponStats _quaternaryStats)
{
weapons.Add(_primaryStats);
weapons.Add(_secondaryStats);
if (_tirtiaryStats != null)
{
weapons.Add(_tirtiaryStats);
}
if (_quaternaryStats != null)
{
weapons.Add(_quaternaryStats);
}
ActiveWeapon = weapons[0];
}
//Equips the weapon
void EquipWeapon ()
{
for (int i = 0; i < weapons.Count; i++) {
if (weaponsInRest[i] != null)
{
Destroy(weaponsInRest[i].gameObject);
}
if (gunInHands != null)
{
Destroy(gunInHands.gameObject);
}
if (i != weaponOrder && weapons[i] != null) {
weaponsInRest[i] = Instantiate(weapons[i].weapon, holsters[i].position, holsters[i].rotation) as Weapon;
weaponsInRest[i].transform.parent = holsters[i];
}
if (ActiveWeapon != null)
{
gunInHands = Instantiate(ActiveWeapon.weapon, hands.position, hands.rotation) as Weapon;
gunInHands.transform.parent = hands;
}
}
}
void Update()
{
switch (Input.inputString)
{
case "1":
weaponOrder = 0;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "2":
weaponOrder = 1;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "3":
if (weapons.Count >= 3)
{
weaponOrder = 2;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
}
break;
case "4":
if (weapons.Count == 4)
{
weaponOrder = 3;
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
}
break;
case "e":
weaponOrder++;
if(weaponOrder > weapons.Count - 1)
{
weaponOrder = 0;
}
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
case "q":
weaponOrder--;
if (weaponOrder < 0)
{
weaponOrder = weapons.Count -1;
Debug.Log(weaponOrder);
}
ActiveWeapon = weapons[weaponOrder];
EquipWeapon();
break;
}
//Sets the fire mode for the weapon
if (ActiveWeapon != null) {
switch (ActiveWeapon.weapon.type)
{
case "Pistol":
if (Input.GetMouseButtonDown(0))
{
Fire();
}
break;
case "Rifle":
if (Input.GetMouseButton(0))
{
Fire();
}
break;
}
}
}
void Fire()
{
gunInHands.Fire(ActiveWeapon.fireRate, ActiveWeapon.damage, ActiveWeapon.velocity);
}
}
I will also try learning c# properties so I can hopefully understand your RollingCollection script and attempt to implement it.
Thanks for your time and most importantlyā¦ Happy Coding!