Running multiple viewports and switching which one is active?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By shianiawhite

I want to be able to run a co-operative network multiplayer (not MMO) where each player can move around to different “rooms” of the game each being a scene. I want to have one player hosting the server which will take decide all the actions of things going on in each room/scene. I came across this question which seems to be doing something similar. However, the details about the answer at this question are not very specific. I’m under the impression that I should have the server create a viewport for each room/scene independently being used by a player, so that all of those scenes can be processed at the same time by the server. From here, the server needs to display the correct viewport to the player who’s playing on the server instance. To actually implement this, I plan to use a root scene which will have a list of viewports in it. As players move to another room/scene, the server will load up that new resource, create a new viewport, and stick the scene in that viewport. Finally, it will remove any viewports for scenes that no players are in.

Firstly, does this seem like a reasonable way to approach this problem? Next, I’ve tried a simple test of a viewport in a root scene, and the output clearly shows some print statements from the child viewport scene running correctly. However, I’m not sure how to then display this child viewport to the server user. Here’s what this simple test root node looks like.

extends Node

func _ready():
    var viewport = Viewport.new()
    viewport.world = World2D.new()
    add_child(viewport)
    var a_room = load('res://test.tscn').instance()
    viewport.add_child(a_room)

Finally, if this is the approach I end up using, are there any significant problems to look out for using this method? For example, I could see trouble arising from the fact that the viewport the server user is viewing is always a sub-viewport rather than the main one. Might this cause a problem? And should it be done some other way to prevent such problems? Thank you for your time!

:bust_in_silhouette: Reply From: shianiawhite

I figured out a way. There is an important gotcha to pay attention to (though it’s not a problem as long as you keep it in mind). There may be other gotchas I don’t know about, or problems that may arise that I don’t know about yet, but this seems to work. Below is a minimal working example where two scenes are running in parallel with only one showing at a time (but can switch between them). Basically, for each independent scene/room, you need to create a ViewportContainer, Viewport, and World2D. You need to make sure all the view stuff matches the size of the root viewport. Then whichever ViewportContainer is a child of the root viewport will be displayed. The one major gotcha is that the second scene (the one that isn’t displayed to start with) needs to have it’s viewport added as a child to the root node. This is because it’s _ready() code won’t be run until it enters the tree (by becoming a child). So adding the Viewport outside a ViewportContainer means it will run _ready() (and so start processing), but it won’t be displayed.

extends Node


var container1
var container2
var subviewport1
var subviewport2
var timer = 0
var change = false


func _ready():
    container1 = ViewportContainer.new()
    container1.rect_size = get_viewport().size
    container1.stretch = true
    subviewport1 = Viewport.new()
    subviewport1.world = World2D.new()
    subviewport1.size = container1.rect_size
    var a_room1 = load('res://test1.tscn').instance()
    subviewport1.add_child(a_room1)
    container1.add_child(subviewport1)
    
    container2 = ViewportContainer.new()
    container2.rect_size = get_viewport().size
    container2.stretch = true
    subviewport2 = Viewport.new()
    subviewport2.world = World2D.new()
    subviewport2.size = container2.rect_size
    var a_room2 = load('res://test2.tscn').instance()
    subviewport2.add_child(a_room2)
    add_child(subviewport2)

    add_child(container1)
    

func _process(delta):
    timer += delta
    if timer > 5 and not change:
        remove_child(container1)
        remove_child(subviewport2)
        container2.add_child(subviewport2)
        add_child(container2)
        change = true
        print('change!')

Of course, there might be a cleaner way to do this, or possibly other problems with this method, but it doesn’t seem too bad at the moment.