From affdd54093c2f7d77449c2d32ee920d74b84b6b1 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Sun, 20 Aug 2023 22:49:03 -0400 Subject: [PATCH] Added passing of data between staged scenes Added passing of user_data between scenes. Added default handling of string/vector3/transform3d user_data as spawn-point. Added extended documentation about staging signals and methods. Modified demo teleporter to support optional spawn-point data. --- VERSIONS.md | 2 + addons/godot-xr-tools/staging/scene_base.gd | 117 +++++++++++++----- addons/godot-xr-tools/staging/staging.gd | 81 ++++++++----- assets/meshes/teleport/teleport.gd | 128 ++++++++++++++++++-- demo_staging.gd | 4 +- 5 files changed, 259 insertions(+), 73 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index 13b80426..56bab886 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -11,6 +11,8 @@ - Modified pickup highlighting to support pickables in snap-zones - Added "UI Objects" layer 23 for viewports to support interaction by pointer and poking - Fixed player scaling issues with crouching and poke +- Added support for passing user data between staged scenes with default handling for spawn-points + # 4.1.0 - Enhanced grappling to support collision and target layers diff --git a/addons/godot-xr-tools/staging/scene_base.gd b/addons/godot-xr-tools/staging/scene_base.gd index b3903e53..c00d86a3 100644 --- a/addons/godot-xr-tools/staging/scene_base.gd +++ b/addons/godot-xr-tools/staging/scene_base.gd @@ -2,32 +2,33 @@ class_name XRToolsSceneBase extends Node3D -## Introduction -# -# This is our base scene for all our levels. -# It ensures that we have all bits in place to load -# our scene into our staging scene. - -## Request staging exit to main menu +## XR Tools Scene Base Class +## +## This is our base scene for all our levels. It ensures that we have all bits +## in place to load our scene into our staging scene. ## +## Developers can customize scene transitions by extending from this class and +## overriding the [method scene_loaded] behavior. + + ## This signal is used to request the staging transition to the main-menu ## scene. Developers should use [method exit_to_main_menu] rather than ## emitting this signal directly. signal request_exit_to_main_menu -## Request staging load a new scene -## ## This signal is used to request the staging transition to the specified ## scene. Developers should use [method load_scene] rather than emitting ## this signal directly. -signal request_load_scene(p_scene_path) - -## Request staging reload the current scene ## +## The [param user_data] parameter is passed through staging to the new scenes. +signal request_load_scene(p_scene_path, user_data) + ## This signal is used to request the staging reload this scene. Developers ## should use [method reset_scene] rather than emitting this signal directly. -signal request_reset_scene +## +## The [param user_data] parameter is passed through staging to the new scenes. +signal request_reset_scene(user_data) ## Interface @@ -35,10 +36,13 @@ signal request_reset_scene func _ready() -> void: pass + # Add support for is_xr_class on XRTools classes func is_xr_class(name : String) -> bool: return name == "XRToolsSceneBase" + +## This method center the player on the [param p_transform] transform. func center_player_on(p_transform : Transform3D): # In order to center our player so the players feet are at the location # indicated by p_transform, and having our player looking in the required @@ -60,28 +64,74 @@ func center_player_on(p_transform : Transform3D): # And now update our origin point $XROrigin3D.global_transform = (p_transform * transform.inverse()).orthonormalized() -func scene_loaded(): + +## This method is called when the scene is loaded, but before it becomes visible. +## +## The [param user_data] parameter is an optional parameter passed in when the +## scene is loaded - usually from the previous scene. By default the +## user_data can be a spawn-point node-name, Vector3, Transform3D, or +## null to spawn at the scenes XROrigin3D location. +## +## Advanced scene-transition functionality can be implemented by overriding this +## method and calling the super() with any desired spawn transform. This could +## come from a field of an advanced user_data class-object, or from a game-state +## singleton. +func scene_loaded(user_data = null): # Called after scene is loaded # Make sure our camera becomes the current camera $XROrigin3D/XRCamera3D.current = true $XROrigin3D.current = true - # Center our player on our origin point - # Note, this means you can place the XROrigin3D point in the start - # position where you want the player to spawn, even if the player is - # physically halfway across the room. - center_player_on($XROrigin3D.global_transform) + # Inspect standard user data types to override the players starting location + # in a scene. Overriding this behavior can be achieved by overriding this + # function and calling super() with: + # - null to use the standard XROrigin3D location + # - string name of a Node3D to spawn at + # - Vector3 to spawn at + # - Transform3D to spawn at + var spawn_position : Transform3D = $XROrigin3D.global_transform + match typeof(user_data): + TYPE_STRING: # Name of Node3D to spawn at + var node = find_child(user_data) + if node is Node3D: + spawn_position = node.global_transform + + TYPE_VECTOR3: # Vector3 to spawn at (rotation comes from XROrigin3D) + spawn_position.origin = user_data + + TYPE_TRANSFORM3D: # Transform3D spawn location + spawn_position = user_data -func scene_visible(): + # Center the player on the spawn location + center_player_on(spawn_position) + + +## This method is called when the scene becomes fully visible to the user. +## +## The [param user_data] parameter is an optional parameter passed in when the +## scene is loaded - usually from the previous scene. +func scene_visible(_user_data = null): # Called after the scene becomes fully visible pass -func scene_pre_exiting(): + +## This method is called before the start of transition from this scene to a +## new scene. +## +## The [param user_data] parameter is an optional parameter passed in when the +## scene transition is requested. +func scene_pre_exiting(_user_data = null): # Called before we start fading out and removing our scene pass -func scene_exiting(): + +## This method is called immediately before this scene is unloaded. +## +## +## The [param user_data] parameter is an optional parameter passed in when the +## scene transition is requested. +func scene_exiting(_user_data = null): # called right before we remove this scene pass @@ -97,19 +147,23 @@ func exit_to_main_menu() -> void: emit_signal("request_exit_to_main_menu") -## Transition to specific scene -## ## This function is used to transition to the specified scene. The default ## implementation sends the [signal request_load_scene]. ## ## Custom scene classes can override this function to add their logic, but ## should usually call this super method. -func load_scene(p_scene_path : String) -> void: - emit_signal("request_load_scene", p_scene_path) +## +## The [param user_data] parameter is passed to the new scene, and can be used +## to relay information through the transition. The default behavior of +## [method scene_loaded] will attempt to interpret it as a spawn-point for the +## player as node-name, Vector3, or Transform3D. +## +## See [method scene_loaded] for options to provide advanced scene-transition +## functionality. +func load_scene(p_scene_path : String, user_data = null) -> void: + emit_signal("request_load_scene", p_scene_path, user_data) -## Reset current scene -## ## This function is used to reset the current scene. The default ## implementation sends the [signal request_reset_scene] which triggers ## a reload of the current scene. @@ -117,6 +171,7 @@ func load_scene(p_scene_path : String) -> void: ## Custom scene classes can override this method to implement faster reset ## logic than is performed by the brute-force scene-reload performed by ## staging. -func reset_scene() -> void: - emit_signal("request_reset_scene") - +## +## Any [param user_data] provided is passed into the new scene. +func reset_scene(user_data = null) -> void: + emit_signal("request_reset_scene", user_data) diff --git a/addons/godot-xr-tools/staging/staging.gd b/addons/godot-xr-tools/staging/staging.gd index 8b2ea141..26fd7bae 100644 --- a/addons/godot-xr-tools/staging/staging.gd +++ b/addons/godot-xr-tools/staging/staging.gd @@ -3,7 +3,7 @@ class_name XRToolsStaging extends Node3D -## XR Tools Staging +## XR Tools Staging Class ## ## When creating a game with multiple levels where you want to ## make use of background loading and have some nice structure @@ -28,22 +28,36 @@ extends Node3D ## explained in individual demos found here. -## Current scene is being unloaded -signal scene_exiting(scene) +## This signal is emitted when the current scene starts to be unloaded. The +## [param scene] parameter is the path of the current scene, and the +## [param user_data] parameter is the optional data passed from the +## current scene to the next. +signal scene_exiting(scene, user_data) -## Switched to the loading scene -signal switching_to_loading_scene +## This signal is emitted when the old scene has been unloaded and the user +## is fading into the loading scene. The [param user_data] parameter is the +## optional data provided by the old scene. +signal switching_to_loading_scene(user_data) -## New scene has been loaded -signal scene_loaded(scene) +## This signal is emitted when the new scene has been loaded before it becomes +## visible. The [param scene] parameter is the path of the new scene, and the +## [param user_data] parameter is the optional data passed from the old scene +## to the new scene. +signal scene_loaded(scene, user_data) -## New scene is now visible -signal scene_visible(scene) +## This signal is emitted when the new scene has become fully visible to the +## player. The [param scene] parameter is the path of the new scene, and the +## [param user_data] parameter is the optional data passed from the old scene +## to the new scene. +signal scene_visible(scene, user_data) -## XR interaction started +## This signal is invoked when the XR experience starts. signal xr_started -## XR interaction ended +## This signal is invoked when the XR experience ends. This usually occurs when +## the player removes the headset. The game may want to react by pausing until +## the player puts the headset back on and the [signal xr_started] signal is +## emitted. signal xr_ended @@ -54,19 +68,19 @@ signal xr_ended @export var prompt_for_continue : bool = true -# Current scene +## The current scene var current_scene : XRToolsSceneBase -# Current scene path +## The current scene path var current_scene_path : String # Tween for fading var _tween : Tween -## XR Origin +## The [XROrigin3D] node used while staging @onready var xr_origin : XROrigin3D = XRHelpers.get_xr_origin(self) -## XR Camera +## The [XRCamera3D] node used while staging @onready var xr_camera : XRCamera3D = XRHelpers.get_xr_camera(self) @@ -114,8 +128,14 @@ func is_xr_class(name : String) -> bool: return name == "XRToolsStaging" -## Load the specified scene -func load_scene(p_scene_path : String) -> void: +## This function loads the [param p_scene_path] scene file. +## +## The [param user_data] parameter contains optional data passed from the old +## scene to the new scene. +## +## See [method XRToolsSceneBase.scene_loaded] for details on how to implement +## advanced scene-switching. +func load_scene(p_scene_path : String, user_data = null) -> void: # Do not load if in the editor if Engine.is_editor_hint(): return @@ -130,7 +150,7 @@ func load_scene(p_scene_path : String) -> void: # Start by unloading our scene # Let the scene know we're about to remove it - current_scene.scene_pre_exiting() + current_scene.scene_pre_exiting(user_data) # Remove signals _remove_signals(current_scene) @@ -143,8 +163,8 @@ func load_scene(p_scene_path : String) -> void: await _tween.finished # Now we remove our scene - emit_signal("scene_exiting", current_scene) - current_scene.scene_exiting() + emit_signal("scene_exiting", current_scene, user_data) + current_scene.scene_exiting(user_data) $Scene.remove_child(current_scene) current_scene.queue_free() current_scene = null @@ -157,7 +177,7 @@ func load_scene(p_scene_path : String) -> void: $LoadingScreen.enable_press_to_continue = false $LoadingScreen.follow_camera = true $LoadingScreen.visible = true - emit_signal("switching_to_loading_scene") + switching_to_loading_scene.emit(user_data) # Fade to visible if _tween: @@ -217,8 +237,8 @@ func load_scene(p_scene_path : String) -> void: # We create a small delay here to give tracking some time to update our nodes... await get_tree().create_timer(0.1).timeout - current_scene.scene_loaded() - emit_signal("scene_loaded", current_scene) + current_scene.scene_loaded(user_data) + scene_loaded.emit(current_scene, user_data) # Fade to visible if _tween: @@ -227,11 +247,12 @@ func load_scene(p_scene_path : String) -> void: _tween.tween_method(set_fade, 1.0, 0.0, 1.0) await _tween.finished - current_scene.scene_visible() - emit_signal("scene_visible", current_scene) + current_scene.scene_visible(user_data) + scene_visible.emit(current_scene, user_data) -## Fade +## This method sets the fade-alpha for scene transitions. The [param p_value] +## parameter must be in the range [0.0 - 1.0]. ## ## Our fade object allows us to black out the screen for transitions. ## Note that our AABB is set to HUGE so it should always be rendered @@ -262,12 +283,12 @@ func _on_exit_to_main_menu(): load_scene(main_scene) -func _on_load_scene(p_scene_path : String): - load_scene(p_scene_path) +func _on_load_scene(p_scene_path : String, user_data): + load_scene(p_scene_path, user_data) -func _on_reset_scene(): - load_scene(current_scene_path) +func _on_reset_scene(user_data): + load_scene(current_scene_path, user_data) func _on_StartXR_xr_started(): diff --git a/assets/meshes/teleport/teleport.gd b/assets/meshes/teleport/teleport.gd index 359c1e40..5f636903 100644 --- a/assets/meshes/teleport/teleport.gd +++ b/assets/meshes/teleport/teleport.gd @@ -3,12 +3,32 @@ class_name Teleport extends Node3D -## Scene base for the current scene -@export var scene_base: NodePath +## Type of spawn-point data +enum SpawnDataType { + ## No data provided + NONE, -## Main scene file + ## Name of spawn-point node provided + NODE_NAME, + + ## Vector3 of spawn-point provided + VECTOR3, + + ## Transform3D of spawn-point provided + TRANSFORM3D +} + + +@export_group("Teleport") + +## Target scene file @export_file('*.tscn') var scene : String +## Spawn point data +@export var spawn_data := SpawnDataType.NONE: set = _set_spawn_data + +@export_group("Display") + ## Title texture @export var title: Texture2D: set = _set_title @@ -24,13 +44,25 @@ extends Node3D ## The beam color in inactive state @export var inactive_beam_color: Color = Color("#ad0400"): set = _set_inactive_beam_color -# Scene base to trigger loading -@onready var _scene_base: XRToolsSceneBase = get_node(scene_base) + +# Spawn point node-name +var spawn_point_name := "" + +# Spawn point position +var spawn_point_position := Vector3.ZERO + +# Spawn point transform +var spawn_point_transform := Transform3D.IDENTITY + +# Scene base +var _scene_base : XRToolsSceneBase + func _ready(): + _scene_base = XRTools.find_xr_ancestor(self, "*", "XRToolsSceneBase") _update_title() _update_teleport() - + # Called when the player enters the teleport area func _on_TeleportArea_body_entered(body: Node3D): @@ -47,10 +79,74 @@ func _on_TeleportArea_body_entered(body: Node3D): return # Teleport - if scene != "": - _scene_base.load_scene(scene) - else: + if scene == "": _scene_base.exit_to_main_menu() + elif spawn_data == SpawnDataType.NODE_NAME: + _scene_base.load_scene(scene, spawn_point_name) + elif spawn_data == SpawnDataType.VECTOR3: + _scene_base.load_scene(scene, spawn_point_position) + elif spawn_data == SpawnDataType.TRANSFORM3D: + _scene_base.load_scene(scene, spawn_point_transform) + else: + _scene_base.load_scene(scene) + + +# Provide custom property information +func _get_property_list() -> Array[Dictionary]: + # Return extra properties + return [ + { + name = "Teleport", + type = TYPE_NIL, + usage = PROPERTY_USAGE_GROUP + }, + { + name = "spawn_point_name", + type = TYPE_STRING, + usage = PROPERTY_USAGE_DEFAULT \ + if spawn_data == SpawnDataType.NODE_NAME \ + else PROPERTY_USAGE_NO_EDITOR + }, + { + name = "spawn_point_position", + type = TYPE_VECTOR3, + usage = PROPERTY_USAGE_DEFAULT \ + if spawn_data == SpawnDataType.VECTOR3 \ + else PROPERTY_USAGE_NO_EDITOR + }, + { + name = "spawn_point_transform", + type = TYPE_TRANSFORM3D, + usage = PROPERTY_USAGE_DEFAULT \ + if spawn_data == SpawnDataType.TRANSFORM3D \ + else PROPERTY_USAGE_NO_EDITOR + } + ] + + +# Allow revert of custom properties +func _property_can_revert(property : StringName) -> bool: + match property: + "spawn_point_name": + return true + "spawn_point_position": + return true + "spawn_point_transform": + return true + _: + return false + + +# Provide revert values for custom properties +func _property_get_revert(property : StringName): # Variant + match property: + "spawn_point_name": + return "" + "spawn_point_position": + return Vector3.ZERO + "spawn_point_transform": + return Transform3D.IDENTITY + func set_collision_disabled(value): if !Engine.is_editor_hint(): @@ -58,36 +154,48 @@ func set_collision_disabled(value): if child is CollisionShape3D: child.disabled = value + +func _set_spawn_data(p_spawn_data : SpawnDataType) -> void: + spawn_data = p_spawn_data + notify_property_list_changed() + + func _set_title(value): title = value if is_inside_tree(): _update_title() + func _update_title(): if title: var material: ShaderMaterial = $TeleportBody/Top.get_active_material(1) material.set_shader_parameter("Title", title) + func _set_active(value): active = value if is_inside_tree(): _update_teleport() - + + func _set_active_beam_color(value): active_beam_color = value if is_inside_tree(): _update_teleport() + func _set_inactive_beam_color(value): inactive_beam_color = value if is_inside_tree(): _update_teleport() + func _set_inactive_beam_visible(value): inactive_beam_visible = value if is_inside_tree(): _update_teleport() + func _update_teleport(): if active: $TeleportArea/Cylinder.get_surface_override_material(0).set_shader_parameter("beam_color", active_beam_color) diff --git a/demo_staging.gd b/demo_staging.gd index cdb9cf46..37796644 100644 --- a/demo_staging.gd +++ b/demo_staging.gd @@ -34,14 +34,14 @@ func _ready() -> void: super() -func _on_Staging_scene_loaded(_scene): +func _on_Staging_scene_loaded(_scene, _user_data): # We only show the press to continue the first time we load a scene # to give the player time to put their headset on. prompt_for_continue = false scene_is_loaded = true -func _on_Staging_scene_exiting(_scene): +func _on_Staging_scene_exiting(_scene, _user_data): # We no longer have an active scene scene_is_loaded = false