Online Multiplayer - Synchronizing functions such as _on_timer_timeout

Godot Version

4.2.1

Question

Hello all,

I’m working on an online multiplayer game. For now, it’s peer to peer, but I’ll likely make a dedicated server in the future. This is my first online game so my networking knowledge is beginner to low-end intermediate at best.

As of now, the player and level nodes are synchronized with MultiplayerSynchronizer nodes, and the game is playable on multiple devices

However, I haven’t quite figured out how to synchronize spawning a random powerup. My level scene uses an _on_timer_timeout function to randomly select between a few powerups and spawn one of them. The MultiplayerSynchronizer node doesn’t seem to allow you to select _on_timer_timeout for synchronization since it’s a signal and not a property

Since both the host and the peer are running the level scene at the same time
they end up spawning their own random powerup which of course isn’t always the same on host and peer.

I tried resolving by putting the following at the top of the _on_timer_timeout func but of course then the peer doesn’t have a powerup spawn at all for them:
if not multiplayer.is_server():
return

There must be a way to achieve this that I’m overlooking, whether that’s by using a workaround with the func, something with a MultiplayerSynchronizer node, or by adding random powerups another way.

Please let me know if you have a solution for this. Thanks!

Have you tried to set the same seed on every peer?

seed(12345)

Circling back to this, I found a solution for synchronizing randomly spawning a powerup between server and client.

Here’s code (yes I could’ve made use of functions to simply this):

extends Node


@onready var invincible = preload('res://powerups/star.tscn')
@onready var pencil = preload('res://powerups/penssile_powerup.tscn')
@onready var ball = preload('res://powerups/ball_powerup.tscn')
@onready var reverse = preload('res://powerups/reverse_gravity.tscn')
@onready var speed_scene = preload('res://powerups/speed.tscn')
@onready var spike = preload('res://powerups/spike_hat.tscn')

func _on_star_timer_timeout():
	if Globals.shrink == false and Globals.powerup == false:
		#get random value
		var random_value = [1,2,3,4,5,6].pick_random()
		
		#call function to spawn powerup on clients
		rpc('spawn_powerup', random_value)
		
		#ensures same powerup spawns on host too
		if random_value == 1:
			var item = invincible.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if random_value == 2:
			var item = pencil.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if random_value == 3:
			var item = ball.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if random_value == 4:
			var item = reverse.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if random_value == 5:
			var item = speed_scene.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if random_value == 6:
			var item = spike.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
	
#powerup spawn on clients
@rpc('call_remote')
func spawn_powerup(powerup_value):
	if Globals.shrink == false and Globals.powerup == false:
		#var random_value = [1,2,3,4,5,6].pick_random()
		if powerup_value == 1:
			var item = invincible.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if powerup_value == 2:
			var item = pencil.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if powerup_value == 3:
			var item = ball.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if powerup_value == 4:
			var item = reverse.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if powerup_value == 5:
			var item = speed_scene.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)
		if powerup_value == 6:
			var item = spike.instantiate()
			item.position = $Marker2D4.position
			get_node('Shrink').add_child(item)