So, I have this class:
public class MobileUnit : Unit {
AidansMovementScript moveConductor;
void Awake () {
string prefab = gameObject.name;
prefab = prefab.Remove(prefab.IndexOf("("));
prefab = "Sprites/" + prefab;
if (photonView.Owner.ActorNumber == 1) {
GetComponent<SpriteRenderer>().sprite = Resources.Load<Sprite>(prefab + "_white");
}
else if (photonView.Owner.ActorNumber == 2) {
GetComponent<SpriteRenderer>().sprite = Resources.Load<Sprite>(prefab + "_orange");
}
if (this.GetType() == typeof(MobileUnit)) {
if (photonView.IsMine) {
gameObject.AddComponent<MobileUnit_local>();
}
else {
gameObject.AddComponent<MobileUnit_remote>();
}
Destroy(this);
}
}
private void Start() {
gameState = GameObject.Find("Goliad").GetComponent<GameState>();
MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
moveConductor = gameObject.GetComponent<AidansMovementScript>();
}
public virtual void move (Vector3 target, Transform movingTransform = null) {
moveConductor.setDestination(target, movingTransform);
}
}
âŚand then I have this subordinate class:
public class MobileUnit_local : MobileUnit {
AidansMovementScript moveConductor;
void Start () {
gameState = GameObject.Find("Goliad").GetComponent<GameState>();
MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
moveConductor = GetComponent<AidansMovementScript>();
gameState.enlivenUnit(gameObject);
}
public override void activate () {
gameState.activateUnit(gameObject);
gameObject.transform.GetChild(0).gameObject.SetActive(true);
transform.GetChild(0).GetChild(0).GetComponent<RectTransform>().localScale = new Vector3(1,1,1) * (Camera.main.orthographicSize / 5);
}
public override void deactivate () {
gameObject.transform.GetChild(0).gameObject.SetActive(false);
gameState.deactivateUnit(gameObject);
}
}
⌠and I call âmoveâ in the subordinate class like so:
unit.GetComponent<MobileUnit>().move(destination, optionalTransform);
I get a null reference exception: moveConductor hasn't been defined. After probing around, I discovered that the MobileUnit.move call goes to a MobileUnit class, not a MobileUnit_local class. This happens even if I call this.move() from in the MobileUnit_local Start() function. I'm not great at inheritance, but I'm pretty sure that I should be able to define methods in parent methods and have them implicitly present in child methods.
What am I not understanding?
Edit: Ignore this answer and see below
But you never made an instance of a MobileUnit_local class (or at the very least, you seem to have upcasted it). You made/got an instance of MobileUnit class, and called its move()
These should be clear enough as your hints
unit.GetComponent<MobileUnit>()
Why should it ever call a MobileUnit_local move method?
If you have a hard time understanding inheritance, Bunny83 wasnât as lazy as me to elaborate on it
Btw, this is unrelated, but try to stick to standard recommendation to C# naming. Method and property names should be PascalCase, field and variable names should camelCase. You can do whatever you want, I donât mind, but being strict with the basic standards such as method and field naming, will likely help you read other peopleâs code (and vice versa) and perhaps introduce less errors, because youâre still using other peopleâs APIs in a different writing standard.
@orionsyndrome This is what the gameobject in question looks like when the method is called:

As you can see, the attached script is a MobileUnit_local, not a MobileUnit.
Edit: Ignore this answer, see the next one
Good thing youâve showed how exactly youâve defined it.
But that still doesnât matter. Youâre upcasting it to MobileUnit by doing unit.GetComponent(). (Well, technically youâre not upcasting anything, but are grabbing itâs superclass instead)
Why not GetComponent of correct type?
unit.GetComponent<MobileUnit_local>().move(...)
If you donât want to, for some reason, maybe itâs a common thing for a bunch of different MobileUnit derivations, okay, then you need to downcast it first, if you know itâs of the right type.
Here
- you grab MobileUnit component
unit.GetComponent<MobileUnit>();
- you then downcast it to MobileUnit_local, it works because the object IS in fact MobileUnit_local or a derivation of it, it wouldnât work otherwise
(MobileUnit_local)unit.GetComponent<MobileUnit>();
((MobileUnit_local)unit.GetComponent<MobileUnit>()).move(...);
To make this easier on the eyes
var mobileUnit = unit.GetComponent<MobileUnit>();
var mobileUnitLocal = (MobileUnit_local)mobileUnit;
mobileUnitLocal.move(...);
Ahh naah man you confused me in a roundabout way
Where is your override in MobileUnit_local? You never override your virtual method.
MobileUnit_local has no move at all
Dismiss what I said above, thatâs not the problem, and your expectation was right.
But you need to override the method.
The method does relate to your instance in question, but it truly belongs to MobileUnit.
Unless you override it. I kind of fully expected youâve done this, by mistake.
public class MobileUnit_local : MobileUnit {
public override move(...) {
//...
}
}
Full answer: you need to have moveConductor defined in MobileUnit if you intend to call MobileUnit.move(). If you want moveConductor only in MobileUnit_local, you have to override move in MobileUnit_local.
move from MobileUnit doesnât know about moveConductor and you canât access fields like that from derived class instances. it works only upstream, and you need to use protected keyword to be able to do that. The reason being a simple fact that you could have several different derivations of the same class, but there is always one superclass to each of them.
I.e. if moveConductor was in MobileUnit and move was in MobileUnit_local
public class MobileUnit {
protected GameObject moveConductor; // private but inherited (NOT SHARED) by any subclasses
}
public class MobileUnit_local : MobileUnit {
public void move(...) {
moveConductor.whatnot();
}
}
if moveConductor was in MobileUnit_local and move was in MobileUnit
public class MobileUnit {
public virtual void move(...) { // must be marked as virtual so that subclasses can override
//...
}
}
public class MobileUnit_local {
private GameObject moveConductor; // field local to this class
public override void move(...) {
moveConductor.whatnot();
}
}
Itâs an important thing to notice, as explained by Bunny83 in that post above, that instances of classes that are derived do not share anything with each other, except for being able to repurpose or override the methods as defined with virtual/override. All data is kept in each instance, as usual.
If you think of classes as templates, your MobileUnit_local class simply adds to any existing definition of MobileUnit, and is treated in addition as âsome kind of MobileUnitâ. Instance of the two classes, however, have nothing in common, except for being treated as type-compatible (both are MobileUnits). I have to mention this just in case.
When you override a virtual method, that means that MobileUnit_local practically redefines this method locally, and whenever the existing logic would necessitate calling of that MobileUnitâs method in an instance of MobileUnit_local, MobileUnit_localâs override would be called instead, because it replaced the original MobileUnitâs definition of what is move.
In your case, however, there is no override, so you end up calling MobileUnitâs move implementation which simply cannot access any data thatâs outside of that class.
This is so confusing to explain because of your poor choice of names.
@orionsyndrome Thank you for giving so much attention to this issue. What you are saying seems to contradict this other example of inheritance in my project: (sorry about the capitalization; Iâll take your advice and change it on my next refactor)
public class Unit : MonoBehaviourPun {
protected GameState gameState;
protected GameObject MeatReadout;
public int maxMeat = 30;
public int meat = 10;
int meatCost = 10;
void Awake () {
...
}
void Start() {
gameState = GameObject.Find("Goliad").GetComponent<GameState>();
MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
}
[PunRPC]
public virtual void die() {
gameState.deadenUnit(gameObject);
GameObject orb = (GameObject)Resources.Load("Orb");
for (; meat > 0; --meat) {
Vector3 place = new Vector3(transform.position.x + Random.Range(-.1f, 0.1f), transform.position.y + Random.Range(-.5f, 0.5f), 0);
GameObject lastOrb = Instantiate(orb, place, transform.rotation);
lastOrb.GetComponent<Rigidbody2D>().AddForce((lastOrb.transform.position - transform.position).normalized * 2);
}
Destroy(gameObject);
}
}
public class Unit_local : Unit {
void Start() {
gameState = GameObject.Find("Goliad").GetComponent<GameState>();
gameState.enlivenUnit(gameObject);
MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
}
public override void activate () {
gameState.activateUnit(gameObject);
gameObject.transform.GetChild(0).gameObject.SetActive(true);
transform.GetChild(0).GetChild(0).GetComponent<RectTransform>().localScale = new Vector3(1,1,1) * (Camera.main.orthographicSize / 5);
}
public override void deactivate () {
gameObject.transform.GetChild(0).gameObject.SetActive(false);
gameState.deactivateUnit(gameObject);
}
}
So, same deal as before:
- Virtual method die() in the base class is not overridden in child class.
- die() refers to things that arenât defined in the base class.
- die() called in an instance of child class (not pictured, but I tested it)
This works! What is different here than in the case of move()?
SOLVED: I just forget to name moveConductor as a Public or Protected object. Sorry @orionsyndrome for taking your time with this one; Iâm sure you understand that sometimes big dialogues are needed to spot small errors. I guess the real mystery is why VS Code didnât highlight the error last night, and Unity allowed the scripts to run.
1 Like
Thereâs no compile time error here. Both classes define AidansMovementScript moveConductor;
, so a field with this name is available to both classes. However, they are not the same field, since they are private to each class. Once you change the moveConductor in your base class to be public, you should see at least a warning in your derived class about a local field using the same name as an inherited field. I hope the compiler gives an error, but Iâm not sure without testing.
When you have an instance of your derived class, Unity will call the derived classâs Start but not the base classâs.š The field local to the derived class is set, but the field local to the base class is not. When Move is called, being in the base class, it only has access to the base classes version of moveConductor, which was not set.
š This is one of the shortcomings of the Magic Method approach Unity took on MonoBehaviours. You might not have run into this if it were possible to call base.Start() from the derived version ⌠though youâd be wastefully allocating two pointers to moveConductor, when only one seems to be used.
Initially youâve managed to confuse me and I gave you a wrong explanation.
I wasnât focused and didnât pick up on the nuances. Sorry if this mislead you.
In the end you got a proper explanation and reasons for your error.
Edit: Though I again managed to not see a field. I guess it was just a bad day for coding.
I couldâve sworn there wasnât a field in the second class. Oh well, bad days for everyone, in terms of cognition.
I had the benefit of AidanofVTâs solution. I could easily have missed the duplicate field too.
The important bit was engaging in discussion. Iâm sure Aidanof will agree, having a sounding board is extremely helpful.