Godot Version
Godot v4.6.0
Question
Ok so in order to explain ANYTHING about this problem I need to explain the setup I have going on here. (i am writing this at 12am after taking both of my sleep meds for the night so excuse my writing here aaaaaaa)
I’m making a sorta basic walking-sim game where each level is a single room. In order to load between each room, i have created a warp point system. Each warp is an Area2D as the trigger for the player, and a Node3D parented which tells the player where to teleport to. (the player never gets deleted)
The Area2D has a script connected to it called WarpPoint. This keeps track of its own warp ID for future reference, the map it should load after the player enters it, and the next warp ID for the player to be teleported at. It also has a reference to the Node3D used for locating where the player gets teleported to on load.
At the higher level, there is a root node for all of this that i like to call the Director. This keeps track of the next map that needs to be loaded, as well as the next warp ID to teleport the player to. When the player touches a WarpPoint, the warp sets the Director’s next level and next warp ID, then triggers the load function to start.
Each room has several of these WarpPoints located within them, and all of these warp points should eventually get interlinked into a network of paths between all of the various levels. When you touch a warp point, it defers loading to the Director as explained before.
The core idea for the level loading system is that the Director’s load function deletes the current map, loads the next map, fetched all the warp points in the new map using the engine’s group system, then teleports the player to the next warp’s spawn point using the next warp ID as a reference.
Upon initial testing between 3 levels, all of this worked. It was all done in a single day. I programmed this without any clever tricks, just pure “f it we ball” energy.
I have learned this was a bad move.
Here is the chunk of code from the Director responsible for the actual level loading magic, as i feel this is what’s responsible for my current headache.
public void LoadMap()
{
//delete current map
if (themap != null) themap.QueueFree();
//load map
PackedScene theNewMapPackedScene = (PackedScene)ResourceLoader.Load(nextMap);
themap = theNewMapPackedScene.Instantiate();
this.AddChild(themap);
//get list of all warps in new map
Node[] nodeWarpList = GetTree().GetNodesInGroup("warps").ToArray();
//hack job fix to filter out deleted warps (doesnt warp)
int actualwarpcount = 0;
for (int i = 0; i < nodeWarpList.Length; i++)
{
if (!nodeWarpList[i].IsQueuedForDeletion()) actualwarpcount++;
}
warpList = new WarpPoint[actualwarpcount];
IDList = new int[actualwarpcount];
GD.Print("There are " + warpList.Length + " new warp points.");
for (int i = 0; i < nodeWarpList.Length; i++)
{
if (!nodeWarpList[i].IsQueuedForDeletion())
{
WarpPoint bufferWarp = nodeWarpList[i] as WarpPoint;
warpList[i] = bufferWarp;
IDList[i] = bufferWarp.ID;
}
}
//set player at correct warp point
if (theplayer == null)
{
theplayer = playerPrefab.Instantiate<CharacterBody3D>() as Player;
this.AddChild(theplayer);
}
int theWarpToPickFromTheArray = IDList.IndexOf(nextEntranceID); //replace this with a dictionary or figure out how to actually sort the got dang warp array
GD.Print("Next warp is ID " + warpList[theWarpToPickFromTheArray].ID);
GD.Print(warpList[theWarpToPickFromTheArray].playerSpawn.GlobalPosition.X + ", " +
warpList[theWarpToPickFromTheArray].playerSpawn.GlobalPosition.Y + ", " +
warpList[theWarpToPickFromTheArray].playerSpawn.GlobalPosition.Z);
theplayer.Position = warpList[theWarpToPickFromTheArray].playerSpawn.GlobalPosition;
GD.Print(theplayer.Position.X + ", " +
theplayer.Position.Y + ", " +
theplayer.Position.Z);
theplayer.SetFacingDirection(warpList[theWarpToPickFromTheArray].playerSpawn.GlobalRotationDegrees);
theplayer.SetNewVelocity(Vector3.Zero);
}
Here is the problem I am currently having.
Upon adding a new 4th level, I discovered that this code doesn’t work in every circumstance. When loading maps from the 4th level to the 3rd level, it appears that the warp fetcher grabs all the warps from both maps, then sets the spawn based on the old map’s warps. Furthermore, that small chunk of code meant to account for warps that are queued for deletion just seems to be ignored as it does not solve the problem. This results in the player getting teleported out of bounds. Which is bad.
I also tried fixing this by calling Free() on all the current warps before unloading the current map. This appears to instead delete all of the warps for the new map. But the new warps still appear in the remote scene hierarchy. but the player can’t interact with these warps as they throw a “Can’t interact with objects queued for deletion” error.
I ALSO tried fixing this by just calling Free() on the old map instead of QueueFree() to try getting rid of everything immediately. This crashes the game entirely.
I do not know what is going on here. I have never seen behavior like this from godot before. My usual debugging tactics only seem to make the behavior worse and I am kind of scared to touch this anymore. I may be doing things horribly wrong. Any suggestions on what to do are highly appreciated.
(I am aware that fetching nodes based on tags fetches them in a somewhat random order, but I don’t know how to sort them. I am also aware I could be using a Dictionary in there somewhere, but I was too tired to properly restructure the code for that since I rarely use Dictionaries.)