+2 votes

Previously, I would set the node-specific custom_multiplayer with self.set_custom_multiplayer(multiplayerAPI) .

How is this done in Godot 4 though?

Godot version Godot 4.0 Alpha 7
in Engine by (19 points)

2 Answers

+1 vote
Best answer

I'm only just figuring it out myself, but this is how I believe it's done as of Beta 1.

3.x:

var network := NetworkedMultiplayerENet.new()
var gateway := SceneMultiplayer.new()

func start_server():
    network.create_server(1910, 100)
    set_custom_multiplayer(gateway)
    custom_multiplayer.set_root_node(self)
    custom_multiplayer.set_network_peer(network)

4.x:

var network := NetworkedMultiplayerENet.new()
var gateway := SceneMultiplayer.new()

func start_server():
    network.create_server(1910, 100)
    get_tree().set_multiplayer(gateway, self.get_path())
    multiplayer.set_multiplayer_peer(network)
by (340 points)
selected by

Thanks for pointing me in the right direction!

+2 votes

I had been having issues with the v4 Custom Multiplayer and not getting RPC functions to work, but BoxyLlama's answer below helped me out a bunch with the second critical parameter node path of the get_tree().set_multiplayer(gateway, self.get_path()) line.

To expand on this for everyone's benefit after I did a bunch of experimentation, here's what I've learned. The sample scenario below is separate client and server projects, however it should apply to combined projects as well. The server has an Autoload singleton called ServerCustom and the client has one called ClientCustom (notice they can be different node names). Start the server up first, then the client which should auto-connect, then call an RPC function that flows through the server and back to the client over several calls that should be self explanatory by name.

Server code (ServerCustom.gd):

extends Node

var server_custom = ENetMultiplayerPeer.new()
var multiplayer_api : MultiplayerAPI

var port = 8888
var max_peers = 5

func _ready():
    print("Custom Server _ready() Entered")

    server_custom.peer_connected.connect(_on_peer_connected)
    server_custom.peer_disconnected.connect(_on_peer_disconnected)
    multiplayer_api = MultiplayerAPI.create_default_interface()
    server_custom.create_server(port, max_peers)

    get_tree().set_multiplayer(multiplayer_api, ServerCustom.get_path())
    # can use "/root/ServerCustom" or self.get_path()
    multiplayer_api.multiplayer_peer = server_custom

func _process(_delta: float) -> void:
    if multiplayer_api.has_multiplayer_peer():
        multiplayer_api.poll()

func _on_peer_connected(peer_id):
    print("Custom Server _on_peer_connected, peer_id: {0}".format([peer_id]))
    await get_tree().create_timer(1).timeout
    print("Custom Peers: {0}".format([multiplayer.get_peers()]))

func _on_peer_disconnected(peer_id):
    print("Custom Server _on_peer_disconnected, peer_id: {0}".format([peer_id]))

@rpc(any_peer) 
func rpc_server_custom():
    var peer_id = multiplayer.get_remote_sender_id() # even custom uses default "multiplayer" calls
    print("rpc_server_custom, peer_id: {0}".format([peer_id]))
    rpc_server_custom_response(peer_id)

@rpc 
func rpc_server_custom_response(peer_id, test_var1 : String = "party like it's", test_var2 : int = 1999):
    print("rpc_server_custom_response to peer_id : {0}".format([peer_id]))
    rpc_server_custom_response.rpc_id(peer_id, test_var1, test_var2)

Client Code (ClientCustom.gd):

extends Node

var client_custom = ENetMultiplayerPeer.new()
var multiplayer_api : MultiplayerAPI

var address = "127.0.0.1"
var port = 8888

func _ready():
    print("Custom Client _ready() Entered")

    client_custom.connection_succeeded.connect(_on_connection_succeeded)
    client_custom.connection_failed.connect(_on_connection_failed)

    client_custom.create_client(address, port)
    multiplayer_api = MultiplayerAPI.create_default_interface()
    get_tree().set_multiplayer(multiplayer_api, ClientCustom.get_path()) 
    multiplayer_api.multiplayer_peer = client_custom

    print("Custom ClientUnique ID: {0}".format([multiplayer_api.get_unique_id()]))

func _process(_delta: float) -> void:
    if multiplayer_api.has_multiplayer_peer():
        multiplayer_api.poll()

func _on_connection_succeeded():
    print("Custom Client _on_connection_succeeded")
    await get_tree().create_timer(1).timeout
    print("Custom Peers: {0}".format([multiplayer.get_peers()]))
    rpc_server_custom()

func _on_connection_failed():
    print("Custom Client _on_connection_failed")


@rpc 
func rpc_server_custom():
    print("Custom Client rpc_server_custom")
    print("Custom Peers: {0}".format([multiplayer.get_peers()]))
    rpc_server_custom.rpc() # this works (NO MORE STRINGS!)

@rpc(authority) 
func rpc_server_custom_response(test_var1, test_var2):
    print("Custom Client rpc_server_custom_response: {0} {1}".format(
        [test_var1, test_var2]))

A few Notes:

  • For CUSTOM multiplayer, it does not appear that node name/paths must match but they do need to be non-default so you're not sharing RPC functions in the same path as default. My tester projects had both default multiplayer AND the custom multiplayer autoload singletons above, and both worked fine together and kept the correct peer IDs straight between who was calling what. The key was to keep all default RPC function definitions in a separate Node path as well as specify the path default multiplayer should use.
  • var scene_multiplayer = SceneMultiplayer.new()
    SceneMultiplayer is the default implementation class for MultiplayerAPI. The biggest difference is that you can't new() up the MultiplayerAPI, you must call the create_deafalt_interface() in the example, but either works with the above pattern if you switch it out for SceneMultiplayer instead
  • Notice that multiplayer.get_peers() and multiplayer.get_remote_sender_id() are the same even if you'r'e using custom or using both default and custom, it keeps them straight internally based on your server path definitions. If you try something like CustomServer.multiplayer_api.get_remote_sender_id() it always returns 0 even during RPC calls
  • For DEFAULT multiplayer, I first thought you had to have the full /root/Path/to/Node be exactly the same on both sides for all RPC functions to work, but that's not actually required as long as you set the get_tree().set_multiplayer(multiplayer, "/root/Path/to/Node") appropriately for where your default RPC functions will be defined.
by (18 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.