List Reference Question--References create a new list?

So I have a base class with a list reference and adds things and removes things from it, and two different child classes with initialize methods that set the list reference to different lists on another gameobject. However, it never actually sets it as the other list, just equal to, sort of. Meaning as i edit the reference, the original is never touched. I really don’t want to have to put all the logic into the child classes, it’s much neater with this set up, but is it possible to create a reference to another list, like to a gameobject, without creating a new one?

I mean lists are a reference type, so yes you can pass around a reference to the same list. You’d have to show an example of your code for us to really be able to tell what’s going wrong.

I guess be aware of whether your lists are potentially being serialized, as that will break any by-reference stuff going on. You may have to ensure the collections are being left out of Unity ‘soft serialization’ as well with [System.NonSerialized].

This is the base class.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AIMovementSOBase : ScriptableObject
{
    protected ThinkerCreature thinker;
    protected GameObject gameObject;
    protected Transform transform;
    protected List<GameObject> steeringReference;
    public virtual void Initialize(ThinkerCreature thinkerCreature)
    {
        thinker = thinkerCreature;
        gameObject = thinkerCreature.gameObject;
        transform = gameObject.transform;
    }
    public virtual void EnterState() 
    {
        if (thinker.visibleEnemies.Count == 0)
        {
            thinker.stateMachine.ChangeState(thinker.pursueState);
        }
        else
        {
            thinker.currentTarget = thinker.visibleEnemies[0];
            Debug.Log(ListManagement.Add(steeringReference, thinker.currentTarget.gameObject));
            Think();
        }
    }
    public virtual void ExitState() 
    {
        ListManagement.Remove(steeringReference, thinker.currentTarget.gameObject);
        ResetValues();
    }
    public virtual void FrameUpdate() 
    {
        if (thinker.visibleEnemies.Count == 0) thinker.stateMachine.ChangeState(thinker.pursueState);
    }
    public virtual void PhysicsUpdate() { }
    public virtual void Think() 
    {
        if (thinker.visibleEnemies.Count == 0)
        {
            thinker.stateMachine.ChangeState(thinker.pursueState);
            return;
        }
        if (thinker.currentTarget != thinker.visibleEnemies[0])
        {
            ListManagement.Remove(steeringReference, thinker.currentTarget?.gameObject);
            thinker.currentTarget = thinker.visibleEnemies[0];
            thinker.visionTarget = thinker.currentTarget.Leader.gameObject;
            ListManagement.Add(steeringReference, thinker.currentTarget?.gameObject);
        }
    }
    public virtual void AnimationTriggerEvent(StateMachineAnimationTrigger triggerType) { }
    public virtual void ResetValues() { }
}

The only relevant code of the children are the following

    public override void Initialize(ThinkerCreature thinkerCreature)
    {
        base.Initialize(thinkerCreature);
        steeringReference = thinker.steeringAvoiding;
    }

and

    public override void Initialize(ThinkerCreature thinkerCreature)
    {
        base.Initialize(thinkerCreature);
        steeringReference = thinker.steeringPursuing;
    }

Hazarding a guess, this might be the soft-serialization issue I mentioned before. Unity will re-serialize even fields not specifically set to be serialized if the values can be serialized. This will most often show up if you have these selected in the inspector.

So I would try applying [System.NonSerialized] to your steeringReference collection.

This could also be happening from where the collection is sourced from, this ThinkerCreature. If the respective assets these collections are in are ever reserialized, then your references will break.

1 Like

Such as [System.NonSerialized] protected List<GameObject> steeringReference; ? I put this in and it’s having the same problems. I made debugs for the count of steeringReference and the count of thinker.steeringPursuing and it comes as different numbers.

I’m not sure what reserializing is but the script contains
public List<GameObject> steeringAvoiding = new List<GameObject>();
and
public List<GameObject> steeringPursuing = new List<GameObject>();

Basically at certain points, Unity will take the instance in memory, serialize it, and then deserialize it. Most common of which is when its being viewed in the inspector, which will reserialize the asset every repaint of said inspector.

But it can also happen at other times.

Do you intend for these collections to be serialized and edited via the inspector?

no they’re edited by other scripts like the ones i’ve shown.

also thanks a bunch for the help

1 Like

Then you probably want to throw [System.NonSerialized] onto them as well.

Alternatively, if they aren’t intended to be edited via the inspector, declaring them with readonly will also work.

If that then doesn’t work, then as best as I can see, that may be down to a bug in your code.

I tried [System.NonSerialized] on them and it didn’t change the result. I tried readonly but it caused an error somewhere else in the script.

All I can provide is I have

            steeringReference.Add(thinker.currentTarget.gameObject);
            Debug.Log(steeringReference.Count);
            Debug.Log(thinker.steeringPursuing.Count);

and the first prints 2 and the second 0, indicating that they’re not the same. I don’t know where else a problem could arise. I’ve displayed everything that interacts with this system.

Oh well. I’ll just take apart the logic and put it in each child class.

Uh, that would only happen it you’re trying to reassign the collection, so wouldn’t that imply that somewhere else in the script, you’re reassigning the collection reference? Thus having a reference to a different collection to the one you assigned to steeringReference?

well idk hahahaah here’s where the error occured:

steeringAvoiding = steeringAvoiding.Where(x => x != null).ToList();
steeringPursuing = steeringPursuing.Where(x => x != null).ToList();

Well there’s your problem, and exactly what I said. That’s creating a whole new list, which will break the reference to the one you assign to your scriptable object.

You would want to get the return on .Where, then clear your collection, before adding the filtered result:

var filtered = steeringAvoiding.Where(x => x != null);
steeringAvoiding.Clear();
steeringAvoiding.AddRange(filtered);

Or you can just make a general extension method that clears null entries from a collection. Ideally you should avoid linq where you can for regularly called runtime code.

Probably something like this:

public static class UnityObjectCollectionExtension
{
	public static void ClearNull<T>(this List<T> uObjects) where T : UnityEngine.Object
	{
		int count = uObjects.Count;
		for (int i = count - 1; i >= 0; i--)
		{
			var obj = uObjects[i];
			if (obj == null)
			{
				uObjects.RemoveAt(i);
			}
		}
	}
}
1 Like

Ah, yep, that did it :sweat_smile: I did have to add .ToList() on the line where filtered is declared but it all works perfectly now. Thanks so much! I honestly posted on here expecting to never get a response as a last-resort but you helped me solve this within 24 hours which is crazy! You rock :grin:

I’ll go learn more about serialization today :upside_down_face: :melting_face:

Probably better to just

steeringAvoiding.RemoveAll(x => x == null);
1 Like