[MP] Convert _spawn_custom to a Callable property.

Renamed to "spawn_function".
Allow both custom spawn and auto spawn list to co-exist.

This makes it possible to implement custom spawn without being forced to
attach a script to MultiplayerSpawner directly.
This commit is contained in:
Fabio Alessandrelli 2023-01-05 08:44:01 +01:00
parent f5f7d11ac4
commit 566c48f193
3 changed files with 23 additions and 28 deletions

View file

@ -5,20 +5,12 @@
</brief_description> </brief_description>
<description> <description>
Spawnable scenes can be configured in the editor or through code (see [method add_spawnable_scene]). Spawnable scenes can be configured in the editor or through code (see [method add_spawnable_scene]).
Also supports custom node spawns through [method spawn], calling [method _spawn_custom] on all peers. Also supports custom node spawns through [method spawn], calling [member spawn_function] on all peers.
Internally, [MultiplayerSpawner] uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way. Internally, [MultiplayerSpawner] uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way.
</description> </description>
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="_spawn_custom" qualifiers="virtual">
<return type="Node" />
<param index="0" name="data" type="Variant" />
<description>
Method called on all peers when a custom spawn was requested by the authority using [method spawn]. Should return a [Node] that is not in the scene tree.
[b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
</description>
</method>
<method name="add_spawnable_scene"> <method name="add_spawnable_scene">
<return type="void" /> <return type="void" />
<param index="0" name="path" type="String" /> <param index="0" name="path" type="String" />
@ -49,12 +41,16 @@
<return type="Node" /> <return type="Node" />
<param index="0" name="data" type="Variant" default="null" /> <param index="0" name="data" type="Variant" default="null" />
<description> <description>
Requests a custom spawn, with [code]data[/code] passed to [method _spawn_custom] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path]. Requests a custom spawn, with [code]data[/code] passed to [member spawn_function] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path].
[b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns. [b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns.
</description> </description>
</method> </method>
</methods> </methods>
<members> <members>
<member name="spawn_function" type="Callable" setter="set_spawn_function" getter="get_spawn_function">
Method called on all peers when for every custom [method spawn] requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree.
[b]Note:[/b] The returned node should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
</member>
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0"> <member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
When set to [code]0[/code] (the default), there is no limit. When set to [code]0[/code] (the default), there is no limit.

View file

@ -93,13 +93,6 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
if (spawn_path.is_empty() || !has_node(spawn_path)) { if (spawn_path.is_empty() || !has_node(spawn_path)) {
warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes.")); warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes."));
} }
bool has_scenes = get_spawnable_scene_count() > 0;
// Can't check if method is overridden in placeholder scripts.
bool has_placeholder_script = get_script_instance() && get_script_instance()->is_placeholder();
if (!has_scenes && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom) && !has_placeholder_script) {
warnings.push_back(RTR("A list of PackedScenes must be set in the \"Auto Spawn List\" property in order for MultiplayerSpawner to automatically spawn them remotely when added as child of \"spawn_path\"."));
warnings.push_back(RTR("Alternatively, a Script implementing the function \"_spawn_custom\" must be set for this MultiplayerSpawner, and \"spawn\" must be called explicitly in code."));
}
return warnings; return warnings;
} }
@ -162,7 +155,9 @@ void MultiplayerSpawner::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit);
ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit");
GDVIRTUAL_BIND(_spawn_custom, "data"); ClassDB::bind_method(D_METHOD("get_spawn_function"), &MultiplayerSpawner::get_spawn_function);
ClassDB::bind_method(D_METHOD("set_spawn_function", "spawn_function"), &MultiplayerSpawner::set_spawn_function);
ADD_PROPERTY(PropertyInfo(Variant::CALLABLE, "spawn_function", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_spawn_function", "get_spawn_function");
ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
@ -183,7 +178,7 @@ void MultiplayerSpawner::_update_spawn_node() {
Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path); Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path);
if (node) { if (node) {
spawn_node = node->get_instance_id(); spawn_node = node->get_instance_id();
if (get_spawnable_scene_count() && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom)) { if (get_spawnable_scene_count()) {
node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
} }
} else { } else {
@ -298,23 +293,26 @@ Node *MultiplayerSpawner::instantiate_scene(int p_id) {
Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) { Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) {
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
Node *node = nullptr; ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires a valid 'spawn_function'.");
if (GDVIRTUAL_CALL(_spawn_custom, p_data, node)) { const Variant *argv[1] = { &p_data };
return node; Variant ret;
} Callable::CallError ce;
ERR_FAIL_V_MSG(nullptr, "Method '_spawn_custom' is not implemented on this peer."); spawn_function.callp(argv, 1, ret, ce);
ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, nullptr, "Failed to call spawn function.");
ERR_FAIL_COND_V_MSG(ret.get_type() != Variant::OBJECT, nullptr, "The spawn function must return a Node.");
return Object::cast_to<Node>(ret.operator Object *());
} }
Node *MultiplayerSpawner::spawn(const Variant &p_data) { Node *MultiplayerSpawner::spawn(const Variant &p_data) {
ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr); ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr);
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script."); ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires the 'spawn_function' property to be a valid callable.");
Node *parent = get_spawn_node(); Node *parent = get_spawn_node();
ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node."); ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node.");
Node *node = instantiate_custom(p_data); Node *node = instantiate_custom(p_data);
ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node."); ERR_FAIL_COND_V_MSG(!node, nullptr, "The 'spawn_function' callable must return a valid node.");
_track(node, p_data); _track(node, p_data);
parent->add_child(node, true); parent->add_child(node, true);

View file

@ -71,6 +71,7 @@ private:
ObjectID spawn_node; ObjectID spawn_node;
HashMap<ObjectID, SpawnInfo> tracked_nodes; HashMap<ObjectID, SpawnInfo> tracked_nodes;
uint32_t spawn_limit = 0; uint32_t spawn_limit = 0;
Callable spawn_function;
void _update_spawn_node(); void _update_spawn_node();
void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID); void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID);
@ -106,6 +107,8 @@ public:
void set_spawn_path(const NodePath &p_path); void set_spawn_path(const NodePath &p_path);
uint32_t get_spawn_limit() const { return spawn_limit; } uint32_t get_spawn_limit() const { return spawn_limit; }
void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; } void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; }
void set_spawn_function(Callable p_spawn_function) { spawn_function = p_spawn_function; }
Callable get_spawn_function() const { return spawn_function; }
const Variant get_spawn_argument(const ObjectID &p_id) const; const Variant get_spawn_argument(const ObjectID &p_id) const;
int find_spawnable_scene_index_from_object(const ObjectID &p_id) const; int find_spawnable_scene_index_from_object(const ObjectID &p_id) const;
@ -114,8 +117,6 @@ public:
Node *instantiate_custom(const Variant &p_data); Node *instantiate_custom(const Variant &p_data);
Node *instantiate_scene(int p_idx); Node *instantiate_scene(int p_idx);
GDVIRTUAL1R(Node *, _spawn_custom, const Variant &);
MultiplayerSpawner() {} MultiplayerSpawner() {}
}; };