Camera Loses Tracking When Reloading Scene: Object Disposed Exception

Godot Version

v4.2.2.stable.mono.official [15073afe3]

Question

I’m trying to figure out a good way to set up my Camera2D. Right now, I’ve got the camera as its own scene called Camera, which is autoloaded. It’s meant to find and follow my player using this code:

using Godot;

public partial class CameraManager : Camera2D {
	
	public static CameraManager cameraManager;
	Node2D player;

	public CameraManager() {
		cameraManager = this;
	}

	public override void _Ready() {
		ProcessMode = ProcessModeEnum.Always;
		FindPlayer();
	}

	// Get a reference to the Player node
	public void FindPlayer() {
		if (player is null) {
			player = GetTree().Root.FindChild("Player", true, false) as Node2D;
		}
	}

	// Move towards the player's position every frame
	public override void _Process(double delta) {
		Position = player.Position;
    }
}

This works surprisingly well — the camera still does smoothing and adheres to its boundary limits — except for one problem: reloading the scene. When I call GetTree().ReloadCurrentScene(), the camera functionality breaks.

E 0:00:02:0887 GodotObject.base.cs:73 @ nint Godot.GodotObject.GetPtr(Godot.GodotObject): System.ObjectDisposedException: Cannot access a disposed object.
Object name: ‘CharacterMovement’.
<C# Error> System.ObjectDisposedException
<C# Source> /root/godot/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs:73 @ nint Godot.GodotObject.GetPtr(Godot.GodotObject)
GodotObject.base.cs:73 @ nint Godot.GodotObject.GetPtr(Godot.GodotObject)
Node2D.cs:259 @ Godot.Vector2 Godot.Node2D.GetPosition()
Node2D.cs:21 @ Godot.Vector2 Godot.Node2D.get_Position()
CameraManager.cs:25 @ void CameraManager._Process(double)
Node.cs:2131 @ bool Godot.Node.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CanvasItem.cs:1370 @ bool Godot.CanvasItem.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
Node2D.cs:522 @ bool Godot.Node2D.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
Camera2D.cs:908 @ bool Godot.Camera2D.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CameraManager_ScriptMethods.generated.cs:58 @ bool CameraManager.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

It says the CharacterMovement object is missing. That’s a script attached to my Player character. Calling FindPlayer again after reloading the scene didn’t fix this problem. I’ve also tried this with and without the ProcessMode = ProcessModeEnum.Always line. After reloading, I can still control the Player character, but my camera just stays its final position before the reload, like it lost its target to track.

How do you code camera tracking around scene reloads?

I’m just taking a wild stab in the dark with this one. You could temporarily removing the if statement from FindPlayer so it’s just the player = ... line and see if that helps.

Ready is used only one time.
Problem is in
Position = player.Position;
You needed check if player is valid here.
On your reload scene Ready is not called again.
You can add camera to player script. And skip search part with will benefit you too.

Remember to use IsInstanceValid, don’t check for null, because “not null” still can be not valid.

Thank you both for the replies. I’ve tried octopuddle’s suggestion: removing the if clause, so that player is assigned after the level is reloaded, no matter what. FindPlayer is called immediately following the level reload, but still, it didn’t work.

I also appreciate Moerus’ advice with IsInstanceValid, but that didn’t fix it either. I’m still stumped on this one. How do people normally handle reloading scenes with camera tracking?

Edited:

Most of my post was actually missing, must have accidentally deleted it before posting it. I’ll try to redo it when I have time.

You could add a GD.Print(player) after the player = ... in FindPlayer which should print out what the reference is. This should look something like

Player:<CharacterBody2D#29225911538>

Then on each reload should either be the same with a different number at the end, or possibly something like

object<null>

I kind of feel though like this might not be the problem, and it’s some other interaction between the scripts elsewhere. Could be wrong though.

To answer your question, I think people normally don’t use an autoload for the camera, and it’s a part of the scene somewhere (for example in a player scene), so it just gets reset on any scene load/reload. That seems to be the general way in any tutorial I’ve seen.

1 Like

in player script in enter tree make:
CameraManager.cameraManager.player = this;
optional on exit tree
CameraManager.cameraManager.player = null;
in CameraManager upi needed make public Node2D player; or have setter getter.
properties are best in most cases

private Node2D player;

public Node2D Player { get => player; set => player = value; }

then
CameraManager.cameraManager.Player = this;
optional on exit tree
CameraManager.cameraManager.Player = null;

Other possibility is you can to initiate your player in autoloader too but in map you will needed make spawn points for set player start position. Spawn points can be good if there’s connections between maps and you can travel between maps.

In Node2D player; instead Node2D you can use player class.

1 Like

I’ve tinkered with this more, based on the advice from you two. I simplified the properties and getter and setter, based on your example, for testing purposes. Here’s my current iteration:

using Godot;

public partial class CameraManager : Camera2D {
	public static CameraManager cameraManager;
	public Node2D player { get; set; }
	
	public CameraManager() {
		cameraManager = this;
	}

	public override void _Ready() {
        ProcessMode = ProcessModeEnum.Always;
		FindPlayer();
	}

    public override void _Process(double delta) {
		Position = player.Position;
    }
}

My Player’s script now includes this code:

    public override void _EnterTree() {
        CameraManager.cameraManager.player = this;
    }

It used to include this code too, but I deleted it:

    public override void _ExitTree() {
        CameraManager.cameraManager.player = null;
    }

The code seems to work the same whether I use _EnterTree or _Ready. However, _ExitTree gives me a minor error upon reload:

E 0:00:03:0988 void CameraManager._Process(double): System.NullReferenceException: Object reference not set to an instance of an object.
<C# Error> System.NullReferenceException
<C# Source> CameraManager.cs:51 @ void CameraManager._Process(double)
CameraManager.cs:51 @ void CameraManager._Process(double)
Node.cs:2131 @ bool Godot.Node.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CanvasItem.cs:1370 @ bool Godot.CanvasItem.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
Node2D.cs:522 @ bool Godot.Node2D.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
Camera2D.cs:908 @ bool Godot.Camera2D.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CameraManager_ScriptMethods.generated.cs:58 @ bool CameraManager.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

The error is referring to this function from the CameraManager:

    public override void _Process(double delta) {
		Position = player.Position;
    }

It throws an error one time per reload, but the game still works. I also tried connecting signals to tree_exiting() and tree_exited(), and setting null from there; they still produced that error. By removing the CameraManager.cameraManager.player = null; line altogether, the game still seems to work just fine and the error goes away.

I will have to revisit the way to camera picks a target as my game becomes more complex (as I will likely want to focus on things sometimes that aren’t the player), but for now, this is doing the job.

Thanks again for your help!

Just make bullet prof with

public override void _Process(double delta)
{
    if (IsInstanceValid(player);
    Position = player.Position;
}

or

public override void _Process(double delta)
{
    if (!IsInstanceValid(player);
    return;
    Position = player.Position;
}

when you gonna have scene like main menu you can miss player node too

alternative when you don’t want extra if

    public override void _EnterTree() {
        CameraManager.cameraManager.player = this;
        CameraManager.cameraManager.SetProcess(true);
    }


    public override void _ExitTree() {
        CameraManager.cameraManager.SetProcess(false);
        CameraManager.cameraManager.player = null;
    }

public override void _Process(double delta) will only process when player enter

or use player setter, Properties use capital first letter (!)

private Node2D player;

public Node2D Player
{
    get => player;
    set
    {
        player = value;
        SetProcess(IsInstanceValid(player)); // instance valid set process true;
    }
}
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.