Hello! I’m currently building a weapon system for a game I’m developing, and was having some trouble referencing properties from my subclass scripts. I’m using an abstract MonoBehaviour script as the base script for all weapons (aptly named “PlayerWeaponBase”), and I’m currently developing a weapon derived from this class called “NinjaStarWeapon”.
I thought it would be clever to have common functions like checking for ammunition and processing player input in a main script, but it seems to be generating more hassle than help. While I don’t get any compiler or run-time errors, I keep running into this frustrating problem: while the NinjaStarWeapon script seems to be aware of how much ammunition it holds, no other script seems to know that. Furthermore, when I try to call the functions of the weapon derived from PlayerWeaponBase, the functions keep thinking the weapon is out of ammunition. This is the case for all of its properties & variables.
My only guess is that the script isn’t actually generating an instance of PlayerWeaponBase… is this true? In my mind, it’s either that, or Unity is high and it’s referencing the properties of an abstract class.
I’ve attached some of my code below for reference. Also, if there’s any way I can/should improve this post, let me know! I’m all ears.
goob
// NOTE: This is a highly-trimmed down version of my work, so a lot of code has been stripped out for the sake of brevity. If you would like more context, I'd be happy to oblige.
// script 1
public abstract class PlayerWeaponBase : MonoBehaviour
{
internal abstract int WeaponCharges {get;}
internal abstract int CurrentCharges {get; set;}
private void Awake()
{
CurrentCharges = WeaponCharges; // fill up ammo
}
}
// script 2
public class NinjaStarWeapon : PlayerWeaponBase
{
internal override int WeaponCharges => 3;
internal override int CurrentCharges {get; set;}
}
// script 3
public class Test1 : MonoBehaviour
{
[SerializeField] PlayerWeaponBase weapon;
void Awake()
{
Debug.Log(weapon.CurrentCharges);
// line always yields 0, regardless of the value of CurrentCharges written in the weapon's dedicated script.
}
}
The example you have here looks like it ought to work, but I suspect you’ve trimmed out something that you might assume was not an issue, so let me ask this: Does NinjaStarWeapon have an Awake() function? If so, then the base class’s Awake() is never called. That’s where CurrentCharges is being set to WeaponCharges, so if it’s never called, it’ll just be at the default (which is 0).
You can make Awake() virtual and override it in NinjaStarWeapon while calling base.Awake().
First, welcome to the unity forum and thank you for using code tags
Second:
Edit: a) and b) are just none-sense by me. As StarManta pointed out you are probably dragging in the NinjaStar in the inspector, my bad (there is no strike text format in the forum?, Edit2: found it!)
a) I am not sure if it’s a side effect of you trimming down the code but your Test1 class has not connection at all to NinjaStarWeapon. Therefore I wouldn’t expect it to have any value but 0. b) Awake is a method called by unity on every object in the scene graph. Your base weapon is just a serialized property within Test1. Therefore Awake() of PlayerWeaponBase will never be called.
c) You are using a lot of syntactic sugar. I’d recommend to type it out and litter it with Debug.Log() or use the debugger to figure out what’s happening.
d) You’d better use composition instead of inheritance imho (opinions may differ in this).
Er, no? PlayerWeaponBase is a MonoBehaviour, so it’ll be attached to an object that OP would drag into the slot on the inspector. And it would get the Awake() message (though as mentioned in the first reply, a derived class’s Awake() would hide it)
Yes, true. My assumption that it’s not part of the scene graph was wrong. I did not think of the OP dragging it in there. Thanks. I’ll edit it so the OP will not follow my wrong lead.
NinjaStarWeapon doesn’t have an Awake() function, no. Thanks for the tip, though!
Perhaps I can provide further clarity by showing more of my code. This is derived from the PlayerWeaponBase script; TryAttack is the only time (outside of Awake()) when CurrentCharges is modified:
Despite the fact that the NinjaStarWeapon object has 3 charges in it by default, (“charges” are what I’m calling “ammo” as not all weapons have ammunition, but rather a limited number of uses) when this function is called, Debug.Log prints out this:
Since I made a fool of myself with my first answer ( ) I booted up Unity and tried your inital code sample. It works just fine (prints “3”). So there has to be something else going on here. Have you tried putting a Debug.Log() into your Awake() and TryAttack()? Maybe you are calling it before Awake()?
You can use Debug.Break() to stop Unity and then proceed frame by frame.
Update I don’t think the extra code you posted is the issue. Here is the test class I’ve written to add a button. The only way I can get “0” as a result is if I call it before first activation in the scene.
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
public class NinjaStarWeapon : PlayerWeaponBase
{
internal override int WeaponCharges => 3;
internal override int CurrentCharges { get; set; }
private float m_LastTimeAttacked;
public float DelayBetweenAttacks = 1f;
virtual internal bool MayShoot()
{
return (
(Time.time - this.m_LastTimeAttacked) >= this.DelayBetweenAttacks
&
this.CurrentCharges >= 1
);
}
public bool TryAttack()
{
Debug.Log(this + "currently has " + CurrentCharges + " charges.");
if (MayShoot())
{
CurrentCharges -= 1;
//HandleAttack(); // attack code
m_LastTimeAttacked = Time.time;
return true;
}
else
{
//TryAttackFailure();
return false;
}
}
// Adds a TryAttack button to your weapon inspector.
#if UNITY_EDITOR
[CustomEditor(typeof(NinjaStarWeapon))]
public class NinjaStarWeaponEditor : Editor
{
public override void OnInspectorGUI()
{
NinjaStarWeapon ninjaStar = this.target as NinjaStarWeapon;
if (GUILayout.Button("TryAttack()"))
{
ninjaStar.TryAttack();
}
base.OnInspectorGUI();
}
}
#endif
}
I have tried using Debug.Log() during Awake() and TryAttack(); the number of charges printed out in the console is accurate during Awake(), but not TryAttack()–unless, of course, if I call it directly through the Custom Editor button, then it works like a charm.
The strange thing about this is that, while NinjaStarWeapon has a hold on its properties when referenced from within its own script, all other scripts seem to think it has 0 Charges. I mean, I know I’ve said this already a handful of times, but look at my console output:
NinjaStarWeapon: I have 3 of 3 charges!
PlayerWeaponsManager: Equipped weapon (NinjaStarWeapon) has 0 of 3 charges!
This has me dumbfounded. I’m referencing the same object from two sources, and they’re returning different values despite there being no possible change during the interim. If I reference CurrentCharges from within NinjaStarWeapon, then it’s fine; no issues! However, if I reference the same value from another script (or even if I call the function by means outside of NinjaStarWeapon; for example, calling it from my otherwise fine HUD script), I get back 0.
My only thought now is that there’s some breakdown in communication between scripts. I mean, that’s the only way that this could be happening, right? Do I need to specify something when I’m referencing an object which is a subclass of an abstract class? Thank you guys so much for your help! This problem has really knocked me back on my heels because it’s so inexplicable.
Can you please try to use Debug.Log(this.GetInstanceID() + "currently has " + CurrentCharges + " charges and is "+(this.gameObject.activeInHierarchy ? "active" : "inactive")+".");, just to confirm it really is called on the same object.
I have tried it with this Test1 class and it works:
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
public class Test1 : MonoBehaviour
{
[SerializeField] PlayerWeaponBase weapon;
void Awake()
{
Debug.Log(weapon.CurrentCharges); // depends on the order of Awake() calls
Debug.Break();
}
#if UNITY_EDITOR
[CustomEditor(typeof(Test1))]
public class Test1Editor : Editor
{
public override void OnInspectorGUI()
{
Test1 test1 = (this.target as Test1);
if (GUILayout.Button("TryAttack()"))
{
((NinjaStarWeapon)test1.weapon).TryAttack(); // prints 3
}
base.OnInspectorGUI();
}
}
#endif
}
Can you post the code you are using to call TryAttack() from an external class? Maybe there is an issue there.
My guess is, because that’s a common mistake, that you referenced the wrong object you try to “use”. Many people accidentally reference the prefab object instead of the actual instance in the scene. While prefabs are also object instances, they do not live in the scene and do not get any callbacks. However they can still be used “manually”, i.e. you can call methods on them.
Try adding a Debug.Log statement like this to your TryAttack method:
Notice the secondary parameter where I passed the gameObject reference. That second parameter is a context object that is stored with the log message. When you click on such a log message in the console, Unity will ping / highlight that object. This should help identifying the object you’re actually working with.