@tool extends Node3D @onready var perspective = $XROrigin3D/Perspective @onready var look_recenter_timer:Timer = Timer.new() @export var right_controller:XRController3D const mouse_look_sensitivity: float = 0.001 const gamepad_look_sensitivity_deg: float = 90.0 const gamepad_look_deadzone: float = 0.15 var _use_prompt_1:bool = false var _use_prompt_2:bool = false @export var use_prompt_1:bool: get: return _use_prompt_1 set(val): _use_prompt_1 = val if (val): $prompt_1_timer.start(prompt_1_time) else: $prompt_1_timer.stop() @export var prompt_1_time:int @export var prompt_2_time:int @export var use_prompt_2:bool: get: return _use_prompt_2 set(val): _use_prompt_2 = val if (val and $prompt_2_timer): $prompt_2_timer.start(prompt_2_time) else: $prompt_2_timer.stop() @export var stage_music:AudioStreamMP3 = null @onready var is_centering:bool = false var _tunnel_1_color:Color = Color.WHITE var _tunnel_2_color:Color = Color.WHITE var _tunnel_3_color:Color = Color.WHITE var _tunnel_1_spiral_color:Color = Color(0.8, 0.8, 0.8, 1) var _tunnel_2_spiral_color:Color = Color(0.8, 0.8, 0.8, 1) var _tunnel_3_spiral_color:Color = Color(0.8, 0.8, 0.8, 1) var _tunnel_1_spiral_turns:float = 1.0 var _tunnel_2_spiral_turns:float = 8.0 var _tunnel_3_spiral_turns:float = 1.0 var _tunnel_1_spiral_swirl:float = 6.0 var _tunnel_2_spiral_swirl:float = 6.0 var _tunnel_3_spiral_swirl:float = 6.0 var _tunnel_1_spiral_width:float = 0.047 var _tunnel_2_spiral_width:float = 0.047 var _tunnel_3_spiral_width:float = 0.047 var _tunnel_1_spiral_strands:float = 1.0 var _tunnel_2_spiral_strands:float = 1.0 var _tunnel_3_spiral_strands:float = 1.0 @export var tunnel_1_color:Color = Color.WHITE: get: return _tunnel_1_color set(val): _tunnel_1_color = val if $MeshInstance3D: _set_mesh_shader_parameter($MeshInstance3D, "color", val) @export var tunnel_2_color:Color = Color.WHITE: get: return _tunnel_2_color set(val): _tunnel_2_color = val if $MeshInstance3D/MeshInstance3D2: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D2, "color", val) @export var tunnel_3_color:Color = Color.WHITE: get: return _tunnel_3_color set(val): _tunnel_3_color = val if $MeshInstance3D/MeshInstance3D3: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D3, "color", val) if $MeshInstance3D/Blurrycap: _set_blurrycap_shader_parameter("tint_color", val) @export var tunnel_1_spiral_color:Color = Color(0.8, 0.8, 0.8, 1): get: return _tunnel_1_spiral_color set(val): _tunnel_1_spiral_color = val if $MeshInstance3D: _set_mesh_shader_parameter($MeshInstance3D, "spiral_color", val) @export var tunnel_2_spiral_color:Color = Color(0.8, 0.8, 0.8, 1): get: return _tunnel_2_spiral_color set(val): _tunnel_2_spiral_color = val if $MeshInstance3D/MeshInstance3D2: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D2, "spiral_color", val) @export var tunnel_3_spiral_color:Color = Color(0.8, 0.8, 0.8, 1): get: return _tunnel_3_spiral_color set(val): _tunnel_3_spiral_color = val if $MeshInstance3D/MeshInstance3D3: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D3, "spiral_color", val) @export_range(1.0, 48.0) var tunnel_1_spiral_turns:float = 1.0: get: return _tunnel_1_spiral_turns set(val): _tunnel_1_spiral_turns = val if $MeshInstance3D: _set_mesh_shader_parameter($MeshInstance3D, "spiral_turns", val) @export_range(1.0, 48.0) var tunnel_2_spiral_turns:float = 8.0: get: return _tunnel_2_spiral_turns set(val): _tunnel_2_spiral_turns = val if $MeshInstance3D/MeshInstance3D2: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D2, "spiral_turns", val) @export_range(1.0, 48.0) var tunnel_3_spiral_turns:float = 1.0: get: return _tunnel_3_spiral_turns set(val): _tunnel_3_spiral_turns = val if $MeshInstance3D/MeshInstance3D3: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D3, "spiral_turns", val) @export_range(6.0, 360.0) var tunnel_1_spiral_swirl:float = 6.0: get: return _tunnel_1_spiral_swirl set(val): _tunnel_1_spiral_swirl = val if $MeshInstance3D: _set_mesh_shader_parameter($MeshInstance3D, "spiral_swirl", val) @export_range(6.0, 360.0) var tunnel_2_spiral_swirl:float = 6.0: get: return _tunnel_2_spiral_swirl set(val): _tunnel_2_spiral_swirl = val if $MeshInstance3D/MeshInstance3D2: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D2, "spiral_swirl", val) @export_range(6.0, 360.0) var tunnel_3_spiral_swirl:float = 6.0: get: return _tunnel_3_spiral_swirl set(val): _tunnel_3_spiral_swirl = val if $MeshInstance3D/MeshInstance3D3: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D3, "spiral_swirl", val) @export_range(0.02, 0.5) var tunnel_1_spiral_width:float = 0.047: get: return _tunnel_1_spiral_width set(val): _tunnel_1_spiral_width = val if $MeshInstance3D: _set_mesh_shader_parameter($MeshInstance3D, "spiral_width", val) @export_range(0.02, 0.5) var tunnel_2_spiral_width:float = 0.047: get: return _tunnel_2_spiral_width set(val): _tunnel_2_spiral_width = val if $MeshInstance3D/MeshInstance3D2: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D2, "spiral_width", val) @export_range(0.02, 0.5) var tunnel_3_spiral_width:float = 0.047: get: return _tunnel_3_spiral_width set(val): _tunnel_3_spiral_width = val if $MeshInstance3D/MeshInstance3D3: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D3, "spiral_width", val) @export_range(1.0, 2.0) var tunnel_1_spiral_strands:float = 1.0: get: return _tunnel_1_spiral_strands set(val): _tunnel_1_spiral_strands = val if $MeshInstance3D: _set_mesh_shader_parameter($MeshInstance3D, "spiral_strands", val) @export_range(1.0, 2.0) var tunnel_2_spiral_strands:float = 1.0: get: return _tunnel_2_spiral_strands set(val): _tunnel_2_spiral_strands = val if $MeshInstance3D/MeshInstance3D2: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D2, "spiral_strands", val) @export_range(1.0, 2.0) var tunnel_3_spiral_strands:float = 1.0: get: return _tunnel_3_spiral_strands set(val): _tunnel_3_spiral_strands = val if $MeshInstance3D/MeshInstance3D3: _set_mesh_shader_parameter($MeshInstance3D/MeshInstance3D3, "spiral_strands", val) func _enter_tree() -> void: if Engine.is_editor_hint(): set_process(true) call_deferred("_apply_tunnel_shader_properties") # Called when the node enters the scene tree for the first time. func _ready() -> void: _apply_tunnel_shader_properties() # Keep editor behavior limited to visual parameter syncing. if Engine.is_editor_hint(): return # Re-trigger setters now that the scene tree is ready. add_child(look_recenter_timer) look_recenter_timer.connect("timeout",_look_recenter_timer_timeout) look_recenter_timer.one_shot = true look_recenter_timer.autostart = false look_recenter_timer.stop() # As a PC game, we want to eat the mouse cursor like an FPS. Input.mouse_mode = Input.MOUSE_MODE_CAPTURED # _ensure_gamepad_look_actions() if use_prompt_1: $prompt_2_timer.start(169) if use_prompt_2: $prompt_1_timer.start(69) # Initialize VR _init_vr() # Wait one frame for XR to be fully set up, then center the headset await get_tree().process_frame if get_viewport().use_xr: XRServer.center_on_hmd(XRServer.DONT_RESET_ROTATION, false) else: # go fullscreen - this is a computer. DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) # Begin the portal _prepare_music() _begin_portal() $XROrigin3D/AudioStreamPlayer.play() func _apply_tunnel_shader_properties() -> void: var mesh_1 := get_node_or_null("MeshInstance3D") as MeshInstance3D var mesh_2 := get_node_or_null("MeshInstance3D/MeshInstance3D2") as MeshInstance3D var mesh_3 := get_node_or_null("MeshInstance3D/MeshInstance3D3") as MeshInstance3D if mesh_1: _set_mesh_shader_parameter(mesh_1, "color", _tunnel_1_color) _set_mesh_shader_parameter(mesh_1, "spiral_color", _tunnel_1_spiral_color) _set_mesh_shader_parameter(mesh_1, "spiral_turns", _tunnel_1_spiral_turns) _set_mesh_shader_parameter(mesh_1, "spiral_swirl", _tunnel_1_spiral_swirl) _set_mesh_shader_parameter(mesh_1, "spiral_width", _tunnel_1_spiral_width) _set_mesh_shader_parameter(mesh_1, "spiral_strands", _tunnel_1_spiral_strands) if mesh_2: _set_mesh_shader_parameter(mesh_2, "color", _tunnel_2_color) _set_mesh_shader_parameter(mesh_2, "spiral_color", _tunnel_2_spiral_color) _set_mesh_shader_parameter(mesh_2, "spiral_turns", _tunnel_2_spiral_turns) _set_mesh_shader_parameter(mesh_2, "spiral_swirl", _tunnel_2_spiral_swirl) _set_mesh_shader_parameter(mesh_2, "spiral_width", _tunnel_2_spiral_width) _set_mesh_shader_parameter(mesh_2, "spiral_strands", _tunnel_2_spiral_strands) if mesh_3: _set_mesh_shader_parameter(mesh_3, "color", _tunnel_3_color) _set_mesh_shader_parameter(mesh_3, "spiral_color", _tunnel_3_spiral_color) _set_mesh_shader_parameter(mesh_3, "spiral_turns", _tunnel_3_spiral_turns) _set_mesh_shader_parameter(mesh_3, "spiral_swirl", _tunnel_3_spiral_swirl) _set_mesh_shader_parameter(mesh_3, "spiral_width", _tunnel_3_spiral_width) _set_mesh_shader_parameter(mesh_3, "spiral_strands", _tunnel_3_spiral_strands) _set_blurrycap_shader_parameter("tint_color", _tunnel_3_color) func _prepare_music()->void: $XROrigin3D/AudioStreamPlayer.stream=stage_music func _cycle_presentation_format() -> void: var emitter := $MeshInstance3D/EmissionPoint if not emitter.is_node_ready(): return emitter.cycle_format() func _set_mesh_shader_parameter( mesh_instance: MeshInstance3D, parameter: StringName, val: Variant ) -> void: var mesh := mesh_instance.mesh as CylinderMesh if not mesh: return var shader_material := mesh.material as ShaderMaterial if shader_material: shader_material.set_shader_parameter(parameter, val) func _set_blurrycap_shader_parameter(parameter: StringName, val: Variant) -> void: var blurrycap := get_node_or_null("MeshInstance3D/Blurrycap") as MeshInstance3D if not blurrycap: return var shader_material := blurrycap.get_surface_override_material(0) as ShaderMaterial if shader_material: shader_material.set_shader_parameter(parameter, val) func _begin_portal() -> void: var emitter := $MeshInstance3D/EmissionPoint emitter.set_viewer_camera(perspective) await emitter.initialize_video_pool() func _init_vr(): if (XRServer.get_interface_count() > 0): var xr_interface = XRServer.find_interface("OpenXR") if xr_interface and xr_interface.is_initialized(): print("OpenXR initialized successfully") # Turn off v-sync! DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) # Change our main viewport to output to the HMD get_viewport().use_xr = true else: print("OpenXR not initialized, please check if your headset is connected") func _input(event:InputEvent) ->void: if Engine.is_editor_hint(): return if event.is_action_pressed("Quit"): get_tree().quit(0) elif event.is_action_pressed("Fullscreen"): if DisplayServer.window_get_mode() != DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) else: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) if event.is_action_pressed("CycleFormat"): _cycle_presentation_format() func _process(delta: float) -> void: if Engine.is_editor_hint(): _apply_tunnel_shader_properties() return # Desktop gamepad look is disabled while XR output is active. _do_gamepad_movement(gamepad_look_sensitivity_deg, delta) if $XROrigin3D/XRController3D.get_float("trigger") > 0: _cycle_presentation_format() _do_autocentering(delta) func _do_autocentering(delta:float)->void: if is_centering: var target_rotation = Vector3.ZERO var recenter_speed = 0.05 # radians per second var rotation_delta = target_rotation - perspective.rotation rotation_delta.x = angle_difference(perspective.rotation.x, target_rotation.x) rotation_delta.y = angle_difference(perspective.rotation.y, target_rotation.y) perspective.rotation += rotation_delta.normalized() * recenter_speed * delta if rotation_delta.length() < 0.01: is_centering = false perspective.rotation = Vector3(0, perspective.rotation.z, 0) func _unhandled_input(event: InputEvent) -> void: if Engine.is_editor_hint(): return if (event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED) : var e = event as InputEventMouseMotion _apply_look_delta( mouse_look_sensitivity * -e.relative.x, mouse_look_sensitivity * -e.relative.y ) look_recenter_timer.start(1) func _apply_look_delta(yaw_delta: float, pitch_delta: float) -> void: perspective.rotate_y(yaw_delta) perspective.rotate_x(pitch_delta) perspective.rotation.x = clampf(perspective.rotation.x, -deg_to_rad(50), deg_to_rad(50)) perspective.rotation.y = clampf(perspective.rotation.y, -deg_to_rad(35), deg_to_rad(35)) perspective.rotation.z = 0 func _do_gamepad_movement(gamepad_look_sensitivity_deg:float, delta:float)->void: if get_viewport().use_xr: return var look_input := Vector2( Input.get_axis("look_left", "look_right"), Input.get_axis("look_up", "look_down") ) if look_input.length_squared() < gamepad_look_deadzone * gamepad_look_deadzone: return _apply_look_delta( deg_to_rad(-look_input.x * gamepad_look_sensitivity_deg * delta), deg_to_rad(-look_input.y * gamepad_look_sensitivity_deg * delta) ) if look_input!=Vector2.ZERO: # You moved! look_recenter_timer.start(1.5) # seconds of no motion until auto recenter. func _look_recenter_timer_timeout(): is_centering = true