godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

[4.0] MultiplayerSpawner should support both static id based spawning and random spawning #4676

Open Shadowblitz16 opened 2 years ago

Shadowblitz16 commented 2 years ago

Describe the project you are working on

A arcade spaceship war game

Describe the problem or limitation you are having in your project

In my game spaceships spawn in different locations depending on what player it is. However I would like to use the spawner to spawn players manually on the server which also syncs them on the clients.

image Right now I am using 4 scenes with separately positioned player node for each network id. But I don't have any documentation on how it works and I am assuming the scene that is spawned is random based on the fact that there doesn't seem to be a way to spawn it based on network id.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I suggest instead of it taking a scene it takes a spawn parameter resource which allows us to supply a transform range and a scene to spawn per range.

I also suggest that the spawner automatically and manually selects the spawn parameter based on network id.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

spawn parameter resource would basicly be a...

@export scene        : PackedScene
@export min_position : Vector3
@export min_rotation : Vector3
@export min_scale    : Vector3 
@export max_position : Vector3
@export max_rotation : Vector3
@export max_scale    : Vector3 

the manual api for spawning things would be..

func spawn(id:int)->Node

If this enhancement will not be used often, can it be worked around with a few lines of script?

It would be used more then the current version.

Is there a reason why this should be core and not an add-on in the asset library?

Its part of core already.

Faless commented 1 year ago

But I don't have any documentation on how it works and I am assuming the scene that is spawned is random based on the fact that there doesn't seem to be a way to spawn it based on network id.

If you use automatic spawning, the spawned scene is the one you add to SceneTree from the authority. So if you configure the autospawn list as ["res://one.tscn", "res://two.tscn"], and assuming the spawner target (spawn_node) is $Objects, then:

var one : Node = load("res://one.tscn").instatiate()
$Objects.add_child(one) # Will spawn scene "one.tscn" on clients
var two : Node = load("res://two.tscn").instatiate()
$Objects.add_child(two) # Will spawn scene "two.tscn" on clients
var three : Node = load("res://three.tscn").instatiate()
$Objects.add_child(three) # Will not spawn anything remotely since "res://three.tscn" is not in the list.

I suggest instead of it taking a scene it takes a spawn parameter resource which allows us to supply a transform range and a scene to spawn per range.

Note that this kind of custom behaviors can already be achieved using custom spawning. You can implement MultiplayerSpawner._spawn_custom and then use MultiplayerSpawner.spawn(data) to spawn different scenes depending on the provided data.

There are multiple ways to implement what you want specifically. An approach I can suggest is something like:

extends MultiplayerSpawner

@export var min_position : Vector3
@export var min_rotation : Vector3
@export var min_scale    : Vector3 
@export var max_position : Vector3
@export var max_rotation : Vector3
@export var max_scale    : Vector3 

func _spawn_custom(data):
    if typeof(data) != TYPE_INT:
        return null # Invalid spawn requested.
    var id : int = data
    # Use id to instantiate the desired scene
    var node : Node = load("res://player_%d.tscn" % id).instantiate()
    # Apply initial values if we are the authority
    if is_multiplayer_authority():
        node.position = Vector3(randf(), randf(), randf()) * (max_position - min_position) + min_position
        node.rotation = Vector3(randf(), randf(), randf()) * (max_rotation - min_rotation) + min_rotation
        node.scale = Vector3(randf(), randf(), randf()) * (max_scale - min_scale) + min_scale
    return node

Then have a MultiplayerSynchronizer in the player scenes configured to replicate at least position, rotation, and scale on spawn (possibly also on sync depending on the game specifics).

You can finally spawn from the authority by calling:

if $PlayerSpawner.is_multiplayer_authority():
    $PlayerSpawner.spawn(player_id)

And this will also allow clients to connect mid-game (which will automatically spawn existing players with the proper transform) applied.