Compare commits

..

5 Commits

Author SHA1 Message Date
42490ddad9 GDScript formatting
Thanks, VS Code (:
2025-03-04 21:51:27 +01:00
1e100bee4d Add test level to menu 2025-03-04 20:11:28 +01:00
5e8725eefc Test level for viewport frame delay
These viewports are NOT delayed. What am I doing wrong? Is it that they
are created programmatically???
2025-03-04 19:04:48 +01:00
eba916fb81 WIP: Camera transforms not working 2025-03-03 23:03:10 +01:00
50314f1ad8 My own buggy portal implementation 2025-03-03 22:31:31 +01:00
28 changed files with 778 additions and 219 deletions

View File

@ -11,16 +11,16 @@ extends Area3D
class_name AdvancedPortalTeleport
## Checks if the node is moving TOWARDS the portal before teleporting it.
@export var velocity_check:bool = true
@export var velocity_check: bool = true
## An additional velocity push given to RigidBody3Ds/CharacterBody3D exiting the portal.
@export var exit_push_velocity:float = 0
@export var exit_push_velocity: float = 0
## Seconds to keep portal clones visible after the node leaves the teleporter.
@export var clone_keep_alive_seconds:float = 0.1
@export var clone_keep_alive_seconds: float = 0.1
var _parent_portal:Portal
var _parent_portal: Portal
# Info about the nodes currently crossing the portal
var _crossing_nodes:Array = []
var _crossing_nodes: Array = []
func _ready() -> void:
_parent_portal = get_parent() as Portal
@ -30,10 +30,10 @@ func _ready() -> void:
connect("area_entered", _on_area_entered)
connect("area_exited", _on_area_exited)
func _process(delta:float) -> void:
func _process(delta: float) -> void:
# Update nodes crossing the portal
for i in range(_crossing_nodes.size() - 1, -1, -1):
var crossing_node:Dictionary = _crossing_nodes[i]
var crossing_node: Dictionary = _crossing_nodes[i]
if not is_instance_valid(crossing_node.node):
# Node has been freed, remove crossing_node
@ -61,17 +61,17 @@ func _process(delta:float) -> void:
_crossing_nodes.remove_at(i)
# Try to teleport the crossing node, and return false otherwise
func _try_teleport(crossing_node:Dictionary) -> bool:
var node:Node3D = crossing_node.node
func _try_teleport(crossing_node: Dictionary) -> bool:
var node: Node3D = crossing_node.node
# Check if the node is moving towards the portal
if velocity_check:
if node is RigidBody3D:
var local_velocity:Vector3 = _parent_portal.global_transform.basis.inverse() * node.linear_velocity
var local_velocity: Vector3 = _parent_portal.global_transform.basis.inverse() * node.linear_velocity
if local_velocity.z >= 0:
return false
elif node is CharacterBody3D:
var local_velocity:Vector3 = _parent_portal.global_transform.basis.inverse() * node.velocity
var local_velocity: Vector3 = _parent_portal.global_transform.basis.inverse() * node.velocity
if local_velocity.z >= 0:
return false
else:
@ -88,7 +88,7 @@ func _try_teleport(crossing_node:Dictionary) -> bool:
# Additional push when exiting the portal
if exit_push_velocity > 0:
var exit_forward:Vector3 = _parent_portal.exit_portal.global_transform.basis.z.normalized()
var exit_forward: Vector3 = _parent_portal.exit_portal.global_transform.basis.z.normalized()
node.linear_velocity += exit_forward * exit_push_velocity
# Handle CharacterBody3D physics
@ -98,7 +98,7 @@ func _try_teleport(crossing_node:Dictionary) -> bool:
# Additional push when exiting the portal
if exit_push_velocity > 0:
var exit_forward:Vector3 = _parent_portal.exit_portal.global_transform.basis.z.normalized()
var exit_forward: Vector3 = _parent_portal.exit_portal.global_transform.basis.z.normalized()
node.velocity += exit_forward * exit_push_velocity
# Transform the position and orientation
@ -106,24 +106,23 @@ func _try_teleport(crossing_node:Dictionary) -> bool:
return true
func _on_area_entered(area:Area3D) -> void:
func _on_area_entered(area: Area3D) -> void:
if area.has_meta("teleportable_root"):
# The node may not teleport immediately if it's not heading TOWARDS the portal,
# so we keep a reference to it until it teleports or leaves.
# This also allows us to hide its portal clone after it leaves.
var root: Node3D = area.get_node(area.get_meta("teleportable_root"))
var root:Node3D = area.get_node(area.get_meta("teleportable_root"))
var crossing_node:Dictionary
var crossing_node: Dictionary
if root.has_meta("crossing_node"):
# Node is crossing another portal, erase it from that portal and start using this one instead
crossing_node = root.get_meta("crossing_node")
crossing_node.teleporter._crossing_nodes.erase(crossing_node)
else:
# First portal
var clone:Node3D = area.get_node(area.get_meta("portal_clone")) if area.has_meta("portal_clone") else null
var clone: Node3D = area.get_node(area.get_meta("portal_clone")) if area.has_meta("portal_clone") else null
crossing_node = {
"node": root,
"node": root,
"clone": clone,
"clone_portal": null,
"teleporter": null,
@ -151,10 +150,10 @@ func _on_area_entered(area:Area3D) -> void:
crossing_node.clone_portal = _parent_portal.exit_portal
crossing_node.left = true
func _on_area_exited(area:Area3D) -> void:
func _on_area_exited(area: Area3D) -> void:
if area.has_meta("teleportable_root"):
var root:Node3D = area.get_node(area.get_meta("teleportable_root"))
var crossing_node:Dictionary = root.get_meta("crossing_node")
var root: Node3D = area.get_node(area.get_meta("teleportable_root"))
var crossing_node: Dictionary = root.get_meta("crossing_node")
if crossing_node.teleporter == self:
# The node left the enter portal without teleporting (but don't erase it yet)

View File

@ -11,18 +11,18 @@ extends Area3D
class_name CollisionDisableArea
## Seconds until collisions are re-enabled after the body leaves the area.
@export var re_enable_delay_seconds:float = 0.1
@export var re_enable_delay_seconds: float = 0.1
# Info about the disabled bodies
var _disables:Array = []
var _disables: Array = []
func _ready() -> void:
connect("body_entered", _on_body_entered)
connect("body_exited", _on_body_exited)
func _process(delta:float) -> void:
func _process(delta: float) -> void:
for i in range(_disables.size() - 1, -1, -1):
var disable_info:Dictionary = _disables[i]
var disable_info: Dictionary = _disables[i]
if not is_instance_valid(disable_info.body):
# Body has been freed, remove disable info
@ -50,7 +50,7 @@ func _process(delta:float) -> void:
# Final disable, so re-enable the collision mask
disable_info.body.set_collision_mask_value(layer_number, true)
func _on_body_entered(body:PhysicsBody3D) -> void:
func _on_body_entered(body: PhysicsBody3D) -> void:
if not body.has_meta("disabled_collision_masks"):
return
@ -65,10 +65,10 @@ func _on_body_entered(body:PhysicsBody3D) -> void:
# Keep track of the number of times each collision mask is disabled in a meta field
if not body.has_meta("collision_disable_count"):
body.set_meta("collision_disable_count", {})
var disable_count:Dictionary = body.get_meta("collision_disable_count")
var disable_count: Dictionary = body.get_meta("collision_disable_count")
# Disable the collision masks specified in the "disabled_collision_masks" metadata array
var disabled_layers:Array = body.get_meta("disabled_collision_masks")
var disabled_layers: Array = body.get_meta("disabled_collision_masks")
for layer_number in disabled_layers:
# Only consider layers which were enabled to begin with
if disable_count.has(layer_number) or body.get_collision_mask_value(layer_number):
@ -87,7 +87,7 @@ func _on_body_entered(body:PhysicsBody3D) -> void:
"left": false,
})
func _on_body_exited(body:PhysicsBody3D) -> void:
func _on_body_exited(body: PhysicsBody3D) -> void:
if body.has_meta("disabled_collision_masks"):
for disable_info in _disables:
if disable_info.body == body:

View File

@ -9,16 +9,16 @@
extends RigidBody3D
class_name Hoverable
@onready var _mesh:MeshInstance3D = $Mesh
@onready var _mesh: MeshInstance3D = $Mesh
var hovered:bool
var hovered: bool
var _strength:float
var _strength: float
func _ready() -> void:
_mesh.material_override = _mesh.mesh.surface_get_material(0).duplicate()
func _process(delta:float) -> void:
func _process(delta: float) -> void:
_strength = min(1, _strength + delta * 10) if hovered else max(0, _strength - delta * 10)
_mesh.material_override.albedo_color = lerp(Color.WHITE, Color.RED, _strength)

View File

@ -9,15 +9,15 @@
extends MeshInstance3D
class_name LineRenderer
@export var material:Material
@export var material: Material
var _lines:Array
var _dirty:bool
var _lines: Array
var _dirty: bool
func _ready() -> void:
mesh = ImmediateMesh.new()
func add_line(from:Vector3, to:Vector3, color:Color = Color.WHITE) -> void:
func add_line(from: Vector3, to: Vector3, color: Color = Color.WHITE) -> void:
_lines.push_back([from, to, color])
_dirty = true
@ -25,7 +25,7 @@ func clear_lines() -> void:
_lines.clear()
_dirty = true
func _process(_delta:float) -> void:
func _process(_delta: float) -> void:
if not _dirty:
return
_dirty = false

View File

@ -8,18 +8,18 @@
extends MeshInstance3D
const _MOUSE_PAN_THRESHOLD:float = 0.4;
const _MOUSE_PAN_SPEED:float = 200
const _MOUSE_PAN_THRESHOLD: float = 0.4;
const _MOUSE_PAN_SPEED: float = 200
const _MOVE_SPEED:float = 4
const _MOVE_SPEED: float = 4
var _mouse_look:Vector2
var _mouse_look: Vector2
@onready var _line_renderer:LineRenderer = $"../LineRenderer"
@onready var _camera:Camera3D = $Camera
@onready var _pip_camera:Camera3D = $PipViewport1/Camera
@onready var _line_renderer: LineRenderer = $"../LineRenderer"
@onready var _camera: Camera3D = $Camera
@onready var _pip_camera: Camera3D = $PipViewport1/Camera
@onready var _handle_raycast_callable:Callable = Callable(self, "_handle_raycast")
@onready var _handle_raycast_callable: Callable = Callable(self, "_handle_raycast")
func _ready() -> void:
set_layer_mask_value(1, false)
@ -27,16 +27,16 @@ func _ready() -> void:
_camera.set_cull_mask_value(2, false)
func _process(delta:float) -> void:
var viewport:Viewport = get_viewport()
var mouse_position:Vector2 = viewport.get_mouse_position()
var viewport_size:Vector2i = viewport.size
var normalized_mouse_position:Vector2 = mouse_position / Vector2(viewport_size)
func _process(delta: float) -> void:
var viewport: Viewport = get_viewport()
var mouse_position: Vector2 = viewport.get_mouse_position()
var viewport_size: Vector2i = viewport.size
var normalized_mouse_position: Vector2 = mouse_position / Vector2(viewport_size)
var horizontal_speed:float = 0
var vertical_speed:float = 0
var horizontal_speed: float = 0
var vertical_speed: float = 0
if normalized_mouse_position.x > 0 and normalized_mouse_position.x < 1 and\
if normalized_mouse_position.x > 0 and normalized_mouse_position.x < 1 and \
normalized_mouse_position.y > 0 and normalized_mouse_position.y < 1:
if normalized_mouse_position.x < _MOUSE_PAN_THRESHOLD:
horizontal_speed = lerp(0, 1, _MOUSE_PAN_THRESHOLD - normalized_mouse_position.x)
@ -53,15 +53,15 @@ func _process(delta:float) -> void:
rotation_degrees = Vector3(0, _mouse_look.x, 0)
_camera.rotation_degrees = Vector3(_mouse_look.y, 0, 0)
var right:Vector3 = (global_transform.basis.x * Vector3(1, 0, 1)).normalized()
var forward:Vector3 = (global_transform.basis.z * Vector3(1, 0, 1)).normalized()
var right: Vector3 = (global_transform.basis.x * Vector3(1, 0, 1)).normalized()
var forward: Vector3 = (global_transform.basis.z * Vector3(1, 0, 1)).normalized()
if Input.is_key_pressed(KEY_LEFT) or Input.is_key_pressed(KEY_A):
global_translate(-right * _MOVE_SPEED * delta)
global_translate(- right * _MOVE_SPEED * delta)
if Input.is_key_pressed(KEY_RIGHT) or Input.is_key_pressed(KEY_D):
global_translate(right * _MOVE_SPEED * delta)
if Input.is_key_pressed(KEY_UP) or Input.is_key_pressed(KEY_W):
global_translate(-forward * _MOVE_SPEED * delta)
global_translate(- forward * _MOVE_SPEED * delta)
if Input.is_key_pressed(KEY_DOWN) or Input.is_key_pressed(KEY_S):
global_translate(forward * _MOVE_SPEED * delta)
@ -75,38 +75,38 @@ func _process(delta:float) -> void:
_pip_camera.global_position = _camera.to_global(Vector3(0, 1.5, 1.5))
_pip_camera.global_rotation = _camera.global_rotation
var origin:Vector3 = _camera.project_ray_origin(mouse_position)
var end:Vector3 = origin + _camera.project_ray_normal(mouse_position) * 100
var origin: Vector3 = _camera.project_ray_origin(mouse_position)
var end: Vector3 = origin + _camera.project_ray_normal(mouse_position) * 100
_line_renderer.clear_lines()
Portal.raycast(get_tree(), origin, (end - origin).normalized(), _handle_raycast_callable)
func _handle_raycast(from:Vector3, dir:Vector3, segment_distance:float, _recursive_distance:float, recursions:int) -> bool:
var distance:float = min(100, segment_distance)
var target:Vector3 = from + dir * distance
func _handle_raycast(from: Vector3, dir: Vector3, segment_distance: float, _recursive_distance: float, recursions: int) -> bool:
var distance: float = min(100, segment_distance)
var target: Vector3 = from + dir * distance
_line_renderer.add_line(from, target, Color.GREEN)
for i in 16:
_line_renderer.add_line(target, target + Vector3(
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1)), Color.RED)
if recursions > 0:
_line_renderer.add_line(from, from + Vector3(
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1)), Color.BLUE)
var space_state:PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(from, from + dir * distance)
var result:Dictionary = space_state.intersect_ray(query)
var space_state: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
var query: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(from, from + dir * distance)
var result: Dictionary = space_state.intersect_ray(query)
if not result.is_empty() and result.collider is Hoverable:
result.collider.hovered = true
for i in 16:
_line_renderer.add_line(result.position, result.position + Vector3(
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1),
randf_range(-0.1, 0.1)), Color.GREEN)
return true

View File

@ -12,56 +12,56 @@ class_name Portal
## The portal represents a single portal mesh in a pair of portals.
## The delay between the main viewport changing size and the portal viewport resizing.
const _RESIZE_THROTTLE_SECONDS:float = 0.1
const _RESIZE_THROTTLE_SECONDS: float = 0.1
## The minimum camera near clipping distance.
const _EXIT_CAMERA_NEAR_MIN:float = 0.01
const _EXIT_CAMERA_NEAR_MIN: float = 0.01
## The portal mesh's local bounding box.
@onready var _mesh_aabb:AABB = mesh.get_aabb()
@onready var _mesh_aabb: AABB = mesh.get_aabb()
## The vertical resolution of the portal viewport which covers the entire screen not just the portal mesh. Use 0 to use the real resolution.
@export var vertical_viewport_resolution:int = 512
@export var vertical_viewport_resolution: int = 512
## Disable viewport distance. Portals further away than this won't have their viewports rendered.
@export var disable_viewport_distance:float = 11
@export var disable_viewport_distance: float = 11
## Whether to destroy the disabled viewport to save texture memory. Useful when you have a lot of portals. The viewport is re/-created when within disable_viewport_distance and visible.
@export var destroy_disabled_viewport:bool = true
@export var destroy_disabled_viewport: bool = true
## The maximum fade-out distance.
@export var fade_out_distance_max:float = 10
@export var fade_out_distance_max: float = 10
## The minimum fade-out distance.
@export var fade_out_distance_min:float = 8
@export var fade_out_distance_min: float = 8
## The fade-out color.
@export var fade_out_color:Color = Color.WHITE
@export var fade_out_color: Color = Color.WHITE
## The scale of the exit side of the portal. < 1 means the exit is smaller than the entrance.
@export var exit_scale:float = 1.0
@export var exit_scale: float = 1.0
## A value subtracted from the exit camera near clipping plane. Useful for handling clipping issues.
@export var exit_near_subtract:float = 0.05
@export var exit_near_subtract: float = 0.05
## The main camera. Leave unset to use the default 3D camera.
@export var main_camera:Camera3D
@export var main_camera: Camera3D
## An environment set for the exit camera. Leave unset to use the default environment.
@export var exit_environment:Environment
@export var exit_environment: Environment
## The cull mask for the exit camera. Use it to hide certain objects in the exit camera.
@export_flags_3d_render var exit_cull_mask:int = 0b11111111111111111111
@export_flags_3d_render var exit_cull_mask: int = 0b11111111111111111111
## The exit portal. Leave unset to use this portal as an exit only.
@export var exit_portal:Portal
@export var exit_portal: Portal
## An environment set for the exit camera. Leave unset to use the default environment.
@export var portal_shader:Shader = preload("res://addons/simple-portal-system/shaders/portal.gdshader")
@export var portal_shader: Shader = preload("res://addons/simple-portal-system/shaders/portal.gdshader")
# The viewport rendering the portal surface
var _viewport:SubViewport
var _viewport: SubViewport
# The exit camera copies the main camera's position relative to the exit portal
var _exit_camera:Camera3D
var _exit_camera: Camera3D
# The number of seconds until the viewport updates its size
var _seconds_until_resize:float
var _seconds_until_resize: float
func _enter_tree() -> void:
add_to_group("portals")
@ -81,7 +81,7 @@ func _ready() -> void:
# Non-uniform parent scaling can introduce skew which isn't compensated for
if get_parent() != null:
var parent_scale:Vector3 = get_parent().global_transform.basis.get_scale()
var parent_scale: Vector3 = get_parent().global_transform.basis.get_scale()
if abs(parent_scale.x - parent_scale.y) > 0.01 or abs(parent_scale.x - parent_scale.z) > 0.01:
push_warning("The parent of \"%s\" is not uniformly scaled. The portal will not work correctly." % name)
@ -100,12 +100,12 @@ func _ready() -> void:
material_override.shader = portal_shader
material_override.set_shader_parameter("fade_out_distance_max", fade_out_distance_max)
material_override.set_shader_parameter("fade_out_distance_min", fade_out_distance_min)
material_override.set_shader_parameter("fade_out_color", fade_out_color)
material_override.set_shader_parameter("fade_out_color", fade_out_color)
# Create the viewport when _ready if it's not destroyed when disabled.
# This may potentially get rid of the initial lag when the viewport is first created at the cost of texture memory.
if not destroy_disabled_viewport:
_create_viewport()
_createa_viewport()
get_viewport().connect("size_changed", _handle_resize)
@ -117,7 +117,7 @@ func _create_viewport() -> void:
_viewport = SubViewport.new()
_viewport.name = "Viewport"
_viewport.render_target_clear_mode = SubViewport.CLEAR_MODE_ONCE
exit_portal.add_child(_viewport)
add_child(_viewport)
material_override.set_shader_parameter("albedo", _viewport.get_texture())
# Create the exit camera which renders the portal surface for the viewport
@ -130,9 +130,9 @@ func _create_viewport() -> void:
# Resize the viewport on the next _process
_seconds_until_resize = 0
func _process(delta:float) -> void:
func _process(delta: float) -> void:
# Disable the viewport if the portal is further away than disable_viewport_distance or if the portal is invisible in the scene tree
var disable_viewport:bool = not is_visible_in_tree() or\
var disable_viewport: bool = not is_visible_in_tree() or \
main_camera.global_position.distance_squared_to(global_position) > disable_viewport_distance * disable_viewport_distance
# Enable or disable 3D rendering for the viewport (if it exists)
@ -163,13 +163,13 @@ func _process(delta:float) -> void:
if _seconds_until_resize <= 0:
_seconds_until_resize = NAN
var viewport_size:Vector2i = get_viewport().size
var viewport_size: Vector2i = get_viewport().size
if vertical_viewport_resolution == 0:
# Resize the viewport to the main viewport size
_viewport.size = viewport_size
else:
# Resize the viewport to the fixed height vertical_viewport_resolution and dynamic width
var aspect_ratio:float = float(viewport_size.x) / viewport_size.y
var aspect_ratio: float = float(viewport_size.x) / viewport_size.y
_viewport.size = Vector2i(int(vertical_viewport_resolution * aspect_ratio + 0.5), vertical_viewport_resolution)
# Move the exit camera relative to the exit portal based on the main camera's position relative to the entrance portal
@ -177,18 +177,18 @@ func _process(delta:float) -> void:
# Get the four X, Y corners of the scaled entrance portal bounding box clamped to Z=0 (portal surface) relative to the exit portal.
# The entrance portal bounding box is used since the entrance portal mesh does not need to match the exit portal mesh.
var corner_1:Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x, _mesh_aabb.position.y, 0) * exit_scale)
var corner_2:Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x + _mesh_aabb.size.x, _mesh_aabb.position.y, 0) * exit_scale)
var corner_3:Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x + _mesh_aabb.size.x, _mesh_aabb.position.y + _mesh_aabb.size.y, 0) * exit_scale)
var corner_4:Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x, _mesh_aabb.position.y + _mesh_aabb.size.y, 0) * exit_scale)
var corner_1: Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x, _mesh_aabb.position.y, 0) * exit_scale)
var corner_2: Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x + _mesh_aabb.size.x, _mesh_aabb.position.y, 0) * exit_scale)
var corner_3: Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x + _mesh_aabb.size.x, _mesh_aabb.position.y + _mesh_aabb.size.y, 0) * exit_scale)
var corner_4: Vector3 = exit_portal.to_global(Vector3(_mesh_aabb.position.x, _mesh_aabb.position.y + _mesh_aabb.size.y, 0) * exit_scale)
# Calculate the distance along the exit camera forward vector at which each of the portal corners projects
var camera_forward:Vector3 = -_exit_camera.global_transform.basis.z.normalized()
var camera_forward: Vector3 = - _exit_camera.global_transform.basis.z.normalized()
var d_1:float = (corner_1 - _exit_camera.global_position).dot(camera_forward)
var d_2:float = (corner_2 - _exit_camera.global_position).dot(camera_forward)
var d_3:float = (corner_3 - _exit_camera.global_position).dot(camera_forward)
var d_4:float = (corner_4 - _exit_camera.global_position).dot(camera_forward)
var d_1: float = (corner_1 - _exit_camera.global_position).dot(camera_forward)
var d_2: float = (corner_2 - _exit_camera.global_position).dot(camera_forward)
var d_3: float = (corner_3 - _exit_camera.global_position).dot(camera_forward)
var d_4: float = (corner_4 - _exit_camera.global_position).dot(camera_forward)
# The near clip distance is the shortest distance which still contains all the corners
_exit_camera.near = max(_EXIT_CAMERA_NEAR_MIN, min(d_1, d_2, d_3, d_4) - exit_near_subtract)
@ -197,60 +197,60 @@ func _process(delta:float) -> void:
_exit_camera.keep_aspect = main_camera.keep_aspect
## Return a new Transform3D relative to the exit portal based on the real Transform3D relative to this portal.
func real_to_exit_transform(real:Transform3D) -> Transform3D:
func real_to_exit_transform(real: Transform3D) -> Transform3D:
# Convert from global space to local space at the entrance (this) portal
var local:Transform3D = global_transform.affine_inverse() * real
var local: Transform3D = global_transform.affine_inverse() * real
# Compensate for any scale the entrance portal may have
var unscaled:Transform3D = local.scaled(global_transform.basis.get_scale())
var unscaled: Transform3D = local.scaled(global_transform.basis.get_scale())
# Flip it (the portal always flips the view 180 degrees)
var flipped:Transform3D = unscaled.rotated(Vector3.UP, PI)
var flipped: Transform3D = unscaled.rotated(Vector3.UP, PI)
# Apply any scale the exit portal may have (and apply custom exit scale)
var exit_scale_vector:Vector3 = exit_portal.global_transform.basis.get_scale()
var scaled_at_exit:Transform3D = flipped.scaled(Vector3.ONE / exit_scale_vector * exit_scale)
var exit_scale_vector: Vector3 = exit_portal.global_transform.basis.get_scale()
var scaled_at_exit: Transform3D = flipped.scaled(Vector3.ONE / exit_scale_vector * exit_scale)
# Convert from local space at the exit portal to global space
var local_at_exit:Transform3D = exit_portal.global_transform * scaled_at_exit
var local_at_exit: Transform3D = exit_portal.global_transform * scaled_at_exit
return local_at_exit
## Return a new position relative to the exit portal based on the real position relative to this portal.
func real_to_exit_position(real:Vector3) -> Vector3:
func real_to_exit_position(real: Vector3) -> Vector3:
# Convert from global space to local space at the entrance (this) portal
var local:Vector3 = global_transform.affine_inverse() * real
var local: Vector3 = global_transform.affine_inverse() * real
# Compensate for any scale the entrance portal may have
var unscaled:Vector3 = local * global_transform.basis.get_scale()
var unscaled: Vector3 = local * global_transform.basis.get_scale()
# Apply any scale the exit portal may have (and apply custom exit scale)
var exit_scale_vector:Vector3 = Vector3(-1, 1, 1) * exit_portal.global_transform.basis.get_scale()
var scaled_at_exit:Vector3 = unscaled / exit_scale_vector * exit_scale
var exit_scale_vector: Vector3 = Vector3(-1, 1, 1) * exit_portal.global_transform.basis.get_scale()
var scaled_at_exit: Vector3 = unscaled / exit_scale_vector * exit_scale
# Convert from local space at the exit portal to global space
var local_at_exit:Vector3 = exit_portal.global_transform * scaled_at_exit
var local_at_exit: Vector3 = exit_portal.global_transform * scaled_at_exit
return local_at_exit
## Return a new direction relative to the exit portal based on the real direction relative to this portal.
func real_to_exit_direction(real:Vector3) -> Vector3:
func real_to_exit_direction(real: Vector3) -> Vector3:
# Convert from global to local space at the entrance (this) portal
var local:Vector3 = global_transform.basis.inverse() * real
var local: Vector3 = global_transform.basis.inverse() * real
# Compensate for any scale the entrance portal may have
var unscaled:Vector3 = local * global_transform.basis.get_scale()
var unscaled: Vector3 = local * global_transform.basis.get_scale()
# Flip it (the portal always flips the view 180 degrees)
var flipped:Vector3 = unscaled.rotated(Vector3.UP, PI)
var flipped: Vector3 = unscaled.rotated(Vector3.UP, PI)
# Apply any scale the exit portal may have (and apply custom exit scale)
var exit_scale_vector:Vector3 = exit_portal.global_transform.basis.get_scale()
var scaled_at_exit:Vector3 = flipped / exit_scale_vector * exit_scale
var exit_scale_vector: Vector3 = exit_portal.global_transform.basis.get_scale()
var scaled_at_exit: Vector3 = flipped / exit_scale_vector * exit_scale
# Convert from local space at the exit portal to global space
var local_at_exit:Vector3 = exit_portal.global_transform.basis * scaled_at_exit
var local_at_exit: Vector3 = exit_portal.global_transform.basis * scaled_at_exit
return local_at_exit
## Raycast against portals (See instructions).
static func raycast(tree:SceneTree, from:Vector3, dir:Vector3, handle_raycast:Callable,
max_distance:float = INF, max_recursions:int = 16, ignore_backside:bool = true) -> void:
var portals:Array = tree.get_nodes_in_group("portals")
var ignore_portal:Portal = null
var recursive_distance:float = 0
static func raycast(tree: SceneTree, from: Vector3, dir: Vector3, handle_raycast: Callable,
max_distance: float = INF, max_recursions: int = 16, ignore_backside: bool = true) -> void:
var portals: Array = tree.get_nodes_in_group("portals")
var ignore_portal: Portal = null
var recursive_distance: float = 0
for r in max_recursions + 1:
var closest_hit:Vector3
var closest_dir:Vector3
var closest_portal:Portal
var closest_distance_sqr:float = INF
var closest_hit: Vector3
var closest_dir: Vector3
var closest_portal: Portal
var closest_distance_sqr: float = INF
# Find the closest portal the ray intersects
for portal in portals:
@ -258,8 +258,8 @@ static func raycast(tree:SceneTree, from:Vector3, dir:Vector3, handle_raycast:Ca
if portal == ignore_portal or not portal.is_inside_tree() or tree != portal.get_tree() or not portal.is_visible_in_tree():
continue
var local_from:Vector3 = portal.to_local(from)
var local_dir:Vector3 = portal.global_transform.basis.inverse() * dir
var local_from: Vector3 = portal.to_local(from)
var local_dir: Vector3 = portal.global_transform.basis.inverse() * dir
# Check if ray is parallel to the portal
if local_dir.z == 0:
@ -270,22 +270,22 @@ static func raycast(tree:SceneTree, from:Vector3, dir:Vector3, handle_raycast:Ca
continue
# Get the intersection point of the ray with the Z axis
var t:float = -local_from.z / local_dir.z
var t: float = - local_from.z / local_dir.z
# Is the intersection behind the start position?
if t < 0:
continue
# Check if the ray hit inside the portal bounding box (ignoring Z)
var local_hit:Vector3 = local_from + t * local_dir
var aabb:AABB = portal._mesh_aabb
if local_hit.x < aabb.position.x or local_hit.x > aabb.position.x + aabb.size.x or\
var local_hit: Vector3 = local_from + t * local_dir
var aabb: AABB = portal._mesh_aabb
if local_hit.x < aabb.position.x or local_hit.x > aabb.position.x + aabb.size.x or \
local_hit.y < aabb.position.y or local_hit.y > aabb.position.y + aabb.size.y:
continue
# Check if this was the closest portal
var hit:Vector3 = portal.to_global(local_hit)
var distance_sqr:float = hit.distance_squared_to(from)
var hit: Vector3 = portal.to_global(local_hit)
var distance_sqr: float = hit.distance_squared_to(from)
if distance_sqr < closest_distance_sqr:
closest_hit = hit
closest_dir = dir
@ -293,7 +293,7 @@ static func raycast(tree:SceneTree, from:Vector3, dir:Vector3, handle_raycast:Ca
closest_portal = portal
# Calculate the ray distance
var hit_distance:float = INF if is_inf(closest_distance_sqr) else sqrt(closest_distance_sqr)
var hit_distance: float = INF if is_inf(closest_distance_sqr) else sqrt(closest_distance_sqr)
# Call the user-defined raycast function
if handle_raycast.call(from, dir, hit_distance, recursive_distance, r):

View File

@ -22,7 +22,7 @@ func _ready():
func _on_area_entered(area: Area3D) -> void:
if area.has_meta("teleportable_root"):
var root:Node3D = area.get_node(area.get_meta("teleportable_root"))
var root: Node3D = area.get_node(area.get_meta("teleportable_root"))
print("[%f] Teleporting %s to %s" % [roundi(Time.get_ticks_msec() / 100) / 10.0, root.name, _parent_portal.exit_portal.name])
root.global_transform = _parent_portal.real_to_exit_transform(root.global_transform)

View File

@ -22,7 +22,7 @@ void vertex() {
// Pass the distance to the fragment shader using a varying attribute.
vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec3 camera_position = (INV_VIEW_MATRIX * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
pixel_distance = distance(world_position, camera_position);
}

4
hud.gd
View File

@ -1,13 +1,17 @@
extends VBoxContainer
@onready var fps_label: Label = $FPS_Label
func _ready() -> void:
toggle_hud() # This should hide the HUD by default
fps_label.reparent.call_deferred(get_parent())
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_cancel"):
toggle_hud()
fps_label.text = "FPS: %d" % Engine.get_frames_per_second()
func toggle_hud() -> void:
var should_show = Input.mouse_mode == Input.MOUSE_MODE_CAPTURED

View File

@ -12,6 +12,10 @@ unique_name_in_owner = true
layout_mode = 2
text = "Back to Menu"
[node name="FPS_Label" type="Label" parent="."]
layout_mode = 2
text = "FPS: -1"
[node name="HSeparator" type="HSeparator" parent="."]
layout_mode = 2

View File

@ -134,5 +134,5 @@ script = ExtResource("7_2gewm")
base = NodePath("../WorldEnvironment")
[node name="ProceduralMeshMaker" parent="." node_paths=PackedStringArray("portal") instance=ExtResource("8_vdsn8")]
width = 2.0
portal = NodePath("../Portal_orange")
width = 2.0

View File

@ -1,26 +1,10 @@
[gd_scene load_steps=9 format=4 uid="uid://dgvdetmbv5jya"]
[gd_scene load_steps=6 format=3 uid="uid://dgvdetmbv5jya"]
[ext_resource type="PackedScene" uid="uid://b5x7fmpwck335" path="res://hud.tscn" id="1_2k7q4"]
[ext_resource type="Script" uid="uid://ca45js46kc0l7" path="res://levels/level_hermione.gd" id="1_gyvwl"]
[ext_resource type="PackedScene" uid="uid://drhaqr78kv1o2" path="res://levels/room_hermione_outside.tscn" id="2_empe6"]
[ext_resource type="PackedScene" uid="uid://ci81nttn6foio" path="res://levels/room_hermione_inside.tscn" id="2_lv1mc"]
[ext_resource type="PackedScene" uid="uid://cgdlowfuuorvi" path="res://player.tscn" id="3_gyvwl"]
[ext_resource type="PackedScene" uid="uid://jecigkibqyew" path="res://portal.tscn" id="6_owlrt"]
[ext_resource type="PackedScene" uid="uid://d1dtxvwk86ple" path="res://procedural_mesh_maker.tscn" id="7_ucmro"]
[sub_resource type="ArrayMesh" id="ArrayMesh_owlrt"]
_surfaces = [{
"aabb": AABB(-0.45, -1.075, 0, 0.9, 2.15, 1e-05),
"attribute_data": PackedByteArray("AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPw=="),
"format": 34359742487,
"index_count": 30,
"index_data": PackedByteArray("AAABAAQABAABAAUAAQADAAUABQADAAcAAwACAAcABwACAAYAAgAAAAYABgAAAAQABAAFAAYABgAFAAcA"),
"name": "Portal Material",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("ZmbmvpqZiT8AAAAAZmbmPpqZiT8AAAAAZmbmvpqZib8AAAAAZmbmPpqZib8AAAAAZmbmvpqZiT8AAAAAZmbmPpqZiT8AAAAAZmbmvpqZib8AAAAAZmbmPpqZib8AAAAA/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgA==")
}]
[node name="level_hermione" type="Node"]
script = ExtResource("1_gyvwl")
@ -46,11 +30,6 @@ render_target_update_mode = 4
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.08041, 1.41104, 0)
[node name="PortalOutside" parent="SubViewportContainer/OutsideWorld/OutsideWorldContainer" node_paths=PackedStringArray("main_camera") instance=ExtResource("6_owlrt")]
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 3.505, 1.55, 0.5)
mesh = SubResource("ArrayMesh_owlrt")
main_camera = NodePath("../Player/Camera3D")
[node name="HUD" parent="." instance=ExtResource("1_2k7q4")]
[node name="TeleportButton" type="Button" parent="HUD"]
@ -66,18 +45,4 @@ size = Vector2i(1920, 1080)
[node name="room_hermione_inside" parent="InsideWorld/InsideWorldContainer" instance=ExtResource("2_lv1mc")]
[node name="PortalInside" parent="InsideWorld/InsideWorldContainer" node_paths=PackedStringArray("main_camera", "exit_portal") instance=ExtResource("6_owlrt")]
transform = Transform3D(1.19249e-08, 0, -1, 0, 1, 0, 1, 0, 1.19249e-08, 3.505, 1.55, 0.5)
mesh = SubResource("ArrayMesh_owlrt")
main_camera = NodePath("../../../SubViewportContainer/OutsideWorld/OutsideWorldContainer/Player/Camera3D")
exit_portal = NodePath("../../../SubViewportContainer/OutsideWorld/OutsideWorldContainer/PortalOutside")
[node name="ProceduralMeshMaker" parent="." node_paths=PackedStringArray("portal") instance=ExtResource("7_ucmro")]
portal = NodePath("../InsideWorld/InsideWorldContainer/PortalInside")
height = 2.15
width = 0.9
indent = 0.0
[connection signal="pressed" from="HUD/TeleportButton" to="." method="_on_teleport_button_pressed"]
[editable path="SubViewportContainer/OutsideWorld/OutsideWorldContainer/Player"]

View File

@ -0,0 +1,15 @@
extends Node3D
@onready var orange: Node3D = $Orange
@onready var green: Node3D = $Green
@onready var player: CharacterBody3D = $Player
func _on_transport_to_orange_pressed() -> void:
player.global_position = orange.global_position
player.global_position.y += 10
func _on_transport_to_green_pressed() -> void:
player.global_position = green.global_position
player.global_position.y += 10

View File

@ -0,0 +1 @@
uid://c8vjum7jkvrv0

View File

@ -0,0 +1,269 @@
[gd_scene load_steps=25 format=4 uid="uid://rlienyx6av8u"]
[ext_resource type="PackedScene" uid="uid://cxopylew5786r" path="res://portal_environment_adapter.tscn" id="1_eoaoo"]
[ext_resource type="Script" uid="uid://c8vjum7jkvrv0" path="res://levels/level_my_portals.gd" id="1_pccqs"]
[ext_resource type="Material" uid="uid://bx6qeabdhq2s" path="res://addons/kenney_prototype_tools/materials/dark/material_01.tres" id="1_v6nyh"]
[ext_resource type="Material" uid="uid://dn16yhnqtqh7i" path="res://addons/kenney_prototype_tools/materials/orange/material_02.tres" id="2_5nkxg"]
[ext_resource type="PackedScene" uid="uid://cgdlowfuuorvi" path="res://player.tscn" id="3_ysrn6"]
[ext_resource type="PackedScene" uid="uid://b5x7fmpwck335" path="res://hud.tscn" id="4_t21k5"]
[ext_resource type="Material" uid="uid://oob6p5w3hsl5" path="res://addons/kenney_prototype_tools/materials/red/material_09.tres" id="6_2ffpc"]
[ext_resource type="Material" uid="uid://b7kc8jfs4fowj" path="res://addons/kenney_prototype_tools/materials/green/material_02.tres" id="6_b88kk"]
[ext_resource type="Script" uid="uid://dh8miiv7xc4ps" path="res://scripts/my_portal.gd" id="7_pvdtj"]
[ext_resource type="Material" uid="uid://ci8rdsdqe5a61" path="res://addons/kenney_prototype_tools/materials/light/material_06.tres" id="8_dhtg5"]
[ext_resource type="PackedScene" uid="uid://d1dtxvwk86ple" path="res://procedural_mesh_maker.tscn" id="9_dhtg5"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_t21k5"]
sky_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
ground_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
[sub_resource type="Sky" id="Sky_eoaoo"]
sky_material = SubResource("ProceduralSkyMaterial_t21k5")
[sub_resource type="Environment" id="Environment_b88kk"]
background_mode = 2
sky = SubResource("Sky_eoaoo")
glow_enabled = true
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_7nftg"]
data = PackedVector3Array(-9, -1.5, -9, -10, -1.5, -10, -9, -1.5, 9, -10, -1.5, 10, -10, -1.5, -10, -10, 1.5, -10, -10, -1.5, -10, -9, -1.5, -9, 9, -1.5, -9, 10, -1.5, -10, -10, 1.5, -10, -10, -1.5, -10, -9, -1.5, 9, -10, -1.5, -10, -10, -1.5, 10, 10, -1.5, 10, -9, -1.5, 9, -10, -1.5, 10, -9, 1.5, -9, -10, 1.5, 10, -10, 1.5, -10, 10, 1.5, -10, -9, 1.5, -9, -10, 1.5, -10, -10, 1.5, -10, -10, 1.5, 10, -10, -1.5, 10, -9, 1.5, 9, -10, 1.5, 10, -9, 1.5, -9, -10, 1.5, 10, 10, 1.5, 10, -10, -1.5, 10, -10, 1.5, 10, -9, 1.5, 9, 9, 1.5, 9, -10, -1.5, -10, 9, -1.5, -9, 10, -1.5, -10, 9, -1.5, -9, 10, -1.5, 10, 10, -1.5, -10, 10, -1.5, 10, 9, -1.5, 9, -9, -1.5, 9, 10, 1.5, 10, 10, -1.5, 10, -10, -1.5, 10, 9, -1.5, 9, 10, -1.5, 10, 9, -1.5, -9, 10, -1.5, 10, 10, 1.5, 10, 10, -1.5, -10, 10, -1.5, -10, 10, 1.5, -10, -10, 1.5, -10, 10, 1.5, -10, 9, 1.5, -9, -9, 1.5, -9, 10, 1.5, 10, 10, 1.5, -10, 10, -1.5, -10, 9, 1.5, -9, 10, 1.5, -10, 9, 1.5, 9, -10, 1.5, 10, 9, 1.5, 9, 10, 1.5, 10, 9, 1.5, 9, 10, 1.5, -10, 10, 1.5, 10, -9, 1.5, -9, -9, -1.5, -9, -9, -1.5, 9, 9, -1.5, -9, -9, -1.5, -9, -9, 1.5, -9, -9, -1.5, 9, -9, 1.5, 9, -9, 1.5, -9, 9, 1.5, 9, -9, 1.5, 9, -9, -1.5, 9, -9, -1.5, 9, 9, -1.5, 9, 9, 1.5, 9, 9, 1.5, 9, 9, -1.5, 9, 9, -1.5, -9, -9, 1.5, -9, 9, 1.5, -9, 9, -1.5, -9, 9, -1.5, -9, 9, 1.5, -9, 9, 1.5, 9)
[sub_resource type="ArrayMesh" id="ArrayMesh_v6nyh"]
_surfaces = [{
"aabb": AABB(-10, -1.5, -10, 20, 3, 20),
"attribute_data": PackedByteArray("MzNzP83MTD0AAIA/AAAAAM3MTD3NzEw9AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAzM3M/zcxMPTMzcz8zM3M/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAzcxMPc3MTD0AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD/NzEw9zcxMPQAAAAAAAAAAMzNzPzMzcz8AAAAAAACAPwAAgD8AAIA/AACAPwAAAAAzM3M/MzNzPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/zcxMPTMzcz8AAAAAAACAPzMzcz8zM3M/AACAPwAAAAAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD/NzEw9MzNzP83MTD3NzEw9AACAPwAAAAAzM3M/MzNzPwAAgD8AAIA/MzNzPzMzcz8AAAAAAACAPwAAgD8AAIA/AAAAAAAAgD/NzEw9MzNzP83MTD3NzEw9AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/zcxMPTMzcz8AAAAAAACAPzMzcz8zM3M/AACAPwAAAAAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAzM3M/zcxMPTMzcz8zM3M/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/MzNzP83MTD0AAIA/AAAAAM3MTD3NzEw9AAAAAAAAgD/NzEw9zcxMPQAAAAAAAAAAzcxMPc3MTD0AAIA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAAAA"),
"format": 34359738391,
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 96,
"vertex_data": PackedByteArray("AAAQwQAAwL8AABDBAAAgwQAAwL8AACDBAAAQwQAAwL8AABBBAAAgwQAAwL8AACBBAAAgwQAAwL8AACDBAAAgwQAAwD8AACDBAAAgwQAAwL8AACDBAAAQwQAAwL8AABDBAAAQQQAAwL8AABDBAAAgQQAAwL8AACDBAAAgwQAAwD8AACDBAAAgwQAAwL8AACDBAAAQwQAAwL8AABBBAAAgwQAAwL8AACDBAAAgwQAAwL8AACBBAAAgQQAAwL8AACBBAAAQwQAAwL8AABBBAAAgwQAAwL8AACBBAAAQwQAAwD8AABDBAAAgwQAAwD8AACBBAAAgwQAAwD8AACDBAAAgQQAAwD8AACDBAAAQwQAAwD8AABDBAAAgwQAAwD8AACDBAAAgwQAAwD8AACDBAAAgwQAAwD8AACBBAAAgwQAAwL8AACBBAAAQwQAAwD8AABBBAAAgwQAAwD8AACBBAAAQwQAAwD8AABDBAAAgwQAAwD8AACBBAAAgQQAAwD8AACBBAAAgwQAAwL8AACBBAAAgwQAAwD8AACBBAAAQwQAAwD8AABBBAAAQQQAAwD8AABBBAAAgwQAAwL8AACDBAAAQQQAAwL8AABDBAAAgQQAAwL8AACDBAAAQQQAAwL8AABDBAAAgQQAAwL8AACBBAAAgQQAAwL8AACDBAAAgQQAAwL8AACBBAAAQQQAAwL8AABBBAAAQwQAAwL8AABBBAAAgQQAAwD8AACBBAAAgQQAAwL8AACBBAAAgwQAAwL8AACBBAAAQQQAAwL8AABBBAAAgQQAAwL8AACBBAAAQQQAAwL8AABDBAAAgQQAAwL8AACBBAAAgQQAAwD8AACBBAAAgQQAAwL8AACDBAAAgQQAAwL8AACDBAAAgQQAAwD8AACDBAAAgwQAAwD8AACDBAAAgQQAAwD8AACDBAAAQQQAAwD8AABDBAAAQwQAAwD8AABDBAAAgQQAAwD8AACBBAAAgQQAAwD8AACDBAAAgQQAAwL8AACDBAAAQQQAAwD8AABDBAAAgQQAAwD8AACDBAAAQQQAAwD8AABBBAAAgwQAAwD8AACBBAAAQQQAAwD8AABBBAAAgQQAAwD8AACBBAAAQQQAAwD8AABBBAAAgQQAAwD8AACDBAAAgQQAAwD8AACBBAAAQwQAAwD8AABDBAAAQwQAAwL8AABDBAAAQwQAAwL8AABBBAAAQQQAAwL8AABDBAAAQwQAAwL8AABDBAAAQwQAAwD8AABDBAAAQwQAAwL8AABBBAAAQwQAAwD8AABBBAAAQwQAAwD8AABDBAAAQQQAAwD8AABBBAAAQwQAAwD8AABBBAAAQwQAAwL8AABBBAAAQwQAAwL8AABBBAAAQQQAAwL8AABBBAAAQQQAAwD8AABBBAAAQQQAAwD8AABBBAAAQQQAAwL8AABBBAAAQQQAAwL8AABDBAAAQwQAAwD8AABDBAAAQQQAAwD8AABDBAAAQQQAAwL8AABDBAAAQQQAAwL8AABDBAAAQQQAAwD8AABDBAAAQQQAAwD8AABBB/38AAP//AAD/fwAA//8AAP9/AAD//wAAAAD/f/9//n8AAP9//3/+fwAA/3//f/5//38AAP//AAD/fwAA//8AAP9/AAD//wAA/////wAA/z//////AAD/P/////8AAP8//38AAP//AAD/fwAA//8AAP9/AAD//wAA/38AAP//AAD/fwAA//8AAP9/AAD//wAA/3//////AAD/f///AAAAAP9//////wAA/3//////AAD/f/////8AAP9//////wAAAAD/f/9//n8AAP9//3/+fwAA/3//f/5//3///wAAAAD/f///AAAAAP9//////wAA/3//fwAA/z//f/9/AAD/P/9//38AAP8//3///wAAAAD/f///AAAAAP9///8AAAAA/38AAP//AAD/fwAA//8AAP9/AAD//wAA/38AAP//AAD/fwAA//8AAP9/AAD//wAA/38AAP//AAD/fwAA//8AAP9/AAD//wAA/3//fwAA/z//f/9/AAD/P/9//38AAP8//38AAP//AAD/fwAA//8AAP9/AAD//wAA////f/9//n////9//3/+f////3//f/5//////wAA/z//////AAD/P/////8AAP8//3//////AAD/f/////8AAP9//////wAA////f/9//n////9//3/+f////3//f/5//3//////AAD/f/////8AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9//////wAA/3///wAAAAD/f/////8AAP9//////wAA////f/9/AID///9//38AgP///3//fwCA/3//fwAA/7//f/9/AAD/v/9//38AAP+/////f/9/AID///9//38AgP///3//fwCA/////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+/AAD/f/9/AIAAAP9//38AgAAA/3//fwCA/3//fwAA/7//f/9/AAD/v/9//38AAP+/AAD/f/9/AIAAAP9//38AgAAA/3//fwCA")
}]
[sub_resource type="BoxMesh" id="BoxMesh_ysrn6"]
size = Vector3(20, 1, 20)
[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_5nkxg"]
points = PackedVector3Array(-10, -0.5, -10, -10, 0.5, -10, 10, -0.5, -10, -10, -0.5, 10, -10, 0.5, 10, 10, 0.5, -10, 10, -0.5, 10, 10, 0.5, 10)
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_eoaoo"]
data = PackedVector3Array(0.125, 1.5, 1.25, 0.125, 1.25, -1, 0.125, 1.25, 1, -0.125, -1.5, -1.25, -0.125, 1.25, -1, -0.125, -1.25, -1, -0.125, -1.5, 1.25, 0.125, -1.5, -1.25, -0.125, -1.5, -1.25, 0.125, -1.5, -1.25, -0.125, 1.5, -1.25, -0.125, -1.5, -1.25, -0.125, -1.5, 1.25, -0.125, -1.25, -1, -0.125, -1.25, 1, -0.125, -1.5, 1.25, -0.125, -1.25, 1, -0.125, 1.5, 1.25, -0.125, 1.5, -1.25, -0.125, 1.25, -1, -0.125, -1.5, -1.25, -0.125, 1.5, -1.25, -0.125, 1.25, 1, -0.125, 1.25, -1, -0.125, 1.5, 1.25, -0.125, -1.25, 1, -0.125, 1.25, 1, -0.125, 1.5, 1.25, -0.125, 1.25, 1, -0.125, 1.5, -1.25, -0.125, 1.5, 1.25, 0.125, 1.5, 1.25, -0.125, -1.5, 1.25, -0.125, 1.5, -1.25, 0.125, 1.5, 1.25, -0.125, 1.5, 1.25, -0.125, -1.5, -1.25, -0.125, -1.25, -1, -0.125, -1.5, 1.25, 0.125, -1.5, -1.25, 0.125, -1.25, 1, 0.125, -1.25, -1, 0.125, -1.5, -1.25, 0.125, -1.25, -1, 0.125, 1.5, -1.25, -0.125, -1.5, 1.25, 0.125, -1.5, 1.25, 0.125, -1.5, -1.25, 0.125, 1.5, 1.25, 0.125, -1.5, 1.25, -0.125, -1.5, 1.25, 0.125, -1.5, 1.25, 0.125, -1.25, 1, 0.125, -1.5, -1.25, 0.125, -1.5, 1.25, 0.125, 1.25, 1, 0.125, -1.25, 1, 0.125, -1.5, -1.25, 0.125, 1.5, -1.25, -0.125, 1.5, -1.25, 0.125, 1.5, -1.25, 0.125, 1.5, 1.25, -0.125, 1.5, -1.25, 0.125, 1.5, -1.25, 0.125, -1.25, -1, 0.125, 1.25, -1, 0.125, 1.5, -1.25, 0.125, 1.25, -1, 0.125, 1.5, 1.25, 0.125, 1.5, 1.25, 0.125, 1.25, 1, 0.125, -1.5, 1.25, 0.125, -1.25, -1, -0.125, -1.25, -1, -0.125, 1.25, -1, -0.125, -1.25, 1, -0.125, -1.25, -1, 0.125, -1.25, 1, -0.125, 1.25, -1, -0.125, 1.25, 1, 0.125, 1.25, -1, 0.125, 1.25, 1, -0.125, 1.25, 1, -0.125, -1.25, 1, -0.125, -1.25, 1, 0.125, -1.25, 1, 0.125, 1.25, 1, -0.125, 1.25, -1, 0.125, 1.25, -1, 0.125, -1.25, -1, 0.125, 1.25, 1, 0.125, 1.25, -1, -0.125, 1.25, 1, 0.125, -1.25, -1, 0.125, -1.25, 1, -0.125, -1.25, -1)
[sub_resource type="ArrayMesh" id="ArrayMesh_b88kk"]
_surfaces = [{
"aabb": AABB(-0.125, -1.5, -1.25, 0.25, 3, 2.5),
"attribute_data": PackedByteArray("AAAAAAAAAACrqqo9ZmZmP6uqqj3NzMw9AACAPwAAAACrqqo9zczMPauqaj/NzMw9AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD+rqmo/zczMPauqaj9mZmY/AACAPwAAgD+rqmo/ZmZmPwAAAAAAAIA/AAAAAAAAAACrqqo9zczMPQAAgD8AAAAAAAAAAAAAAACrqqo9ZmZmP6uqqj3NzMw9AAAAAAAAgD+rqmo/ZmZmP6uqqj1mZmY/AAAAAAAAgD+rqqo9ZmZmPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AACAPwAAAACrqmo/zczMPQAAgD8AAIA/AACAPwAAgD+rqmo/zczMPauqaj9mZmY/AACAPwAAgD+rqmo/ZmZmPwAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAACrqmo/zczMPQAAgD8AAIA/AACAPwAAAACrqqo9zczMPauqaj/NzMw9AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD+rqmo/ZmZmP6uqqj1mZmY/AAAAAAAAgD+rqqo9ZmZmPwAAAAAAAAAAAAAAAAAAAACrqqo9zczMPQAAgD8AAAAAAADAPgAAAAAAACA/AAAAAAAAID8AAIA/AAAAAAAAwD4AAIA/AADAPgAAAAAAACA/AACAPwAAID8AAAAAAAAgPwAAgD8AAMA+AADAPgAAAAAAACA/AAAAAAAAID8AAIA/AAAgPwAAgD8AAMA+AACAPwAAwD4AAAAAAAAgPwAAgD8AAMA+AACAPwAAwD4AAAAAAAAAAAAAwD4AAIA/AADAPgAAAAAAACA/AACAPwAAID8AAAAAAAAgPwAAgD8AAMA+"),
"format": 34359738391,
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 96,
"vertex_data": PackedByteArray("AAAAPgAAwD8AAKA/AAAAPgAAoD8AAIC/AAAAPgAAoD8AAIA/AAAAvgAAwL8AAKC/AAAAvgAAoD8AAIC/AAAAvgAAoL8AAIC/AAAAvgAAwL8AAKA/AAAAPgAAwL8AAKC/AAAAvgAAwL8AAKC/AAAAPgAAwL8AAKC/AAAAvgAAwD8AAKC/AAAAvgAAwL8AAKC/AAAAvgAAwL8AAKA/AAAAvgAAoL8AAIC/AAAAvgAAoL8AAIA/AAAAvgAAwL8AAKA/AAAAvgAAoL8AAIA/AAAAvgAAwD8AAKA/AAAAvgAAwD8AAKC/AAAAvgAAoD8AAIC/AAAAvgAAwL8AAKC/AAAAvgAAwD8AAKC/AAAAvgAAoD8AAIA/AAAAvgAAoD8AAIC/AAAAvgAAwD8AAKA/AAAAvgAAoL8AAIA/AAAAvgAAoD8AAIA/AAAAvgAAwD8AAKA/AAAAvgAAoD8AAIA/AAAAvgAAwD8AAKC/AAAAvgAAwD8AAKA/AAAAPgAAwD8AAKA/AAAAvgAAwL8AAKA/AAAAvgAAwD8AAKC/AAAAPgAAwD8AAKA/AAAAvgAAwD8AAKA/AAAAvgAAwL8AAKC/AAAAvgAAoL8AAIC/AAAAvgAAwL8AAKA/AAAAPgAAwL8AAKC/AAAAPgAAoL8AAIA/AAAAPgAAoL8AAIC/AAAAPgAAwL8AAKC/AAAAPgAAoL8AAIC/AAAAPgAAwD8AAKC/AAAAvgAAwL8AAKA/AAAAPgAAwL8AAKA/AAAAPgAAwL8AAKC/AAAAPgAAwD8AAKA/AAAAPgAAwL8AAKA/AAAAvgAAwL8AAKA/AAAAPgAAwL8AAKA/AAAAPgAAoL8AAIA/AAAAPgAAwL8AAKC/AAAAPgAAwL8AAKA/AAAAPgAAoD8AAIA/AAAAPgAAoL8AAIA/AAAAPgAAwL8AAKC/AAAAPgAAwD8AAKC/AAAAvgAAwD8AAKC/AAAAPgAAwD8AAKC/AAAAPgAAwD8AAKA/AAAAvgAAwD8AAKC/AAAAPgAAwD8AAKC/AAAAPgAAoL8AAIC/AAAAPgAAoD8AAIC/AAAAPgAAwD8AAKC/AAAAPgAAoD8AAIC/AAAAPgAAwD8AAKA/AAAAPgAAwD8AAKA/AAAAPgAAoD8AAIA/AAAAPgAAwL8AAKA/AAAAPgAAoL8AAIC/AAAAvgAAoL8AAIC/AAAAvgAAoD8AAIC/AAAAvgAAoL8AAIA/AAAAvgAAoL8AAIC/AAAAPgAAoL8AAIA/AAAAvgAAoD8AAIC/AAAAvgAAoD8AAIA/AAAAPgAAoD8AAIC/AAAAPgAAoD8AAIA/AAAAvgAAoD8AAIA/AAAAvgAAoL8AAIA/AAAAvgAAoL8AAIA/AAAAPgAAoL8AAIA/AAAAPgAAoD8AAIA/AAAAvgAAoD8AAIC/AAAAPgAAoD8AAIC/AAAAPgAAoL8AAIC/AAAAPgAAoD8AAIA/AAAAPgAAoD8AAIC/AAAAvgAAoD8AAIA/AAAAPgAAoL8AAIC/AAAAPgAAoL8AAIA/AAAAvgAAoL8AAIC/////f/9//n////9//3/+f////3//f/5/AAD/f/9//n8AAP9//3/+fwAA/3//f/5//38AAP//AAD/fwAA//8AAP9/AAD//wAA/////wAA/z//////AAD/P/////8AAP8/AAD/f/9//n8AAP9//3/+fwAA/3//f/5/AAD/f/9//n8AAP9//3/+fwAA/3//f/5/AAD/f/9//n8AAP9//3/+fwAA/3//f/5/AAD/f/9//n8AAP9//3/+fwAA/3//f/5/AAD/f/9//n8AAP9//3/+fwAA/3//f/5/AAD/f/9//n8AAP9//3/+fwAA/3//f/5//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//////AAD/f/////8AAP9//////wAAAAD/f/9//n8AAP9//3/+fwAA/3//f/5/////f/9//n////9//3/+f////3//f/5/////f/9//n////9//3/+f////3//f/5//38AAP//AAD/fwAA//8AAP9/AAD//wAA/3//fwAA/z//f/9/AAD/P/9//38AAP8/////f/9//n////9//3/+f////3//f/5/////f/9//n////9//3/+f////3//f/5//////wAA/z//////AAD/P/////8AAP8//3//////AAD/f/////8AAP9//////wAA////f/9//n////9//3/+f////3//f/5/////f/9//n////9//3/+f////3//f/5/////f/9//n////9//3/+f////3//f/5//3//fwAA/7//f/9/AAD/v/9//38AAP+//3//////////f/////////9//////////38AAP//////fwAA//////9/AAD//////////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//3//fwAA/7//f/9/AAD/v/9//38AAP+//38AAP//////fwAA//////9/AAD//////3//////////f/////////9/////////")
}]
[sub_resource type="BoxMesh" id="BoxMesh_pvdtj"]
material = ExtResource("6_2ffpc")
[sub_resource type="ArrayMesh" id="ArrayMesh_pccqs"]
_surfaces = [{
"aabb": AABB(-1, -1.25, -0.1, 2, 2.5, 0.1),
"attribute_data": PackedByteArray("AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAgD/NzEw9CtcjPTMzcz8K1yM9zcxMPY/CdT8zM3M/j8J1Pw=="),
"format": 34359742487,
"index_count": 30,
"index_data": PackedByteArray("AAABAAQABAABAAUAAQADAAUABQADAAcAAwACAAcABwACAAYAAgAAAAYABgAAAAQABAAFAAYABgAFAAcA"),
"name": "Portal Material",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("AACAvwAAoD8AAAAAAACAPwAAoD8AAAAAAACAvwAAoL8AAAAAAACAPwAAoL8AAAAAZmZmvzMzkz/NzMy9ZmZmPzMzkz/NzMy9ZmZmvzMzk7/NzMy9ZmZmPzMzk7/NzMy9/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgA==")
}]
[sub_resource type="SphereMesh" id="SphereMesh_pccqs"]
material = ExtResource("8_dhtg5")
[sub_resource type="ArrayMesh" id="ArrayMesh_pvdtj"]
_surfaces = [{
"aabb": AABB(-1, -1.25, -0.1, 2, 2.5, 0.1),
"attribute_data": PackedByteArray("AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAgD/NzEw9CtcjPTMzcz8K1yM9zcxMPY/CdT8zM3M/j8J1Pw=="),
"format": 34359742487,
"index_count": 30,
"index_data": PackedByteArray("AAABAAQABAABAAUAAQADAAUABQADAAcAAwACAAcABwACAAYAAgAAAAYABgAAAAQABAAFAAYABgAFAAcA"),
"name": "Portal Material",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("AACAvwAAoD8AAAAAAACAPwAAoD8AAAAAAACAvwAAoL8AAAAAAACAPwAAoL8AAAAAZmZmvzMzkz/NzMy9ZmZmPzMzkz/NzMy9ZmZmvzMzk7/NzMy9ZmZmPzMzk7/NzMy9/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgA==")
}]
[node name="World" type="Node3D"]
script = ExtResource("1_pccqs")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
shadow_enabled = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_b88kk")
[node name="PortalEnvironmentAdapter" parent="." node_paths=PackedStringArray("base") instance=ExtResource("1_eoaoo")]
base = NodePath("../WorldEnvironment")
[node name="HUD" parent="." instance=ExtResource("4_t21k5")]
offset_right = 219.0
offset_bottom = 77.0
[node name="TransportToGreen" type="Button" parent="HUD"]
layout_mode = 2
text = "Transport to green"
[node name="TransportToOrange" type="Button" parent="HUD"]
layout_mode = 2
text = "Transport to orange"
[node name="Player" parent="." instance=ExtResource("3_ysrn6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.68722, 0.942887, -13.7212)
[node name="Orange" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -12)
[node name="Room" type="Node3D" parent="Orange"]
[node name="Wall" type="StaticBody3D" parent="Orange/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
[node name="CSGBakedCollisionShape3D" type="CollisionShape3D" parent="Orange/Room/Wall"]
visible = false
shape = SubResource("ConcavePolygonShape3D_7nftg")
[node name="CSGBakedMeshInstance3D" type="MeshInstance3D" parent="Orange/Room/Wall"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
visible = false
material_override = ExtResource("1_v6nyh")
mesh = SubResource("ArrayMesh_v6nyh")
[node name="Ground" type="StaticBody3D" parent="Orange/Room"]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Orange/Room/Ground"]
material_override = ExtResource("2_5nkxg")
mesh = SubResource("BoxMesh_ysrn6")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Orange/Room/Ground"]
shape = SubResource("ConvexPolygonShape3D_5nkxg")
[node name="PortalFrame" type="StaticBody3D" parent="Orange/Room"]
[node name="PortalFrame" type="CollisionShape3D" parent="Orange/Room/PortalFrame"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
shape = SubResource("ConcavePolygonShape3D_eoaoo")
[node name="PortalFrameCollision" type="MeshInstance3D" parent="Orange/Room/PortalFrame"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
material_override = ExtResource("1_v6nyh")
mesh = SubResource("ArrayMesh_b88kk")
[node name="Cube" type="MeshInstance3D" parent="Orange/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.5, 1, -3.5)
mesh = SubResource("BoxMesh_pvdtj")
skeleton = NodePath("../..")
[node name="Cube2" type="MeshInstance3D" parent="Orange/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 1, -6.5)
mesh = SubResource("BoxMesh_pvdtj")
skeleton = NodePath("../..")
[node name="Cube3" type="MeshInstance3D" parent="Orange/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.5, 1, -1)
mesh = SubResource("BoxMesh_pvdtj")
skeleton = NodePath("../..")
[node name="Cube4" type="MeshInstance3D" parent="Orange/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 1, 1)
mesh = SubResource("BoxMesh_pvdtj")
skeleton = NodePath("../..")
[node name="Cube5" type="MeshInstance3D" parent="Orange/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 1, 3)
mesh = SubResource("BoxMesh_pvdtj")
skeleton = NodePath("../..")
[node name="OrangePortal" type="MeshInstance3D" parent="Orange" node_paths=PackedStringArray("exit_portal")]
transform = Transform3D(1.31134e-07, 0, -1, 0, 1, 0, 1, 0, 1.31134e-07, 0, 2, 0)
mesh = SubResource("ArrayMesh_pccqs")
skeleton = NodePath("../..")
script = ExtResource("7_pvdtj")
exit_portal = NodePath("../../Green/GreenPortal")
[node name="Green" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 12.5)
[node name="Room" type="Node3D" parent="Green"]
[node name="Wall" type="StaticBody3D" parent="Green/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
[node name="CSGBakedCollisionShape3D" type="CollisionShape3D" parent="Green/Room/Wall"]
visible = false
shape = SubResource("ConcavePolygonShape3D_7nftg")
[node name="CSGBakedMeshInstance3D" type="MeshInstance3D" parent="Green/Room/Wall"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
visible = false
material_override = ExtResource("1_v6nyh")
mesh = SubResource("ArrayMesh_v6nyh")
[node name="Ground" type="StaticBody3D" parent="Green/Room"]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Green/Room/Ground"]
material_override = ExtResource("6_b88kk")
mesh = SubResource("BoxMesh_ysrn6")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Green/Room/Ground"]
shape = SubResource("ConvexPolygonShape3D_5nkxg")
[node name="PortalFrame" type="StaticBody3D" parent="Green/Room"]
[node name="PortalFrame" type="CollisionShape3D" parent="Green/Room/PortalFrame"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
shape = SubResource("ConcavePolygonShape3D_eoaoo")
[node name="PortalFrameCollision" type="MeshInstance3D" parent="Green/Room/PortalFrame"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
material_override = ExtResource("1_v6nyh")
mesh = SubResource("ArrayMesh_b88kk")
[node name="Ball6" type="MeshInstance3D" parent="Green/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.5, 1, -3.5)
mesh = SubResource("SphereMesh_pccqs")
skeleton = NodePath("../../../Orange")
[node name="Ball7" type="MeshInstance3D" parent="Green/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 1, -6.5)
mesh = SubResource("SphereMesh_pccqs")
skeleton = NodePath("../../../Orange")
[node name="Ball8" type="MeshInstance3D" parent="Green/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.5, 1, -1)
mesh = SubResource("SphereMesh_pccqs")
skeleton = NodePath("../../../Orange")
[node name="Ball9" type="MeshInstance3D" parent="Green/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 1, 1)
mesh = SubResource("SphereMesh_pccqs")
skeleton = NodePath("../../../Orange")
[node name="Ball10" type="MeshInstance3D" parent="Green/Room"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 1, 3)
mesh = SubResource("SphereMesh_pccqs")
skeleton = NodePath("../../../Orange")
[node name="GreenPortal" type="MeshInstance3D" parent="Green" node_paths=PackedStringArray("exit_portal")]
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0, 2, 0)
mesh = SubResource("ArrayMesh_pvdtj")
skeleton = NodePath("../..")
script = ExtResource("7_pvdtj")
exit_portal = NodePath("../../Orange/OrangePortal")
[node name="ProceduralMeshMaker" parent="." node_paths=PackedStringArray("portal") instance=ExtResource("9_dhtg5")]
portal = NodePath("../Orange/OrangePortal")
height = 2.5
width = 2.0
[connection signal="pressed" from="HUD/TransportToGreen" to="." method="_on_transport_to_green_pressed"]
[connection signal="pressed" from="HUD/TransportToOrange" to="." method="_on_transport_to_orange_pressed"]

View File

@ -0,0 +1,159 @@
[gd_scene load_steps=19 format=4 uid="uid://b2c27cvkqvhbi"]
[ext_resource type="Material" uid="uid://bx6qeabdhq2s" path="res://addons/kenney_prototype_tools/materials/dark/material_01.tres" id="1_kvwhs"]
[ext_resource type="PackedScene" uid="uid://b5x7fmpwck335" path="res://hud.tscn" id="2_hpe2j"]
[ext_resource type="PackedScene" uid="uid://cgdlowfuuorvi" path="res://player.tscn" id="3_mc50s"]
[ext_resource type="Script" uid="uid://d2bvvjsibau8c" path="res://addons/simple-portal-system/scripts/portal.gd" id="4_hg00i"]
[ext_resource type="PackedScene" uid="uid://d1dtxvwk86ple" path="res://procedural_mesh_maker.tscn" id="5_ka6qt"]
[ext_resource type="PackedScene" uid="uid://cxopylew5786r" path="res://portal_environment_adapter.tscn" id="6_ka6qt"]
[ext_resource type="PackedScene" uid="uid://dn8qt0qwx4sfs" path="res://addons/kenney_prototype_tools/scenes/orange/orange_02.tscn" id="7_o2k5d"]
[ext_resource type="Material" uid="uid://uylhy3ucrinn" path="res://addons/kenney_prototype_tools/materials/purple/material_04.tres" id="8_25cs0"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sjb6i"]
sky_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
ground_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
[sub_resource type="Sky" id="Sky_o2k5d"]
sky_material = SubResource("ProceduralSkyMaterial_sjb6i")
[sub_resource type="Environment" id="Environment_25cs0"]
background_mode = 2
sky = SubResource("Sky_o2k5d")
tonemap_mode = 2
glow_enabled = true
[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_fdix6"]
points = PackedVector3Array(-10, -0.5, -10, -10, 0.5, -10, 10, -0.5, -10, -10, -0.5, 10, -10, 0.5, 10, 10, 0.5, -10, 10, -0.5, 10, 10, 0.5, 10)
[sub_resource type="BoxMesh" id="BoxMesh_hpe2j"]
size = Vector3(20, 1, 20)
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_hg00i"]
data = PackedVector3Array(-1, -1.125, -0.25, -1, -1.125, 0.25, -0.75, -1.125, -0.25, 0.75, 0.875, 0.25, 1, 1.125, 0.25, 0.75, -1.125, 0.25, -1, -1.125, 0.25, -1, -1.125, -0.25, -1, 1.125, -0.25, -0.75, -1.125, -0.25, -1, -1.125, 0.25, -0.75, -1.125, 0.25, -0.75, -1.125, 0.25, -1, -1.125, 0.25, -0.75, 0.875, 0.25, -0.75, 0.875, -0.25, -1, 1.125, -0.25, -0.75, -1.125, -0.25, -0.75, 0.875, -0.25, 1, 1.125, -0.25, -1, 1.125, -0.25, -1, 1.125, -0.25, -1, 1.125, 0.25, -1, -1.125, 0.25, -1, 1.125, 0.25, -0.75, 0.875, 0.25, -1, -1.125, 0.25, -1, 1.125, -0.25, 1, 1.125, 0.25, -1, 1.125, 0.25, -0.75, 0.875, 0.25, -1, 1.125, 0.25, 0.75, 0.875, 0.25, 1, -1.125, -0.25, 0.75, -1.125, -0.25, 1, -1.125, 0.25, -1, -1.125, -0.25, -0.75, -1.125, -0.25, -1, 1.125, -0.25, 0.75, -1.125, -0.25, 1, -1.125, -0.25, 0.75, 0.875, -0.25, 1, -1.125, 0.25, 0.75, -1.125, -0.25, 0.75, -1.125, 0.25, 1, -1.125, 0.25, 1, 1.125, 0.25, 1, -1.125, -0.25, 1, -1.125, 0.25, 0.75, -1.125, 0.25, 1, 1.125, 0.25, 0.75, 0.875, -0.25, 1, 1.125, -0.25, -0.75, 0.875, -0.25, 1, 1.125, -0.25, 1, 1.125, 0.25, -1, 1.125, -0.25, 1, 1.125, -0.25, 0.75, 0.875, -0.25, 1, -1.125, -0.25, 1, 1.125, 0.25, 1, 1.125, -0.25, 1, -1.125, -0.25, 0.75, 0.875, 0.25, -1, 1.125, 0.25, 1, 1.125, 0.25, -0.75, 0.875, 0.25, -0.75, -1.125, -0.25, -0.75, -1.125, 0.25, -0.75, -1.125, -0.25, -0.75, 0.875, 0.25, -0.75, 0.875, -0.25, 0.75, 0.875, -0.25, 0.75, -1.125, 0.25, 0.75, -1.125, -0.25, 0.75, 0.875, 0.25, 0.75, 0.875, -0.25, -0.75, 0.875, -0.25, 0.75, -1.125, 0.25, 0.75, 0.875, -0.25, 0.75, 0.875, 0.25, -0.75, 0.875, -0.25, -0.75, 0.875, 0.25, 0.75, 0.875, 0.25)
[sub_resource type="ArrayMesh" id="ArrayMesh_ka6qt"]
_surfaces = [{
"aabb": AABB(-1, -1.125, -0.25, 2, 2.25, 0.5),
"attribute_data": PackedByteArray("AACAPwAAAAAAAAAAAAAAAAAAgD8AAAA+AAAAPjmO4z0AAAAAAAAAAAAAAD4AAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAPwAAAD4AAAAAAAAAAAAAAAAAAAA+AABgPwAAgD8AAIA/AACAPwAAYD85juM9AABgPzmOYz8AAIA/AACAPwAAYD8AAAAAAABgPzmOYz8AAAAAAACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAGA/OY7jPQAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AABgPzmO4z0AAIA/AAAAAAAAAD45juM9AACAPwAAgD8AAIA/AABgPwAAAAAAAIA/AACAPwAAAAAAAGA/AAAAAAAAgD8AAIA/AAAAPgAAAAAAAAAAAAAAAAAAAD45jmM/AAAAAAAAgD8AAIA/AABgPwAAAAAAAGA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAA+AACAPwAAAAAAAAAAAAAAPjmOYz8AAAAAAACAPwAAYD85jmM/AACAPwAAAAAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAA+OY5jPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AAAAPjmO4z0AAIA/AAAAAAAAAAAAAAAAAAAAAAAAQD8AAIA/AACAPgAAgD8AAEA/AACAPwAAgD4AAAAAAABAPwAAAAAAAIA+AAAAAAAAQD8AAIA/AACAPgAAgD8AAEA/AACAPgAAAAAAAEA/AAAAAAAAQD8AAIA/AACAPwAAgD4AAAAAAABAPwAAAAAAAIA+AABAPwAAgD8AAIA+AACAPwAAgD4AAAAA"),
"format": 34359738391,
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 84,
"vertex_data": PackedByteArray("AACAvwAAkL8AAIC+AACAvwAAkL8AAIA+AABAvwAAkL8AAIC+AABAPwAAYD8AAIA+AACAPwAAkD8AAIA+AABAPwAAkL8AAIA+AACAvwAAkL8AAIA+AACAvwAAkL8AAIC+AACAvwAAkD8AAIC+AABAvwAAkL8AAIC+AACAvwAAkL8AAIA+AABAvwAAkL8AAIA+AABAvwAAkL8AAIA+AACAvwAAkL8AAIA+AABAvwAAYD8AAIA+AABAvwAAYD8AAIC+AACAvwAAkD8AAIC+AABAvwAAkL8AAIC+AABAvwAAYD8AAIC+AACAPwAAkD8AAIC+AACAvwAAkD8AAIC+AACAvwAAkD8AAIC+AACAvwAAkD8AAIA+AACAvwAAkL8AAIA+AACAvwAAkD8AAIA+AABAvwAAYD8AAIA+AACAvwAAkL8AAIA+AACAvwAAkD8AAIC+AACAPwAAkD8AAIA+AACAvwAAkD8AAIA+AABAvwAAYD8AAIA+AACAvwAAkD8AAIA+AABAPwAAYD8AAIA+AACAPwAAkL8AAIC+AABAPwAAkL8AAIC+AACAPwAAkL8AAIA+AACAvwAAkL8AAIC+AABAvwAAkL8AAIC+AACAvwAAkD8AAIC+AABAPwAAkL8AAIC+AACAPwAAkL8AAIC+AABAPwAAYD8AAIC+AACAPwAAkL8AAIA+AABAPwAAkL8AAIC+AABAPwAAkL8AAIA+AACAPwAAkL8AAIA+AACAPwAAkD8AAIA+AACAPwAAkL8AAIC+AACAPwAAkL8AAIA+AABAPwAAkL8AAIA+AACAPwAAkD8AAIA+AABAPwAAYD8AAIC+AACAPwAAkD8AAIC+AABAvwAAYD8AAIC+AACAPwAAkD8AAIC+AACAPwAAkD8AAIA+AACAvwAAkD8AAIC+AACAPwAAkD8AAIC+AABAPwAAYD8AAIC+AACAPwAAkL8AAIC+AACAPwAAkD8AAIA+AACAPwAAkD8AAIC+AACAPwAAkL8AAIC+AABAPwAAYD8AAIA+AACAvwAAkD8AAIA+AACAPwAAkD8AAIA+AABAvwAAYD8AAIA+AABAvwAAkL8AAIC+AABAvwAAkL8AAIA+AABAvwAAkL8AAIC+AABAvwAAYD8AAIA+AABAvwAAYD8AAIC+AABAPwAAYD8AAIC+AABAPwAAkL8AAIA+AABAPwAAkL8AAIC+AABAPwAAYD8AAIA+AABAPwAAYD8AAIC+AABAvwAAYD8AAIC+AABAPwAAkL8AAIA+AABAPwAAYD8AAIC+AABAPwAAYD8AAIA+AABAvwAAYD8AAIC+AABAvwAAYD8AAIA+AABAPwAAYD8AAIA+/38AAP//AAD/fwAA//8AAP9/AAD//wAA/3//fwAA/z//f/9/AAD/P/9//38AAP8/AAD/f/9//n8AAP9//3/+fwAA/3//f/5//38AAP//AAD/fwAA//8AAP9/AAD//wAA/3//fwAA/z//f/9/AAD/P/9//38AAP8//////wAA/z//////AAD/P/////8AAP8//////wAA/z//////AAD/P/////8AAP8/AAD/f/9//n8AAP9//3/+fwAA/3//f/5//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//////AAD/f/////8AAP9//////wAA/3//fwAA/z//f/9/AAD/P/9//38AAP8//38AAP//AAD/fwAA//8AAP9/AAD//wAA/////wAA/z//////AAD/P/////8AAP8//////wAA/z//////AAD/P/////8AAP8//38AAP//AAD/fwAA//8AAP9/AAD//wAA////f/9//n////9//3/+f////3//f/5//3//fwAA/z//f/9/AAD/P/9//38AAP8//////wAA/z//////AAD/P/////8AAP8//3//////AAD/f/////8AAP9//////wAA/////wAA/z//////AAD/P/////8AAP8/////f/9//n////9//3/+f////3//f/5//3//fwAA/z//f/9/AAD/P/9//38AAP8/////f/9/AID///9//38AgP///3//fwCA////f/9/AID///9//38AgP///3//fwCAAAD/f/9/AIAAAP9//38AgAAA/3//fwCA/38AAP//////fwAA//////9/AAD/////AAD/f/9/AIAAAP9//38AgAAA/3//fwCA/38AAP//////fwAA//////9/AAD/////")
}]
[sub_resource type="ArrayMesh" id="ArrayMesh_hg00i"]
_surfaces = [{
"aabb": AABB(-0.75, -1, -0.1, 1.5, 2, 0.1),
"attribute_data": PackedByteArray("AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAgD+JiIg9zcxMPe/ubj/NzEw9iYiIPTMzcz/v7m4/MzNzPw=="),
"format": 34359742487,
"index_count": 30,
"index_data": PackedByteArray("AAABAAQABAABAAUAAQADAAUABQADAAcAAwACAAcABwACAAYAAgAAAAYABgAAAAQABAAFAAYABgAFAAcA"),
"name": "Portal Material",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("AABAvwAAgD8AAAAAAABAPwAAgD8AAAAAAABAvwAAgL8AAAAAAABAPwAAgL8AAAAAZmYmv2ZmZj/NzMy9ZmYmP2ZmZj/NzMy9ZmYmv2ZmZr/NzMy9ZmYmP2ZmZr/NzMy9/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgA==")
}]
[sub_resource type="ArrayMesh" id="ArrayMesh_sjb6i"]
_surfaces = [{
"aabb": AABB(-0.75, -1, -0.1, 1.5, 2, 0.1),
"attribute_data": PackedByteArray("AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAgD+JiIg9zcxMPe/ubj/NzEw9iYiIPTMzcz/v7m4/MzNzPw=="),
"format": 34359742487,
"index_count": 30,
"index_data": PackedByteArray("AAABAAQABAABAAUAAQADAAUABQADAAcAAwACAAcABwACAAYAAgAAAAYABgAAAAQABAAFAAYABgAFAAcA"),
"name": "Portal Material",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("AABAvwAAgD8AAAAAAABAPwAAgD8AAAAAAABAvwAAgL8AAAAAAABAPwAAgL8AAAAAZmYmv2ZmZj/NzMy9ZmYmP2ZmZj/NzMy9ZmYmv2ZmZr/NzMy9ZmYmP2ZmZr/NzMy9/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgP9//3//fwCA/3//f/9/AID/f/9//38AgA==")
}]
[sub_resource type="SphereMesh" id="SphereMesh_xuajm"]
[node name="level_test_portal_delay" type="Node3D"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_25cs0")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
shadow_enabled = true
[node name="StaticBody3D" type="StaticBody3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
shape = SubResource("ConvexPolygonShape3D_fdix6")
[node name="Ground" type="MeshInstance3D" parent="StaticBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
material_override = ExtResource("1_kvwhs")
mesh = SubResource("BoxMesh_hpe2j")
skeleton = NodePath("../..")
[node name="HUD" parent="." instance=ExtResource("2_hpe2j")]
[node name="Player" parent="." instance=ExtResource("3_mc50s")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.96416, 0, 3.38931)
[node name="PortalFrame" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.374685, 0)
[node name="CSGBakedCollisionShape3D" type="CollisionShape3D" parent="PortalFrame"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
shape = SubResource("ConcavePolygonShape3D_hg00i")
[node name="CSGBakedMeshInstance3D" type="MeshInstance3D" parent="PortalFrame"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
material_override = ExtResource("1_kvwhs")
cast_shadow = 0
mesh = SubResource("ArrayMesh_ka6qt")
skeleton = NodePath("../..")
[node name="Portal_A" type="MeshInstance3D" parent="PortalFrame" node_paths=PackedStringArray("exit_portal")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.37468, 0)
cast_shadow = 0
mesh = SubResource("ArrayMesh_hg00i")
skeleton = NodePath("../..")
script = ExtResource("4_hg00i")
exit_portal = NodePath("../../PortalFrame2/Portal_B")
[node name="PortalFrame2" type="StaticBody3D" parent="."]
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 3, -0.375, -2.5)
[node name="CSGBakedCollisionShape3D" type="CollisionShape3D" parent="PortalFrame2"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
shape = SubResource("ConcavePolygonShape3D_hg00i")
[node name="CSGBakedMeshInstance3D" type="MeshInstance3D" parent="PortalFrame2"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
material_override = ExtResource("1_kvwhs")
cast_shadow = 0
mesh = SubResource("ArrayMesh_ka6qt")
skeleton = NodePath("../..")
[node name="Portal_B" type="MeshInstance3D" parent="PortalFrame2" node_paths=PackedStringArray("exit_portal")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.375, 0)
cast_shadow = 0
mesh = SubResource("ArrayMesh_sjb6i")
skeleton = NodePath("../..")
script = ExtResource("4_hg00i")
exit_portal = NodePath("../../PortalFrame/Portal_A")
[node name="ProceduralMeshMaker" parent="." node_paths=PackedStringArray("portal") instance=ExtResource("5_ka6qt")]
portal = NodePath("../PortalFrame2/Portal_B")
width = 1.5
[node name="PortalEnvironmentAdapter" parent="." node_paths=PackedStringArray("base") instance=ExtResource("6_ka6qt")]
base = NodePath("../WorldEnvironment")
[node name="Cube" parent="." instance=ExtResource("7_o2k5d")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, -4)
[node name="Mesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0.5, 3)
material_override = ExtResource("8_25cs0")
mesh = SubResource("SphereMesh_xuajm")
skeleton = NodePath("")

16
menu.gd
View File

@ -1,11 +1,12 @@
extends Control
const LEVEL_5_ROOMS: PackedScene = preload("res://levels/level_5rooms.tscn")
const LEVEL_3_ROOMS: PackedScene = preload("res://levels/level_3_rooms.tscn")
const LEVEL_PLATFORMER: PackedScene = preload("res://levels/level_platformer.tscn")
const LEVEL_SEMAPHORE_LIGHTS: PackedScene = preload("res://levels/level_semaphore_lights.tscn")
const LEVEL_CUBE_PORTALS: PackedScene = preload("res://levels/level_cube_portals.tscn")
const LEVEL_HERMIONE: PackedScene = preload("res://levels/level_hermione.tscn")
const LEVEL_5_ROOMS: PackedScene = preload("res://levels/level_5rooms.tscn")
const LEVEL_3_ROOMS: PackedScene = preload("res://levels/level_3_rooms.tscn")
const LEVEL_PLATFORMER: PackedScene = preload("res://levels/level_platformer.tscn")
const LEVEL_SEMAPHORE_LIGHTS: PackedScene = preload("res://levels/level_semaphore_lights.tscn")
const LEVEL_CUBE_PORTALS: PackedScene = preload("res://levels/level_cube_portals.tscn")
const LEVEL_HERMIONE: PackedScene = preload("res://levels/level_hermione.tscn")
const LEVEL_TEST_PORTAL_DELAY: PackedScene = preload("res://levels/level_test_portal_delay.tscn")
@onready var items_container: VBoxContainer = $CenterContainer/ItemsContainer
@ -15,7 +16,8 @@ var levels: Dictionary = {
"5 rooms": LEVEL_5_ROOMS,
"Platform": LEVEL_PLATFORMER,
"Recurisve portals - WIP": LEVEL_CUBE_PORTALS,
"Multiple Worlds": LEVEL_HERMIONE
"Multiple Worlds - WIP": LEVEL_HERMIONE,
"No Portal Delay?": LEVEL_TEST_PORTAL_DELAY
}
func _ready() -> void:

View File

@ -1,6 +1,6 @@
extends CharacterBody3D
@onready var camera: Camera3D = $Camera3D
@onready var camera: Camera3D = $PlayerCamera
const SPEED = 5.0
const JUMP_VELOCITY = 4.5

View File

@ -17,7 +17,7 @@ height = 1.75
script = ExtResource("1_4flbx")
metadata/teleportable = false
[node name="Camera3D" type="Camera3D" parent="."]
[node name="PlayerCamera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.32153, 0)
cull_mask = 1048571

View File

@ -1,6 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://jecigkibqyew"]
[ext_resource type="Script" uid="uid://d2bvvjsibau8c" path="res://addons/simple-portal-system/scripts/portal.gd" id="1_0r486"]
[node name="Portal" type="MeshInstance3D"]
script = ExtResource("1_0r486")

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://cxopylew5786r"]
[ext_resource type="Script" uid="uid://cili3lyodjqel" path="res://portal_environment_adapter.gd" id="1_5srpn"]
[node name="PortalEnvironmentAdapter" type="Node"]
script = ExtResource("1_5srpn")

View File

@ -1,29 +1,35 @@
@tool
extends Node
@export_range(0.1, 10, 0.01) var height: float = 2.0
@export_range(0.1, 10, 0.01) var width: float = 1.0
## Player camera's NEAR clip distance
@export_range(0, 0.2, 0.001) var indent: float = 0.1
@export_tool_button("Generate Portal Mesh")
var _generate_portal_mesh: Callable = generate_portal_mesh
@export_tool_button("Remove Mesh")
var _remove_mesh: Callable = func(): portal.mesh = null
@export var portal: Portal
@export_range(0.1, 10, 0.01) var height: float = 2.0:
set(v):
height = v
if Engine.is_editor_hint(): generate_portal_mesh()
@export_range(0.1, 10, 0.01) var width: float = 1.0:
set(v):
width = v
if Engine.is_editor_hint(): generate_portal_mesh()
## Player camera's NEAR clip distance
@export_range(0, 0.2, 0.001) var indent: float = 0.1:
set(v):
indent = v
generate_portal_mesh()
@export var portal_material: BaseMaterial3D
func _add_inspector_buttons() -> Array:
var buttons: Array = []
buttons.push_back({
"name": "Generate Portal Mesh",
"pressed": generate_portal_mesh
})
buttons.push_back({
"name": "Remove Mesh",
"pressed": func(): portal.mesh = null
})
return buttons
func _get_configuration_warnings() -> PackedStringArray:
var warnings: Array = []

View File

@ -14,6 +14,7 @@ config/name="DP Konzultace"
run/main_scene="uid://cim3ul77o0ipr"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
config/tags=PackedStringArray("dp")
[display]

100
scripts/my_portal.gd Normal file
View File

@ -0,0 +1,100 @@
extends MeshInstance3D
class_name MyPortal
## My own portal implementation
@export var exit_portal: MyPortal
#preload("res://scripts/my_portal_material.tres")
const MY_PORTAL_MATERIAL = preload("uid://cwobk5ik5k5aj")
var _viewport: SubViewport
var _camera: Camera3D = Camera3D.new()
var _main_camera: Camera3D = null
var exit_scale: int = 1 # This is not really needed I think
func _ready() -> void:
assert(exit_portal != null)
# Update portals last
process_priority = 1000
_main_camera = get_viewport().get_camera_3d()
# NOTE: In case the exit portal is in a different world, better put the viewport and camera
# onto IT, rather than me (?)
_viewport = SubViewport.new()
_viewport.name = name + "_Viewport"
_viewport.size = get_viewport().size
add_child(_viewport)
_camera.name = name + "_ExitCamera"
_viewport.add_child(_camera)
# FIXME: Remove this - debug only
var debug = MeshInstance3D.new()
debug.rotate_z(PI / 2)
debug.material_override = StandardMaterial3D.new()
debug.material_override.albedo_color = Color.MAGENTA
debug.mesh = PrismMesh.new()
_camera.add_child(debug)
var mat: ShaderMaterial = MY_PORTAL_MATERIAL
material_override = mat
mat.set_shader_parameter("albedo", _viewport.get_texture())
print(name, ": _ready")
func _process(delta: float) -> void:
_camera.global_transform = real_to_exit_transform(_main_camera.global_transform)
# TODO: Adjust near clip plane to the portal
_camera.near = _main_camera.near
_camera.far = _main_camera.far
_camera.fov = _main_camera.fov
_camera.keep_aspect = _main_camera.keep_aspect
## Return a new Transform3D relative to the exit portal based on the real Transform3D relative to this portal.
func real_to_exit_transform(real:Transform3D) -> Transform3D:
# Convert from global space to local space at the entrance (this) portal
var local:Transform3D = global_transform.affine_inverse() * real
# Compensate for any scale the entrance portal may have
var unscaled:Transform3D = local.scaled(global_transform.basis.get_scale())
# Flip it (the portal always flips the view 180 degrees)
var flipped:Transform3D = unscaled.rotated(Vector3.UP, PI)
# Apply any scale the exit portal may have (and apply custom exit scale)
var exit_scale_vector:Vector3 = exit_portal.global_transform.basis.get_scale()
var scaled_at_exit:Transform3D = flipped.scaled(Vector3.ONE / exit_scale_vector * exit_scale)
# Convert from local space at the exit portal to global space
var local_at_exit:Transform3D = exit_portal.global_transform * scaled_at_exit
return local_at_exit
## Return a new position relative to the exit portal based on the real position relative to this portal.
func real_to_exit_position(real:Vector3) -> Vector3:
# Convert from global space to local space at the entrance (this) portal
var local:Vector3 = global_transform.affine_inverse() * real
# Compensate for any scale the entrance portal may have
var unscaled:Vector3 = local * global_transform.basis.get_scale()
# Apply any scale the exit portal may have (and apply custom exit scale)
var exit_scale_vector:Vector3 = Vector3(-1, 1, 1) * exit_portal.global_transform.basis.get_scale()
var scaled_at_exit:Vector3 = unscaled / exit_scale_vector * exit_scale
# Convert from local space at the exit portal to global space
var local_at_exit:Vector3 = exit_portal.global_transform * scaled_at_exit
return local_at_exit
## Return a new direction relative to the exit portal based on the real direction relative to this portal.
func real_to_exit_direction(real:Vector3) -> Vector3:
# Convert from global to local space at the entrance (this) portal
var local:Vector3 = global_transform.basis.inverse() * real
# Compensate for any scale the entrance portal may have
var unscaled:Vector3 = local * global_transform.basis.get_scale()
# Flip it (the portal always flips the view 180 degrees)
var flipped:Vector3 = unscaled.rotated(Vector3.UP, PI)
# Apply any scale the exit portal may have (and apply custom exit scale)
var exit_scale_vector:Vector3 = exit_portal.global_transform.basis.get_scale()
var scaled_at_exit:Vector3 = flipped / exit_scale_vector * exit_scale
# Convert from local space at the exit portal to global space
var local_at_exit:Vector3 = exit_portal.global_transform.basis * scaled_at_exit
return local_at_exit

1
scripts/my_portal.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://dh8miiv7xc4ps

View File

@ -0,0 +1,25 @@
shader_type spatial;
render_mode unshaded;
uniform sampler2D albedo: hint_default_black, source_color;
varying float pixel_distance;
void vertex() {
// Calculate the world-space distance between the pixel and camera.
// Pass the distance to the fragment shader using a varying attribute.
vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
vec3 camera_position = (INV_VIEW_MATRIX * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
pixel_distance = distance(world_position, camera_position);
}
void fragment() {
// The portal color is simply the screen-space color of the exit camera render target.
// This is because the exit camera views the exit portal from the perspective of the player watching
// the entrance portal, meaning the exit portal will occupy the same screen-space as the entrance portal.
vec3 portal_color = texture(albedo, SCREEN_UV).rgb;
ALBEDO = portal_color;
}

View File

@ -0,0 +1 @@
uid://b6cs66n3vj4di

View File

@ -0,0 +1,7 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://cwobk5ik5k5aj"]
[ext_resource type="Shader" uid="uid://b6cs66n3vj4di" path="res://scripts/my_portal.gdshader" id="1_3tu08"]
[resource]
render_priority = 0
shader = ExtResource("1_3tu08")