Godot Version
4.6
Question
Hello, I currently have a very basic 3D mutliplayer game: You can move around world, take a portal to move to the dungeon and there’s also a portal in dungeon you can take to move back to world.
Here’s the current setup from the view of the host (the server/host is always player 1):
At the start, someone hosts a game and we add World and Dungeon nodes via add_child() to the WorldSpawner which is a MultiplayerSpawner node. If other players now join this game, they will automatically receive these nodes. Note that each, World and Dungeon have their own MultiplayerSynchronizer nodes, where the public_visbility of World is turned on but for Dungeon its turned off. This will tell the WorldSpawner to only spawn World initially. So a player connects, he gets World but not Dungeon.
Now if a non-host player takes the portal, we simply toggle the visibility of World and Dungeon via their MultiplayerSynchronizer nodes. Note that this isn’t the same as toggling .visible property of a node but it tells the spawner which nodes to spawn or despawn for the non-host players. We can do this, because the players only need to care about the nodes in their world they currently are in. E.g. if they are in World, their client doesn’t have to run any simulation logic for e.g. the Dungeon. So this all works fine.
For the host, we have the problem, that we can’t just despawn or spawn the nodes we need. We always need to basically run everything all the time because the host is the main authority running everthign for everyone basically. So e.g. if the host player is in World, we still need to run the logic for the AI for the mobs in Dungeon. That’s why you can see both nodes, World and Dungeon in the screenshot above (it’s from the pov of the host).
Now this comes with a problem: Both nodes, World and Dungeon exist in the same World3D given by our root viewport root. Furthermore, these two scenes World and Dungeon overlap in the global coordinate system of the roots World3D.
Since we can’tdespawn or spawn nodes for the host but need all of them all the time we solve it by toggling the .vibisle property of the nodes. So if the host is in World, we hide Dungeon and vice versa. The problem with this is that since we are in the same World3D, we share the same physics space, so we get issues when it comes to collision etc.
And this is the main problem: We need to decouple the physics space between World and Dungeon. This isn’t a problem for the non-host players simply because we only run one of them at a time but for the host we run both simultaneously.
The question is, how do I solve this? I can think of three “solutions”.
- I simply add an offset of e.g.
y=10000(random value) to each node, so they are physically separated. The still share the same physics space but in theory, we shouldn’t run into collision issues. Unless we might have projectiles or some other funny things but you can see, this probably isn’t a nice solution. Depends a bit on the game. - We set collision masks/layer for each
WorldandDungeondifferently and just toggle it on the player, when we take the portal. I can imagine though, that this will get complicated but that’s a bit of a guess since I’m still a noob in Godot. - We might be able to use different
World3Dinstances for each node. From the docs we know that “A resource that holds all components of a 3D world, such as a visual scenario and a physics space.” but I’m not sure if I can actually use it like that. I guess this would involve usingSubViewportsbut again, I’m very unsure about this and the implications. I’m very interested in this solution though. Now assume I can actually use that, what would be the best way to do that? E.g. can I just run both worlds and only “attach” one to the viewport of the root or do I use subviewports? do I use 1 or 2? etc. Furthermore I’m not sure how I would structure this because as far as I know, I can only have oneSpawnPathfor myPlayerSpawnerbut if we move theWorldandDungeoninto their respective ownWorld3D’s, then I think the Player also needs to be a child of thatWorld3D, no?
I hope this all makes sense. Curious on whats the best approach on solving that. ![]()
Thanks in advance.
Edit: I thought about it and maybe I could do something like this:
root/
├── WorldSpawner (MultiplayerSpawner: spawn_path = ^"WorldContainer")
├── DungeonSpawner (MultiplayerSpawner: spawn_path = ^"DungeonContainer")
├── WorldContainer (Node3D)
│ └── WorldViewport (SubViewport: own_world_3d = true)
│ ├── WorldSynchronizer (MultiplayerSynchronizer)
│ ├── WorldScene (StaticBody3D, Environment, etc.)
│ └── [Players currently in World]
├── DungeonContainer (Node3D)
│ └── DungeonViewport (SubViewport: own_world_3d = true)
│ ├── DungeonSynchronizer (MultiplayerSynchronizer)
│ ├── DungeonScene (StaticBody3D, Environment, etc.)
│ └── [Players currently in Dungeon]
└── UI (CanvasLayer)
├── WorldDisplay (SubViewportContainer: visible = true/false)
│ └── [Remote Path to WorldViewport]
└── DungeonDisplay (SubViewportContainer: visible = true/false)
└── [Remote Path to DungeonViewport]
