diff --git a/addons/netfox/icons/scene-spawner.svg b/addons/netfox/icons/scene-spawner.svg new file mode 100644 index 00000000..3dce235b --- /dev/null +++ b/addons/netfox/icons/scene-spawner.svg @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/addons/netfox/icons/scene-spawner.svg.import b/addons/netfox/icons/scene-spawner.svg.import new file mode 100644 index 00000000..5f0a1bb5 --- /dev/null +++ b/addons/netfox/icons/scene-spawner.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dukty4wrjaxj4" +path="res://.godot/imported/scene-spawner.svg-f967e6043fd5d663cd896e9f7b925df9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/netfox/icons/scene-spawner.svg" +dest_files=["res://.godot/imported/scene-spawner.svg-f967e6043fd5d663cd896e9f7b925df9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/netfox/netfox.gd b/addons/netfox/netfox.gd index 0c090552..996d99fe 100644 --- a/addons/netfox/netfox.gd +++ b/addons/netfox/netfox.gd @@ -145,7 +145,11 @@ const AUTOLOADS: Array[Dictionary] = [ { "name": "NetworkPerformance", "path": ROOT + "/network-performance.gd" - } + }, + { + "name": "NetworkSceneSpawner", + "path": ROOT + "/network-scene-spawner.gd" + }, ] const TYPES: Array[Dictionary] = [ @@ -173,6 +177,12 @@ const TYPES: Array[Dictionary] = [ "script": ROOT + "/rewindable-action.gd", "icon": ROOT + "/icons/rewindable-action.svg" }, + { + "name": "SceneSpawner", + "base": "Node", + "script": ROOT + "/scene-spawner.gd", + "icon": ROOT + "/icons/scene-spawner.svg" + }, ] func _enter_tree(): diff --git a/addons/netfox/network-scene-spawner.gd b/addons/netfox/network-scene-spawner.gd new file mode 100644 index 00000000..56de35fe --- /dev/null +++ b/addons/netfox/network-scene-spawner.gd @@ -0,0 +1,63 @@ +extends Node +class_name _NetworkSceneSpawner + +## Singleton that spawns/despawns scenes with [SceneSpawner] helper node on clients. + +# Preloaded scenes can be passed to avoid loading scenes on runtime. +# See [method _NetworkSceneSpawner.set_preloaded_scenes]. +var _preloaded_scenes : Dictionary[String, PackedScene] = {} + +# Netfox logger. +static var _logger: _NetfoxLogger = _NetfoxLogger.for_netfox("NetworkSceneSpawner") + +## Set preloaded scenes with given param scenes. +func set_preloaded_scenes(scenes : Array[PackedScene]) -> void: + _preloaded_scenes.clear() + for packed_scene in scenes: + _preloaded_scenes[packed_scene.resource_path] = packed_scene + +@rpc("authority", "call_remote", "reliable") +func _spawn(scene_path : String, absolute_node_path : String, data : Dictionary) -> void: + + var scene : PackedScene = null + if _preloaded_scenes.has(scene_path): + scene = _preloaded_scenes.get(scene_path) + else: + scene = load(scene_path) as PackedScene + + if not scene: + _logger.error("Cant load scene from received path: %s" %scene_path) + return + + var spawned_node := scene.instantiate() as Node + + if not spawned_node: + _logger.error("Cant instantiate scene from received path: %s" %scene_path) + return + + var parent_node := get_node(absolute_node_path) + + if not parent_node: + _logger.error("Cant find parent node from received path: %s" %absolute_node_path) + return + + parent_node.add_child(spawned_node, true) + + for key in data.keys(): + if key is String: + spawned_node.set_indexed(key, data[key]) + else: + _logger.warning("Received non string property name in data dictionary.") + continue + + _logger.info("Instantiated scene %s" %spawned_node.name) + +@rpc("authority", "call_remote", "reliable") +func _despawn(absolute_node_path : String) -> void: + var node := get_node(absolute_node_path) + if not node: + _logger.error("Cant fetch node with path %s to despawn" %absolute_node_path) + return + + _logger.info("Erased scene %s" %node.name) + node.queue_free() diff --git a/addons/netfox/network-scene-spawner.gd.uid b/addons/netfox/network-scene-spawner.gd.uid new file mode 100644 index 00000000..ab85b5a8 --- /dev/null +++ b/addons/netfox/network-scene-spawner.gd.uid @@ -0,0 +1 @@ +uid://b2ln64fq5quv5 diff --git a/addons/netfox/scene-spawner.gd b/addons/netfox/scene-spawner.gd new file mode 100644 index 00000000..95a3acb8 --- /dev/null +++ b/addons/netfox/scene-spawner.gd @@ -0,0 +1,85 @@ +extends Node +class_name SceneSpawner + +## Spawns/Despawns new scenes with the latest properties over network. +## +## This node is designed to use only on the host. + +## The root node, is used to fetch properties and ensure root is ready. +@export var root : Node = null + +## Properties to record and use on spawn. +@export var properties: Array[String] + +## If true, [SceneSpawner] will spawn this scene on all peers with properties fetched. +## Keep in mind that to fetch properties, [SceneSpawner] waits root nodes ready if not. +@export var replicate_on_spawn : bool = false + +## If true, [SceneSpawner] will despawn this scene on all peers. +## If replicate_on_spawn is false, setting this to true will still despawn on +## peers which have remote spawned node. +@export var replicate_on_despawn : bool = false + +## File path to the current scene, must be configured manually. +@export_file_path("*.tscn") var scene_path = "" + +# Array list of peers which this node is spawned remotely. +# Used to automaticly despawn when this node exits tree. +var _replicated_peers : Array[int] = [] + +# Ensure root is ready, get_snapshot then replicate spawn to peers if enabled. +func _ready() -> void: + multiplayer.peer_disconnected.connect(_on_multiplayer_peer_disconnected) + + if not replicate_on_spawn: + return + + if root.is_node_ready(): + var absolute_node_path := root.get_parent().get_path() + NetworkSceneSpawner._spawn.rpc(scene_path, absolute_node_path, _get_snapshot()) + +# Replicate despawn to all peers if enabled. +# Replicate despawn to [member SceneSpawner._replicated_peers] always. +func _exit_tree() -> void: + var absolute_node_path := root.get_path() + + if replicate_on_despawn: + NetworkSceneSpawner._despawn.rpc(absolute_node_path) + else: + # Despawn on replicated peers. See member spawn_on_peer. + for peer_id : int in _replicated_peers: + NetworkSceneSpawner._despawn.rpc_id(peer_id, absolute_node_path) + +## Spawn on specific peer with current properties. +## Spawned peer id will be remembered and automaticly despawned if +## [member SceneSpawner.replicate_on_despawn] is true. +func spawn_on_peer(peer_id : int) -> void: + var absolute_node_path := root.get_parent().get_path() + NetworkSceneSpawner._spawn.rpc_id(peer_id, scene_path, absolute_node_path, _get_snapshot()) + + _replicated_peers.push_back(peer_id) + +## Despawn on specific peer. +## If given [param peer_id] was not replicated before, this function will return without despawning. +func despawn_on_peer(peer_id : int) -> void: + if not _replicated_peers.has(peer_id): + return + + var absolute_node_path := root.get_path() + NetworkSceneSpawner._despawn.rpc_id(peer_id, absolute_node_path) + _replicated_peers.erase(peer_id) + +# Get current snapshot as Dictionary. +func _get_snapshot() -> Dictionary: + var dict := {} + + for property : String in properties: + var value = root.get_indexed(property) + dict[property] = value + + return dict + +# If disconnected peer_id was in the list [member SceneSpawner._replicated_peers], erase it. +func _on_multiplayer_peer_disconnected(peer_id : int) -> void: + # Just try to erase without checking if its there. + _replicated_peers.erase(peer_id) diff --git a/addons/netfox/scene-spawner.gd.uid b/addons/netfox/scene-spawner.gd.uid new file mode 100644 index 00000000..efd3c457 --- /dev/null +++ b/addons/netfox/scene-spawner.gd.uid @@ -0,0 +1 @@ +uid://caakls1o4k54q