Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions addons/netfox/icons/scene-spawner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions addons/netfox/icons/scene-spawner.svg.import
Original file line number Diff line number Diff line change
@@ -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
12 changes: 11 additions & 1 deletion addons/netfox/netfox.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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] = [
Expand Down Expand Up @@ -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():
Expand Down
63 changes: 63 additions & 0 deletions addons/netfox/network-scene-spawner.gd
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions addons/netfox/network-scene-spawner.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://b2ln64fq5quv5
85 changes: 85 additions & 0 deletions addons/netfox/scene-spawner.gd
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions addons/netfox/scene-spawner.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://caakls1o4k54q