Update portals plugin
This commit is contained in:
parent
80cf038a97
commit
5bd3226960
64
addons/portals/README.md
Normal file
64
addons/portals/README.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Portals 3D
|
||||||
|
|
||||||
|
This plugin enables you to easily create seamless plugins.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For documentation about `Portal3D`, see the portal script itself.
|
||||||
|
Everything is properly documented and viewable in the default
|
||||||
|
documentation window. Click _Script > Search Help_ and type
|
||||||
|
"Portal3D" in there.
|
||||||
|
|
||||||
|
For everything else, there is this README.
|
||||||
|
|
||||||
|
## Materials
|
||||||
|
|
||||||
|
### Portals
|
||||||
|
|
||||||
|
The portal mesh has a custom shader material assigned to it at runtime,
|
||||||
|
(defined in `materials/portal_shader.gdshader`), but in editor, it uses
|
||||||
|
a regular material -- find it at `materials/editor-preview-portal-material.tres`.
|
||||||
|
You can edit this material to customize how portals look in the editor
|
||||||
|
(in case the default gray color blends in too much).
|
||||||
|
|
||||||
|
### Smooth teleportation
|
||||||
|
|
||||||
|
The Portal3D script provides a mechanism for smooth teleportation. In order to be
|
||||||
|
able to create smooth portal transitions, you need to implement a clipping shader
|
||||||
|
on all meshes that are supposed to participate in the teleportation.
|
||||||
|
|
||||||
|
**How to convert a regular mesh to a clippable one?** Like this:
|
||||||
|
|
||||||
|
1. On your material, click the downward arrow menu and select _Convert to ShaderMaterial_
|
||||||
|
2. Include the shader macros and use them to inject clipping uniforms, the vertex logic
|
||||||
|
and the fragment logic.
|
||||||
|
|
||||||
|
```c
|
||||||
|
shader_type spatial;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
#include "res://addons/portals/materials/portalclip_mesh.gdshaderinc"
|
||||||
|
|
||||||
|
PORTALCLIP_UNIFORMS
|
||||||
|
|
||||||
|
void vertex() {
|
||||||
|
// ...
|
||||||
|
PORTALCLIP_VERTEX
|
||||||
|
}
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
// ...
|
||||||
|
PORTALCLIP_FRAGMENT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's it! Now look for `DUPLICATE_MESHES_CALLBACK` in the Portal3D script, you
|
||||||
|
are ready to get going with smooth teleportation!
|
||||||
|
|
||||||
|
## Gizmos
|
||||||
|
|
||||||
|
This plugin includes couple of custom gizmos. One gives a
|
||||||
|
connected portal an outline and the second one visualizes portal's
|
||||||
|
front direction. You can configure the color of both gizmos in
|
||||||
|
_Project Settings > Addons > Portals_ or turn them off altogether.
|
@ -1,9 +1,24 @@
|
|||||||
class_name AtExport
|
class_name AtExport extends Object
|
||||||
|
|
||||||
## Helper class for defining custom export inspector.
|
## Helper class for defining custom export inspector.
|
||||||
##
|
##
|
||||||
## Instead of [code]@export var foo: int = 0[/code] you could return
|
## Intended usage is when using [method Object._get_property_list] to define a custom editor
|
||||||
## [code]AtExport.int_("foo")[/code] in your [method Object._get_property_list]
|
## inspector. The list not exhaustive, as I didn't need every single export annotation. [br]
|
||||||
|
## [codeblock]
|
||||||
|
## @export var foo: int = 0
|
||||||
|
## [/codeblock]
|
||||||
|
## becomes
|
||||||
|
## [codeblock]
|
||||||
|
## var foo: int = 0
|
||||||
|
##
|
||||||
|
## func _get_property_list() -> void:
|
||||||
|
## return [
|
||||||
|
## AtExport.int_("health")
|
||||||
|
## ]
|
||||||
|
## [/codeblock]
|
||||||
|
## Coincidentally, the dictionaries used to register [ProjectSettings] are very similar,
|
||||||
|
## too.
|
||||||
|
|
||||||
|
|
||||||
static func _base(propname: String, type: int) -> Dictionary:
|
static func _base(propname: String, type: int) -> Dictionary:
|
||||||
return {
|
return {
|
||||||
@ -12,6 +27,7 @@ static func _base(propname: String, type: int) -> Dictionary:
|
|||||||
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE
|
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_tool_button]
|
||||||
static func button(propname: String, button_text: String, button_icon: String = "Callable") -> Dictionary:
|
static func button(propname: String, button_text: String, button_icon: String = "Callable") -> Dictionary:
|
||||||
var result := _base(propname, TYPE_CALLABLE)
|
var result := _base(propname, TYPE_CALLABLE)
|
||||||
|
|
||||||
@ -22,25 +38,43 @@ static func button(propname: String, button_text: String, button_icon: String =
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] bool variables
|
||||||
static func bool_(propname: String) -> Dictionary:
|
static func bool_(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_BOOL)
|
return _base(propname, TYPE_BOOL)
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] [Color] variables
|
||||||
static func color(propname: String) -> Dictionary:
|
static func color(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_COLOR)
|
return _base(propname, TYPE_COLOR)
|
||||||
|
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_color_no_alpha]
|
||||||
static func color_no_alpha(propname: String) -> Dictionary:
|
static func color_no_alpha(propname: String) -> Dictionary:
|
||||||
var result := _base(propname, TYPE_COLOR)
|
var result := _base(propname, TYPE_COLOR)
|
||||||
result["hint"] = PROPERTY_HINT_COLOR_NO_ALPHA
|
result["hint"] = PROPERTY_HINT_COLOR_NO_ALPHA
|
||||||
return result
|
return result
|
||||||
|
|
||||||
## Following two lines are equivalent: [br]
|
## Exporting an enum variable.[br]Example:
|
||||||
## [codeblock]
|
## [codeblock]
|
||||||
## @export var height: float
|
## var view_direction: ViewDirection
|
||||||
## AtExport.float_("height")
|
## # ...
|
||||||
|
## AtExport.enum_("view_direction", &"Portal3D.ViewDirection", ViewDirection)
|
||||||
## [/codeblock]
|
## [/codeblock]
|
||||||
|
static func enum_(propname: String, parent_and_enum: StringName, enum_class: Variant) -> Dictionary:
|
||||||
|
var result := int_(propname)
|
||||||
|
|
||||||
|
result["class_name"] = parent_and_enum
|
||||||
|
result["hint"] = PROPERTY_HINT_ENUM
|
||||||
|
result["hint_string"] = ",".join(enum_class.keys())
|
||||||
|
result["usage"] |= PROPERTY_USAGE_CLASS_IS_ENUM
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] float variables
|
||||||
static func float_(propname: String) -> Dictionary:
|
static func float_(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_FLOAT)
|
return _base(propname, TYPE_FLOAT)
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_range] with float variables.
|
||||||
|
## Also see [method int_range]
|
||||||
static func float_range(propname: String, min: float, max: float, step: float = 0.01, extra_hints: Array[String] = []) -> Dictionary:
|
static func float_range(propname: String, min: float, max: float, step: float = 0.01, extra_hints: Array[String] = []) -> Dictionary:
|
||||||
var result := float_(propname)
|
var result := float_(propname)
|
||||||
var hint_string = "%f,%f,%f" % [min, max, step]
|
var hint_string = "%f,%f,%f" % [min, max, step]
|
||||||
@ -54,45 +88,37 @@ static func float_range(propname: String, min: float, max: float, step: float =
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] integer variables
|
||||||
static func int_(propname: String) -> Dictionary:
|
static func int_(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_INT)
|
return _base(propname, TYPE_INT)
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_flags]
|
||||||
static func int_flags(propname: String, options: Array) -> Dictionary:
|
static func int_flags(propname: String, options: Array) -> Dictionary:
|
||||||
var result := int_(propname)
|
var result := int_(propname)
|
||||||
result["hint"] = PROPERTY_HINT_FLAGS
|
result["hint"] = PROPERTY_HINT_FLAGS
|
||||||
result["hint_string"] = ",".join(options)
|
result["hint_string"] = ",".join(options)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_flags_3d_physics]
|
||||||
static func int_physics_3d(propname: String) -> Dictionary:
|
static func int_physics_3d(propname: String) -> Dictionary:
|
||||||
var result := int_(propname)
|
var result := int_(propname)
|
||||||
result["hint"] = PROPERTY_HINT_LAYERS_3D_PHYSICS
|
result["hint"] = PROPERTY_HINT_LAYERS_3D_PHYSICS
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_range] with integer variables.
|
||||||
|
## Also see [method float_range]
|
||||||
static func int_range(propname: String, min: int, max: int, step: int = 1, extra_hints: Array[String] = []) -> Dictionary:
|
static func int_range(propname: String, min: int, max: int, step: int = 1, extra_hints: Array[String] = []) -> Dictionary:
|
||||||
var result := float_range(propname, min, max, step, extra_hints)
|
var result := float_range(propname, min, max, step, extra_hints)
|
||||||
result["type"] = TYPE_INT
|
result["type"] = TYPE_INT
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_flags_3d_render]
|
||||||
static func int_render_3d(propname: String) -> Dictionary:
|
static func int_render_3d(propname: String) -> Dictionary:
|
||||||
var result := int_(propname)
|
var result := int_(propname)
|
||||||
result["hint"] = PROPERTY_HINT_LAYERS_3D_RENDER
|
result["hint"] = PROPERTY_HINT_LAYERS_3D_RENDER
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_group].
|
||||||
|
|
||||||
static func enum_(propname: String, parent_and_enum: StringName, enum_class: Variant) -> Dictionary:
|
|
||||||
var result := int_(propname)
|
|
||||||
|
|
||||||
result["class_name"] = parent_and_enum
|
|
||||||
result["hint"] = PROPERTY_HINT_ENUM
|
|
||||||
result["hint_string"] = ",".join(enum_class.keys())
|
|
||||||
result["usage"] |= PROPERTY_USAGE_CLASS_IS_ENUM
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
static func group(group_name: String, prefix: String = "") -> Dictionary:
|
static func group(group_name: String, prefix: String = "") -> Dictionary:
|
||||||
var result := _base(group_name, TYPE_NIL)
|
var result := _base(group_name, TYPE_NIL)
|
||||||
# Overwrite the usage!
|
# Overwrite the usage!
|
||||||
@ -100,9 +126,18 @@ static func group(group_name: String, prefix: String = "") -> Dictionary:
|
|||||||
result["hint_string"] = prefix
|
result["hint_string"] = prefix
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## Close the group that began with [method group]. If you've supplied a prefix to [method group],
|
||||||
|
## it should close itself.
|
||||||
static func group_end() -> Dictionary:
|
static func group_end() -> Dictionary:
|
||||||
return group("")
|
return group("")
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] NodePath variables. Variables of [i]node type[/i] also only store
|
||||||
|
## [NodePath]s.
|
||||||
|
## [codeblock]
|
||||||
|
## var mesh: MeshInstance3D
|
||||||
|
## # inside _get_property_list
|
||||||
|
## AtExport.node("mesh", "MeshInstance3D")
|
||||||
|
## [/codeblock]
|
||||||
static func node(propname: String, node_class: StringName) -> Dictionary:
|
static func node(propname: String, node_class: StringName) -> Dictionary:
|
||||||
var result = _base(propname, TYPE_OBJECT)
|
var result = _base(propname, TYPE_OBJECT)
|
||||||
result["hint"] = PROPERTY_HINT_NODE_TYPE
|
result["hint"] = PROPERTY_HINT_NODE_TYPE
|
||||||
@ -110,9 +145,12 @@ static func node(propname: String, node_class: StringName) -> Dictionary:
|
|||||||
result["hint_string"] = node_class
|
result["hint_string"] = node_class
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] for [String] variables
|
||||||
static func string(propname: String) -> Dictionary:
|
static func string(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_STRING)
|
return _base(propname, TYPE_STRING)
|
||||||
|
|
||||||
|
## Replacement for [annotation @GDScript.@export_subgroup]. Only works when nested inside
|
||||||
|
## [method group].
|
||||||
static func subgroup(subgroup_name: String, prefix: String = "") -> Dictionary:
|
static func subgroup(subgroup_name: String, prefix: String = "") -> Dictionary:
|
||||||
var result := _base(subgroup_name, TYPE_NIL)
|
var result := _base(subgroup_name, TYPE_NIL)
|
||||||
# Overwrite the usage!
|
# Overwrite the usage!
|
||||||
@ -120,11 +158,14 @@ static func subgroup(subgroup_name: String, prefix: String = "") -> Dictionary:
|
|||||||
result["hint_string"] = prefix
|
result["hint_string"] = prefix
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
## Closes a subgroup created with [method subgroup]. Also see [method group_end]
|
||||||
static func subgroup_end() -> Dictionary:
|
static func subgroup_end() -> Dictionary:
|
||||||
return subgroup("")
|
return subgroup("")
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] for [Vector2] variables
|
||||||
static func vector2(propname: String) -> Dictionary:
|
static func vector2(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_VECTOR2)
|
return _base(propname, TYPE_VECTOR2)
|
||||||
|
|
||||||
|
## [annotation @GDScript.@export] for [Vector3] variables
|
||||||
static func vector3(propname: String) -> Dictionary:
|
static func vector3(propname: String) -> Dictionary:
|
||||||
return _base(propname, TYPE_VECTOR3)
|
return _base(propname, TYPE_VECTOR3)
|
||||||
|
@ -4,8 +4,22 @@ class_name Portal3D extends Node3D
|
|||||||
|
|
||||||
## Seamless 3D portal
|
## Seamless 3D portal
|
||||||
##
|
##
|
||||||
## This node is a tool script that provides configuration options for portal setup. The portal
|
## To get started, create two Portal3D instances and set their [member exit_portal] to each other.
|
||||||
## can be visual-only or also teleporting.
|
## This creates a linked portal pair that you can look through. Make your player to collide with
|
||||||
|
## [member teleport_collision_mask] and you will be able to walk back and forth through the portal.
|
||||||
|
## [br][br]
|
||||||
|
## To integrate portals into your game, you can make use of the [signal on_teleport] and
|
||||||
|
## [signal on_teleport_receive] signals. You can link a portal a different one by chaning its
|
||||||
|
## [member exit_portal] during gameplay. The next level is to make use of the portal's callbacks,
|
||||||
|
## mainly the [member ON_TELEPORT_CALLBACK]. If you need to raycast through a portal, then the
|
||||||
|
## [method forward_raycast] method might come in handy! When it comes to optimization, you can use
|
||||||
|
## the [method activate] and [method deactivate] methods to control which portals are consuming
|
||||||
|
## resources.
|
||||||
|
## [br][br]
|
||||||
|
## [b]TIP:[/b] If you change the default value of some property, it will not get synchronized into existing
|
||||||
|
## portal instances due to how Godot handles custom inspectors. For easier defaults management,
|
||||||
|
## I recommend creating a scene with Portal3D as a root and re-using that.
|
||||||
|
|
||||||
|
|
||||||
#region Public API
|
#region Public API
|
||||||
|
|
||||||
@ -16,38 +30,39 @@ signal on_teleport(node: Node3D)
|
|||||||
## its [member exit_portal] triggered a teleport!
|
## its [member exit_portal] triggered a teleport!
|
||||||
signal on_teleport_receive(node: Node3D)
|
signal on_teleport_receive(node: Node3D)
|
||||||
|
|
||||||
## The portal starts rendering again, [member portal_mesh] becomes visible and teleport
|
## Activates the portal, making it visible and teleporting again. THe assumption is that it was
|
||||||
## activates (if the portal is teleporting).[br][br]
|
## previously deactivated by [method deactivate] or [member start_deactivated]. Recreates internal
|
||||||
## Also see [method deactivate]
|
## viewports if needed.
|
||||||
func activate() -> void:
|
func activate() -> void:
|
||||||
process_mode = Node.PROCESS_MODE_INHERIT
|
process_mode = Node.PROCESS_MODE_INHERIT
|
||||||
|
|
||||||
# Viewports have been destroyed
|
|
||||||
if portal_viewport == null:
|
if portal_viewport == null:
|
||||||
|
# Viewports have been destroyed
|
||||||
_setup_cameras()
|
_setup_cameras()
|
||||||
|
|
||||||
show()
|
show()
|
||||||
|
|
||||||
|
|
||||||
## Disables processing and hides the portal. Optionally destroys the viewports, freeing memory.
|
## Disables all processing (this includes teleportation) and hides the portal. Optionally destroys
|
||||||
## Set [member start_deactivated] to [code]true[/code] to avoid viewport allocation at the start of
|
## the viewports, freeing up memory. [br][br]
|
||||||
|
## Setting [member start_deactivated] to [code]true[/code] avoid viewport allocation at the start of
|
||||||
## the game. [br][br]
|
## the game. [br][br]
|
||||||
## Also see [method activate]
|
## Deactivated portal has to be explicitly activated by calling [method activate].
|
||||||
func deactivate(destroy_viewports: bool = false) -> void:
|
func deactivate(destroy_viewports: bool = false) -> void:
|
||||||
hide()
|
hide()
|
||||||
_watchlist_teleportables.clear()
|
_watchlist_teleportables.clear()
|
||||||
|
|
||||||
if destroy_viewports:
|
if destroy_viewports:
|
||||||
if portal_viewport:
|
if portal_viewport:
|
||||||
print("[%s] freeing viewport" % name)
|
|
||||||
portal_viewport.queue_free()
|
portal_viewport.queue_free()
|
||||||
portal_viewport = null
|
portal_viewport = null
|
||||||
portal_camera = null
|
portal_camera = null
|
||||||
|
|
||||||
process_mode = Node.PROCESS_MODE_DISABLED
|
process_mode = Node.PROCESS_MODE_DISABLED
|
||||||
|
|
||||||
## If your [RayCast3D] node hits a portal that it was meant to go through, pass it to this function
|
## Helper method for checking for raycast collisions through portals. If your [RayCast3D] node hits
|
||||||
## and it will get you the next collider behind the portal.
|
## a portal collider, pass the [RayCast3D] node to this function to find out what's on the other
|
||||||
|
## side of the portal! [br][br]
|
||||||
## Uses [method PhysicsDirectSpaceState3D.intersect_ray] under the hood.[br][br]
|
## Uses [method PhysicsDirectSpaceState3D.intersect_ray] under the hood.[br][br]
|
||||||
## Also see [method forward_raycast_query].
|
## Also see [method forward_raycast_query].
|
||||||
func forward_raycast(raycast: RayCast3D) -> Dictionary:
|
func forward_raycast(raycast: RayCast3D) -> Dictionary:
|
||||||
@ -68,9 +83,10 @@ func forward_raycast(raycast: RayCast3D) -> Dictionary:
|
|||||||
return get_world_3d().direct_space_state.intersect_ray(query)
|
return get_world_3d().direct_space_state.intersect_ray(query)
|
||||||
|
|
||||||
## When doing raycasts with [method PhysicsDirectSpaceState3D.intersect_ray] and you hit a portal
|
## When doing raycasts with [method PhysicsDirectSpaceState3D.intersect_ray] and you hit a portal
|
||||||
## that you want to go through, pass the existing [PhysicsRayQueryParameters3D] to this function.
|
## that you want to go through, pass the [PhysicsRayQueryParameters3D] you are using to this
|
||||||
## It will take over the parameters and calculate the ray's continuation. [br][br]
|
## function. It will calculate the ray's continuation and execute the raycast again, returning the
|
||||||
## See [method forward_raycast] for usage with [RayCast3D].
|
## result dictionary. [br][br]
|
||||||
|
## If you are using [RayCast3D] for raycasting, see [method forward_raycast].
|
||||||
func forward_raycast_query(params: PhysicsRayQueryParameters3D) -> Dictionary:
|
func forward_raycast_query(params: PhysicsRayQueryParameters3D) -> Dictionary:
|
||||||
var start := to_exit_position(params.from)
|
var start := to_exit_position(params.from)
|
||||||
var end := to_exit_position(params.to)
|
var end := to_exit_position(params.to)
|
||||||
@ -89,24 +105,65 @@ func forward_raycast_query(params: PhysicsRayQueryParameters3D) -> Dictionary:
|
|||||||
|
|
||||||
return get_world_3d().direct_space_state.intersect_ray(query)
|
return get_world_3d().direct_space_state.intersect_ray(query)
|
||||||
|
|
||||||
|
|
||||||
|
## This method will be called on a teleported node if [member TeleportInteractions.CALLBACK]
|
||||||
|
## is checked in [member teleport_interactions]. The portal will try to call the method
|
||||||
|
## [code]on_teleport[/code] on any object being teleported by it.[br][br]
|
||||||
|
## Example:
|
||||||
|
## [codeblock]
|
||||||
|
## func on_teleport(portal: Portal3D) -> void:
|
||||||
|
## print("Teleported by %s!" % portal.name)
|
||||||
|
## [/codeblock]
|
||||||
|
const ON_TELEPORT_CALLBACK: StringName = &"on_teleport"
|
||||||
|
|
||||||
|
## This method will be called on a node that will get into close proximity of a portal that has
|
||||||
|
## [member TeleportInteractions.DUPLICATE_MESHES] turned on. The method is expected to return an
|
||||||
|
## array of [MeshInstance3D]s.[br][br]
|
||||||
|
## Example:
|
||||||
|
## [codeblock]
|
||||||
|
## @onready var character_mesh: MeshInstance = $CharacterMesh
|
||||||
|
##
|
||||||
|
## func get_teleportable_meshes() -> Array[MeshInstance3D]:
|
||||||
|
## return [character_mesh]
|
||||||
|
## [/codeblock]
|
||||||
|
##
|
||||||
|
## The returned meshes require a special material. Check out the plugin's README for more
|
||||||
|
## information!
|
||||||
|
const DUPLICATE_MESHES_CALLBACK: StringName = &"get_teleportable_meshes"
|
||||||
|
|
||||||
|
## By default, object triggering the teleport gets teleported. You can override this with a
|
||||||
|
## metadata property that contains a [NodePath]. If the metadata property is set, then the node at
|
||||||
|
## the node path will be teleported instead. Setting this to ancestor nodes is recommended.[br][br]
|
||||||
|
## Example:
|
||||||
|
## [codeblock]
|
||||||
|
## func _ready() -> void:
|
||||||
|
## self.set_meta("teleport_root", ^"..") # parent
|
||||||
|
## [/codeblock]
|
||||||
|
## Or you can set the metadata property via the inspector!
|
||||||
|
const TELEPORT_ROOT_META: StringName = &"teleport_root"
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
## Size of the portal rectangle. [br][br]
|
## Size of the portal rectangle, height and width.
|
||||||
## Detph of the portal is an implementation detail and is set automatically.
|
|
||||||
var portal_size: Vector2 = Vector2(2.0, 2.5):
|
var portal_size: Vector2 = Vector2(2.0, 2.5):
|
||||||
set(v):
|
set(v):
|
||||||
portal_size = v
|
portal_size = v
|
||||||
if caused_by_user_interaction():
|
if _caused_by_user_interaction():
|
||||||
_on_portal_size_changed()
|
_on_portal_size_changed()
|
||||||
update_configuration_warnings()
|
update_configuration_warnings()
|
||||||
if exit_portal:
|
if exit_portal:
|
||||||
exit_portal.update_configuration_warnings()
|
exit_portal.update_configuration_warnings()
|
||||||
|
|
||||||
## The exit of this particular portal. Portal camera renders what it sees through this
|
## The exit of this particular portal. Portal camera renders what it sees through this
|
||||||
## [member exit_portal]. Teleports take you here.
|
## [member exit_portal] and teleports take you here. This is a [b]required[/b] property, it
|
||||||
|
## can never be [code]null[/code].
|
||||||
## [br][br]
|
## [br][br]
|
||||||
## Two portals commonly have each other set as their exit portals, which allows you to
|
## You can change this property during gameplay to switch the portal to a different destination.
|
||||||
## travel back and forth. But this does not have to be the case!
|
## To disable a portal, see [method deactivate].
|
||||||
|
## [br][br]
|
||||||
|
## [b]TIP:[/b] Commonly, two portals have set each other as [member exit_portal], which
|
||||||
|
## allows you to travel back and forth. But you can experiment with one-way portals too!
|
||||||
var exit_portal: Portal3D:
|
var exit_portal: Portal3D:
|
||||||
set(v):
|
set(v):
|
||||||
exit_portal = v
|
exit_portal = v
|
||||||
@ -116,37 +173,28 @@ var exit_portal: Portal3D:
|
|||||||
var _tb_pair_portals: Callable = _editor_pair_portals.bind()
|
var _tb_pair_portals: Callable = _editor_pair_portals.bind()
|
||||||
var _tb_sync_portal_sizes: Callable = _editor_sync_portal_sizes.bind()
|
var _tb_sync_portal_sizes: Callable = _editor_sync_portal_sizes.bind()
|
||||||
|
|
||||||
## Manually specify the main camera. By default it's inferred as the camera rendering the
|
## Manually override what's the main camera of the scene. By default it's inferred as the camera
|
||||||
## parent viewport of the portal. You might have to specify this, if your game uses multiple
|
## rendering the parent viewport of the portal. You might have to specify this, if your game uses
|
||||||
## [SubViewport]s.
|
## multiple [SubViewport]s.
|
||||||
var player_camera: Camera3D
|
var player_camera: Camera3D
|
||||||
|
|
||||||
## [member VisualInstance3D.layers] settging for [member portal_mesh]. So that the portal cameras
|
## The portal camera sets its [member Camera3D.near] as close to the portal as possible, in an
|
||||||
## don't see other portals.[br][br]
|
## effort to clip objects close behind the portal. This value offsets the [member portal_camera]'s
|
||||||
## You can set the default in [i]Project settings > Addons > Portals[/i].
|
|
||||||
var portal_render_layer: int = 1 << 19:
|
|
||||||
set(v):
|
|
||||||
portal_render_layer = v
|
|
||||||
if caused_by_user_interaction():
|
|
||||||
portal_mesh.layers = v
|
|
||||||
|
|
||||||
|
|
||||||
## The portal camera sets its [member Camera3D.near] as close to the portal as possible, to
|
|
||||||
## hopefully cull objects close behind the portal. This value offsets the [member portal_camera]'s
|
|
||||||
## near clip plane. Might be useful, if the portal has a thick frame around it.
|
## near clip plane. Might be useful, if the portal has a thick frame around it.
|
||||||
var portal_frame_width: float = 0
|
var portal_frame_width: float = 0
|
||||||
|
|
||||||
## Determines how big the internal portal viewports are. It helps to reduce the memory usage
|
## Options for different sizes of the internal viewports. It helps to reduce the memory usage
|
||||||
## by not rendering the portals at full resolution. Viewports are resized on window resize.
|
## by not rendering the portals at full resolution. Viewports are resized on window resize.
|
||||||
enum PortalViewportSizeMode {
|
enum PortalViewportSizeMode {
|
||||||
## Render at full window resolution.
|
## Render at full window resolution.
|
||||||
FULL,
|
FULL,
|
||||||
## Portal viewport max width. Height is calculated from window aspect ratio.
|
## The portal will be [b]at most[/b] this wide. Height is calculated from window aspect ratio.
|
||||||
MAX_WIDTH_ABSOLUTE,
|
MAX_WIDTH_ABSOLUTE,
|
||||||
## Portal viewport will be a fraction of full window size.
|
## Portal viewport will be a fraction of full window size.
|
||||||
FRACTIONAL
|
FRACTIONAL
|
||||||
}
|
}
|
||||||
## Size mode to use for the portal viewport size.
|
|
||||||
|
## Size mode to use for the portal viewport size. Only set this via the inspector.
|
||||||
var viewport_size_mode: PortalViewportSizeMode = PortalViewportSizeMode.FULL:
|
var viewport_size_mode: PortalViewportSizeMode = PortalViewportSizeMode.FULL:
|
||||||
set(v):
|
set(v):
|
||||||
viewport_size_mode = v
|
viewport_size_mode = v
|
||||||
@ -155,9 +203,11 @@ var _viewport_size_max_width_absolute: int = ProjectSettings.get_setting("displa
|
|||||||
var _viewport_size_fractional: float = 0.5
|
var _viewport_size_fractional: float = 0.5
|
||||||
|
|
||||||
|
|
||||||
## Hints the direction from which you expect the portal to be viewed. Makes sense to restrict on
|
## Hints the direction from which you expect the portal to be viewed.[br][br]
|
||||||
## one-way portals or visual-only portals (with [member is_teleport] set to [code]false[/code]).
|
## Use cases: one-way portals, visual-only portals (with [member is_teleport] set to
|
||||||
|
## [code]false[/code]), or portals that are flush with a wall.
|
||||||
enum ViewDirection {
|
enum ViewDirection {
|
||||||
|
## Portal is expected to be viewed from either side (default)
|
||||||
FRONT_AND_BACK,
|
FRONT_AND_BACK,
|
||||||
## Corresponds to portal's FORWARD direction (-Z)
|
## Corresponds to portal's FORWARD direction (-Z)
|
||||||
ONLY_FRONT,
|
ONLY_FRONT,
|
||||||
@ -172,14 +222,24 @@ enum ViewDirection {
|
|||||||
## Also see [member teleport_direction]
|
## Also see [member teleport_direction]
|
||||||
var view_direction: ViewDirection = ViewDirection.FRONT_AND_BACK
|
var view_direction: ViewDirection = ViewDirection.FRONT_AND_BACK
|
||||||
|
|
||||||
## If [code]true[/code], the portal is also a teleport.
|
|
||||||
|
## The [member portal_mesh] setting for [member VisualInstance3D.layers], so that the portal
|
||||||
|
## cameras don't see other portals.
|
||||||
|
var portal_render_layer: int = 1 << 19:
|
||||||
|
set(v):
|
||||||
|
portal_render_layer = v
|
||||||
|
if _caused_by_user_interaction():
|
||||||
|
portal_mesh.layers = v
|
||||||
|
|
||||||
|
## If [code]true[/code], the portal is also a teleport. If [code]false[/code], the portal is
|
||||||
|
## visual-only.
|
||||||
## [br][br]
|
## [br][br]
|
||||||
## You are expected to toggle this in the editor. For runtime teleport toggling, see
|
## You are expected to toggle this in the editor. For runtime teleport toggling, see
|
||||||
## [method activate] and [method deactivate].
|
## [method activate] and [method deactivate].
|
||||||
var is_teleport: bool = true:
|
var is_teleport: bool = true:
|
||||||
set(v):
|
set(v):
|
||||||
is_teleport = v
|
is_teleport = v
|
||||||
if caused_by_user_interaction():
|
if _caused_by_user_interaction():
|
||||||
_setup_teleport()
|
_setup_teleport()
|
||||||
notify_property_list_changed()
|
notify_property_list_changed()
|
||||||
|
|
||||||
@ -189,12 +249,11 @@ enum TeleportDirection {
|
|||||||
FRONT,
|
FRONT,
|
||||||
## Corresponds to portal's BACK direction (+Z)
|
## Corresponds to portal's BACK direction (+Z)
|
||||||
BACK,
|
BACK,
|
||||||
## Teleports stuff coming from either side.
|
## Teleports stuff coming from either side. (default)
|
||||||
FRONT_AND_BACK
|
FRONT_AND_BACK
|
||||||
}
|
}
|
||||||
|
|
||||||
## If the portal is also a teleport, it will only teleport things coming from
|
## Portal will only teleport things coming from this direction.
|
||||||
## this direction.
|
|
||||||
var teleport_direction: TeleportDirection = TeleportDirection.FRONT_AND_BACK
|
var teleport_direction: TeleportDirection = TeleportDirection.FRONT_AND_BACK
|
||||||
|
|
||||||
## When a [RigidBody3D] goes through the portal, give its new normalized velocity a
|
## When a [RigidBody3D] goes through the portal, give its new normalized velocity a
|
||||||
@ -202,9 +261,6 @@ var teleport_direction: TeleportDirection = TeleportDirection.FRONT_AND_BACK
|
|||||||
## Recommended values: 1 to 3
|
## Recommended values: 1 to 3
|
||||||
var rigidbody_boost: float = 0.0
|
var rigidbody_boost: float = 0.0
|
||||||
|
|
||||||
## [CollisionObject3D]s detected by this mask will be registered by the portal and teleported.
|
|
||||||
var teleport_collision_mask: int = 1 << 15
|
|
||||||
|
|
||||||
## When teleporting, the portal checks if the teleported object is less than [b]this[/b] near.
|
## When teleporting, the portal checks if the teleported object is less than [b]this[/b] near.
|
||||||
## Prevents false negatives when multiple portals are on top of each other.
|
## Prevents false negatives when multiple portals are on top of each other.
|
||||||
var teleport_tolerance: float = 0.5
|
var teleport_tolerance: float = 0.5
|
||||||
@ -219,33 +275,23 @@ enum TeleportInteractions {
|
|||||||
PLAYER_UPRIGHT = 1 << 1,
|
PLAYER_UPRIGHT = 1 << 1,
|
||||||
## Duplicate meshes present on the teleported object, resulting in a [i]smooth teleport[/i]
|
## Duplicate meshes present on the teleported object, resulting in a [i]smooth teleport[/i]
|
||||||
## from a 3rd point of view. [br]
|
## from a 3rd point of view. [br]
|
||||||
## This option is quite involved, requires a method named [constant DUPLICATE_MESHES_CALLBACK]
|
## To use this feature, implement a method named [constant DUPLICATE_MESHES_CALLBACK] on the
|
||||||
## implemented on the teleported body, which returns an array of mesh instances that should be
|
## teleported body, which returns an array of mesh instances that should be duplicated.
|
||||||
## duplicated. Every one of those meshes also needs to implement a special shader to clip it
|
## Every one of those meshes also needs to implement a special shader material to clip it along
|
||||||
## along the portal plane.
|
## the portal plane.
|
||||||
|
## See shaderinclude at [code]addons/portals/materials/portalclip_mesh.gdshaderinc[/code]
|
||||||
DUPLICATE_MESHES = 1 << 2
|
DUPLICATE_MESHES = 1 << 2
|
||||||
}
|
}
|
||||||
|
|
||||||
## This method will be called on a teleported node if [member TeleportInteractions.CALLBACK]
|
|
||||||
## is checked in [member teleport_interactions]
|
|
||||||
const ON_TELEPORT_CALLBACK: StringName = &"on_teleport"
|
|
||||||
|
|
||||||
## This method will be called on a node that will get into close proximity of a portal that has
|
|
||||||
## [member TeleportInteractions.DUPLICATE_MESHES] turned on. The method is expected to return an
|
|
||||||
## array of [MeshInstance3D]s.
|
|
||||||
const DUPLICATE_MESHES_CALLBACK: StringName = &"get_teleportable_meshes"
|
|
||||||
|
|
||||||
## When a [CollisionObject3D] should be teleported, the portal check for a [NodePath] for an
|
|
||||||
## alternative node to teleport. For example it's useful when the [Area3D] that's triggering the
|
|
||||||
## teleport isn't the root of a player or object.
|
|
||||||
const TELEPORT_ROOT_META: StringName = &"teleport_root"
|
|
||||||
|
|
||||||
|
|
||||||
## See [enum TeleportInteractions] for options.
|
## See [enum TeleportInteractions] for options.
|
||||||
var teleport_interactions: int = TeleportInteractions.CALLBACK \
|
var teleport_interactions: int = TeleportInteractions.CALLBACK \
|
||||||
| TeleportInteractions.PLAYER_UPRIGHT
|
| TeleportInteractions.PLAYER_UPRIGHT
|
||||||
|
|
||||||
|
|
||||||
|
## Any [CollisionObject3D]s detected by this mask will be registered by the portal and teleported,
|
||||||
|
## when they cross the portal boundary.
|
||||||
|
var teleport_collision_mask: int = 1 << 15
|
||||||
|
|
||||||
## If the portal is not immediately visible on scene start, you can start it in [i]disabled
|
## If the portal is not immediately visible on scene start, you can start it in [i]disabled
|
||||||
## mode[/i]. This just means it will not create the appropriate subviewports, saving memory.
|
## mode[/i]. This just means it will not create the appropriate subviewports, saving memory.
|
||||||
## It will also not be processed.[br][br]
|
## It will also not be processed.[br][br]
|
||||||
@ -257,7 +303,7 @@ var start_deactivated: bool = false
|
|||||||
@export_storage var _portal_thickness: float = 0.05:
|
@export_storage var _portal_thickness: float = 0.05:
|
||||||
set(v):
|
set(v):
|
||||||
_portal_thickness = v
|
_portal_thickness = v
|
||||||
if caused_by_user_interaction(): _on_portal_size_changed()
|
if _caused_by_user_interaction(): _on_portal_size_changed()
|
||||||
|
|
||||||
@export_storage var _portal_mesh_path: NodePath
|
@export_storage var _portal_mesh_path: NodePath
|
||||||
## Mesh used to visualize the portal surface. Created when the portal is added to the scene
|
## Mesh used to visualize the portal surface. Created when the portal is added to the scene
|
||||||
@ -284,14 +330,17 @@ var teleport_collider: CollisionShape3D:
|
|||||||
|
|
||||||
|
|
||||||
## Camera that looks through the exit portal and renders to [member portal_viewport].
|
## Camera that looks through the exit portal and renders to [member portal_viewport].
|
||||||
## Created in [code]_ready[/code]
|
## Created in [method Node._ready]
|
||||||
var portal_camera: Camera3D = null
|
var portal_camera: Camera3D = null
|
||||||
|
|
||||||
## Viewport that supplies the albedo texture to portal mesh. Rendered by [member portal_camera].
|
## Viewport that supplies the albedo texture to portal mesh. Rendered by [member portal_camera].
|
||||||
## Created in [code]_ready[/code]
|
## Created in [method Node._ready]
|
||||||
var portal_viewport: SubViewport = null
|
var portal_viewport: SubViewport = null
|
||||||
|
|
||||||
## Metadata kept about the teleportable objects watched by the portal.
|
## Metadata about teleported objects.
|
||||||
|
##
|
||||||
|
## When the portal detects a teleportable body (or area) nearby, it gathers this metadata and
|
||||||
|
## starts watching it every frame for teleportation.
|
||||||
class TeleportableMeta:
|
class TeleportableMeta:
|
||||||
## Forward distance from the portal
|
## Forward distance from the portal
|
||||||
var forward: float = 0
|
var forward: float = 0
|
||||||
@ -301,14 +350,14 @@ class TeleportableMeta:
|
|||||||
## Cloned [member Portal3D.TeleportableMeta.meshes] with [method Node.duplicate]
|
## Cloned [member Portal3D.TeleportableMeta.meshes] with [method Node.duplicate]
|
||||||
var mesh_clones: Array[MeshInstance3D] = []
|
var mesh_clones: Array[MeshInstance3D] = []
|
||||||
|
|
||||||
# These physics bodies are being watched by the portal. They are registered under their instance ID
|
# These physics bodies are being watched by the portal. They are registered with their instance IDs
|
||||||
# as the keys of the dictionary. Registering them by their object references was unreliable when
|
# as the keys of the dictionary. Registering them by their object references becomes unreliable
|
||||||
# freeing object for some reason.
|
# when the teleport candidate gets freed.
|
||||||
var _watchlist_teleportables: Dictionary[int, TeleportableMeta] = {}
|
var _watchlist_teleportables: Dictionary[int, TeleportableMeta] = {}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Editor Configuration Stuff
|
#region Editor Configuration
|
||||||
|
|
||||||
const _PORTAL_SHADER: Shader = preload("uid://bhdb2skdxehes")
|
const _PORTAL_SHADER: Shader = preload("uid://bhdb2skdxehes")
|
||||||
const _EDITOR_PREVIEW_PORTAL_MATERIAL: StandardMaterial3D = preload("uid://dcfkcyddxkglf")
|
const _EDITOR_PREVIEW_PORTAL_MATERIAL: StandardMaterial3D = preload("uid://dcfkcyddxkglf")
|
||||||
@ -324,7 +373,7 @@ func _editor_ready() -> void:
|
|||||||
_setup_mesh()
|
_setup_mesh()
|
||||||
_setup_teleport()
|
_setup_teleport()
|
||||||
|
|
||||||
self.group_node(self)
|
self._group_node(self)
|
||||||
|
|
||||||
func _notification(what: int) -> void:
|
func _notification(what: int) -> void:
|
||||||
match what:
|
match what:
|
||||||
@ -358,7 +407,7 @@ func _setup_teleport():
|
|||||||
var area = Area3D.new()
|
var area = Area3D.new()
|
||||||
area.name = "TeleportArea"
|
area.name = "TeleportArea"
|
||||||
|
|
||||||
add_child_in_editor(self, area)
|
_add_child_in_editor(self, area)
|
||||||
_teleport_area_path = get_path_to(area)
|
_teleport_area_path = get_path_to(area)
|
||||||
|
|
||||||
var collider = CollisionShape3D.new()
|
var collider = CollisionShape3D.new()
|
||||||
@ -368,7 +417,7 @@ func _setup_teleport():
|
|||||||
box.size.y = portal_size.y
|
box.size.y = portal_size.y
|
||||||
collider.shape = box
|
collider.shape = box
|
||||||
|
|
||||||
add_child_in_editor(teleport_area, collider)
|
_add_child_in_editor(teleport_area, collider)
|
||||||
_teleport_collider_path = get_path_to(collider)
|
_teleport_collider_path = get_path_to(collider)
|
||||||
|
|
||||||
|
|
||||||
@ -388,7 +437,7 @@ func _on_portal_size_changed() -> void:
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GAMEPLAY RUNTIME STUFF
|
#region GAMEPLAY LOGIC
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
@ -430,7 +479,6 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _process_cameras() -> void:
|
func _process_cameras() -> void:
|
||||||
|
|
||||||
if portal_camera == null:
|
if portal_camera == null:
|
||||||
push_error("%s: No portal camera" % name)
|
push_error("%s: No portal camera" % name)
|
||||||
return
|
return
|
||||||
@ -511,11 +559,11 @@ func _process_teleports() -> void:
|
|||||||
exit_portal._process_cameras()
|
exit_portal._process_cameras()
|
||||||
|
|
||||||
# Resolve teleport interactions
|
# Resolve teleport interactions
|
||||||
if was_player and check_tp_interaction(TeleportInteractions.PLAYER_UPRIGHT):
|
if was_player and _check_tp_interaction(TeleportInteractions.PLAYER_UPRIGHT):
|
||||||
get_tree().create_tween().tween_property(teleportable, "rotation:x", 0, 0.3)
|
get_tree().create_tween().tween_property(teleportable, "rotation:x", 0, 0.3)
|
||||||
get_tree().create_tween().tween_property(teleportable, "rotation:z", 0, 0.3)
|
get_tree().create_tween().tween_property(teleportable, "rotation:z", 0, 0.3)
|
||||||
|
|
||||||
if check_tp_interaction(TeleportInteractions.CALLBACK):
|
if _check_tp_interaction(TeleportInteractions.CALLBACK):
|
||||||
if teleportable.has_method(ON_TELEPORT_CALLBACK):
|
if teleportable.has_method(ON_TELEPORT_CALLBACK):
|
||||||
teleportable.call(ON_TELEPORT_CALLBACK, self)
|
teleportable.call(ON_TELEPORT_CALLBACK, self)
|
||||||
|
|
||||||
@ -573,7 +621,7 @@ func _setup_mesh() -> void:
|
|||||||
# Editor-only material. Will be replaced when game starts.
|
# Editor-only material. Will be replaced when game starts.
|
||||||
mi.material_override = _EDITOR_PREVIEW_PORTAL_MATERIAL
|
mi.material_override = _EDITOR_PREVIEW_PORTAL_MATERIAL
|
||||||
|
|
||||||
add_child_in_editor(self, mi)
|
_add_child_in_editor(self, mi)
|
||||||
_portal_mesh_path = get_path_to(mi)
|
_portal_mesh_path = get_path_to(mi)
|
||||||
|
|
||||||
func _setup_cameras() -> void:
|
func _setup_cameras() -> void:
|
||||||
@ -584,7 +632,7 @@ func _setup_cameras() -> void:
|
|||||||
if exit_portal != null:
|
if exit_portal != null:
|
||||||
portal_viewport = SubViewport.new()
|
portal_viewport = SubViewport.new()
|
||||||
portal_viewport.name = self.name + "_SubViewport"
|
portal_viewport.name = self.name + "_SubViewport"
|
||||||
portal_viewport.size = get_desired_viewport_size()
|
portal_viewport.size = _calculate_viewport_size()
|
||||||
self.add_child(portal_viewport, true)
|
self.add_child(portal_viewport, true)
|
||||||
|
|
||||||
# Disable tonemapping on portal cameras
|
# Disable tonemapping on portal cameras
|
||||||
@ -595,7 +643,7 @@ func _setup_cameras() -> void:
|
|||||||
adjusted_env.tonemap_mode = Environment.TONE_MAPPER_LINEAR
|
adjusted_env.tonemap_mode = Environment.TONE_MAPPER_LINEAR
|
||||||
adjusted_env.tonemap_exposure = 1
|
adjusted_env.tonemap_exposure = 1
|
||||||
|
|
||||||
portal_camera = player_camera.duplicate(0)
|
portal_camera = Camera3D.new()
|
||||||
portal_camera.name = self.name + "_Camera3D"
|
portal_camera.name = self.name + "_Camera3D"
|
||||||
portal_camera.environment = adjusted_env
|
portal_camera.environment = adjusted_env
|
||||||
|
|
||||||
@ -636,7 +684,7 @@ func _on_teleport_body_exited(body: Node3D) -> void:
|
|||||||
|
|
||||||
func _on_window_resize() -> void:
|
func _on_window_resize() -> void:
|
||||||
if portal_viewport:
|
if portal_viewport:
|
||||||
portal_viewport.size = get_desired_viewport_size()
|
portal_viewport.size = _calculate_viewport_size()
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -646,7 +694,7 @@ func _construct_tp_metadata(node: Node3D) -> void:
|
|||||||
var meta = TeleportableMeta.new()
|
var meta = TeleportableMeta.new()
|
||||||
meta.forward = forward_distance(node)
|
meta.forward = forward_distance(node)
|
||||||
|
|
||||||
if check_tp_interaction(TeleportInteractions.DUPLICATE_MESHES) and \
|
if _check_tp_interaction(TeleportInteractions.DUPLICATE_MESHES) and \
|
||||||
node.has_method(DUPLICATE_MESHES_CALLBACK):
|
node.has_method(DUPLICATE_MESHES_CALLBACK):
|
||||||
meta.meshes = node.call(DUPLICATE_MESHES_CALLBACK)
|
meta.meshes = node.call(DUPLICATE_MESHES_CALLBACK)
|
||||||
for m: MeshInstance3D in meta.meshes:
|
for m: MeshInstance3D in meta.meshes:
|
||||||
@ -655,7 +703,7 @@ func _construct_tp_metadata(node: Node3D) -> void:
|
|||||||
meta.mesh_clones.append(dupe)
|
meta.mesh_clones.append(dupe)
|
||||||
self.add_child(dupe, true)
|
self.add_child(dupe, true)
|
||||||
|
|
||||||
enable_mesh_clipping(meta, self)
|
_enable_mesh_clipping(meta, self)
|
||||||
|
|
||||||
_watchlist_teleportables.set(node.get_instance_id(), meta)
|
_watchlist_teleportables.set(node.get_instance_id(), meta)
|
||||||
|
|
||||||
@ -663,12 +711,12 @@ func _erase_tp_metadata(node_id: int) -> void:
|
|||||||
var meta = _watchlist_teleportables.get(node_id)
|
var meta = _watchlist_teleportables.get(node_id)
|
||||||
if meta != null:
|
if meta != null:
|
||||||
meta = meta as TeleportableMeta
|
meta = meta as TeleportableMeta
|
||||||
for m in meta.meshes: disable_mesh_clipping(m)
|
for m in meta.meshes: _disable_mesh_clipping(m)
|
||||||
for c in meta.mesh_clones: c.queue_free()
|
for c in meta.mesh_clones: c.queue_free()
|
||||||
|
|
||||||
_watchlist_teleportables.erase(node_id)
|
_watchlist_teleportables.erase(node_id)
|
||||||
|
|
||||||
func enable_mesh_clipping(meta: TeleportableMeta, along_portal: Portal3D) -> void:
|
func _enable_mesh_clipping(meta: TeleportableMeta, along_portal: Portal3D) -> void:
|
||||||
for mi: MeshInstance3D in meta.meshes:
|
for mi: MeshInstance3D in meta.meshes:
|
||||||
var clip_normal = signf(meta.forward) * along_portal.global_basis.z
|
var clip_normal = signf(meta.forward) * along_portal.global_basis.z
|
||||||
mi.set_instance_shader_parameter("portal_clip_active", true)
|
mi.set_instance_shader_parameter("portal_clip_active", true)
|
||||||
@ -682,7 +730,7 @@ func enable_mesh_clipping(meta: TeleportableMeta, along_portal: Portal3D) -> voi
|
|||||||
clone.set_instance_shader_parameter("portal_clip_point", exit.global_position)
|
clone.set_instance_shader_parameter("portal_clip_point", exit.global_position)
|
||||||
clone.set_instance_shader_parameter("portal_clip_normal", clip_normal)
|
clone.set_instance_shader_parameter("portal_clip_normal", clip_normal)
|
||||||
|
|
||||||
func disable_mesh_clipping(mi: MeshInstance3D) -> void:
|
func _disable_mesh_clipping(mi: MeshInstance3D) -> void:
|
||||||
mi.set_instance_shader_parameter("portal_clip_active", false)
|
mi.set_instance_shader_parameter("portal_clip_active", false)
|
||||||
|
|
||||||
func _transfer_tp_metadata_to_exit(for_body: Node3D) -> void:
|
func _transfer_tp_metadata_to_exit(for_body: Node3D) -> void:
|
||||||
@ -696,7 +744,7 @@ func _transfer_tp_metadata_to_exit(for_body: Node3D) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
tp_meta.forward = exit_portal.forward_distance(for_body)
|
tp_meta.forward = exit_portal.forward_distance(for_body)
|
||||||
enable_mesh_clipping(tp_meta, exit_portal) # Switch, the main mesh is clipped by exit portal!
|
_enable_mesh_clipping(tp_meta, exit_portal) # Switch, the main mesh is clipped by exit portal!
|
||||||
|
|
||||||
exit_portal._watchlist_teleportables.set(body_id, tp_meta)
|
exit_portal._watchlist_teleportables.set(body_id, tp_meta)
|
||||||
# NOTE: Not using '_erase_tp_metadata' here, as it also frees the cloned meshes!
|
# NOTE: Not using '_erase_tp_metadata' here, as it also frees the cloned meshes!
|
||||||
@ -739,32 +787,26 @@ func forward_distance(node: Node3D) -> float:
|
|||||||
var node_relative: Vector3 = (node.global_transform.origin - self.global_transform.origin)
|
var node_relative: Vector3 = (node.global_transform.origin - self.global_transform.origin)
|
||||||
return portal_front.dot(node_relative)
|
return portal_front.dot(node_relative)
|
||||||
|
|
||||||
## Helper function meant to be used in editor. Adds [param node] as a child to
|
# Helper function meant to be used in editor. Adds [param node] as a child to
|
||||||
## [param parent]. Forces a readable name and sets the child's owner to the same
|
# [param parent]. Forces a readable name and sets the child's owner to the same
|
||||||
## as parent's.
|
# as parent's.
|
||||||
func add_child_in_editor(parent: Node, node: Node) -> void:
|
func _add_child_in_editor(parent: Node, node: Node) -> void:
|
||||||
parent.add_child(node, true)
|
parent.add_child(node, true)
|
||||||
# self.owner is null if this node is the scene root. Supply self.
|
# self.owner is null if this node is the scene root. Supply self.
|
||||||
node.owner = self if self.owner == null else self.owner
|
node.owner = self if self.owner == null else self.owner
|
||||||
|
|
||||||
## Used to conditionally run property setters.
|
# Used to conditionally run property setters.
|
||||||
## [br]
|
# [br]
|
||||||
## Setters fire both on editor set and when the scene starts up (the engine is
|
# Setters fire both on editor set and when the scene starts up (the engine is
|
||||||
## assigning exported members). This should prevent the second case.
|
# assigning exported members). This should prevent the second case.
|
||||||
func caused_by_user_interaction() -> bool:
|
func _caused_by_user_interaction() -> bool:
|
||||||
return Engine.is_editor_hint() and is_node_ready()
|
return Engine.is_editor_hint() and is_node_ready()
|
||||||
|
|
||||||
## Editor helper function. Locks node in 3D editor view.
|
# Editor helper function. Groups nodes in 3D editor view.
|
||||||
static func lock_node(node: Node3D) -> void:
|
func _group_node(node: Node) -> void:
|
||||||
node.set_meta("_edit_lock_", true)
|
|
||||||
|
|
||||||
|
|
||||||
## Editor helper function. Groups nodes in 3D editor view.
|
|
||||||
static func group_node(node: Node) -> void:
|
|
||||||
node.set_meta("_edit_group_", true)
|
node.set_meta("_edit_group_", true)
|
||||||
|
|
||||||
|
func _calculate_viewport_size() -> Vector2i:
|
||||||
func get_desired_viewport_size() -> Vector2i:
|
|
||||||
var vp_size: Vector2i = get_viewport().size
|
var vp_size: Vector2i = get_viewport().size
|
||||||
var aspect_ratio: float = float(vp_size.x) / float(vp_size.y)
|
var aspect_ratio: float = float(vp_size.x) / float(vp_size.y)
|
||||||
|
|
||||||
@ -783,10 +825,10 @@ func get_desired_viewport_size() -> Vector2i:
|
|||||||
ProjectSettings.get_setting("display/window/size/viewport_height")
|
ProjectSettings.get_setting("display/window/size/viewport_height")
|
||||||
)
|
)
|
||||||
|
|
||||||
func check_tp_interaction(flag: int) -> bool:
|
func _check_tp_interaction(flag: int) -> bool:
|
||||||
return (teleport_interactions & flag) > 0
|
return (teleport_interactions & flag) > 0
|
||||||
|
|
||||||
## Get a point where the portal plane intersects a line. Line ends [param start] and [param end]
|
## Get a point where the portal plane intersects a line. Line [param start] and [param end]
|
||||||
## are in global coordinates and so is the result. Used for forwarding raycast queries.
|
## are in global coordinates and so is the result. Used for forwarding raycast queries.
|
||||||
func line_intersection(start: Vector3, end: Vector3) -> Vector3:
|
func line_intersection(start: Vector3, end: Vector3) -> Vector3:
|
||||||
var plane_normal = - global_basis.z
|
var plane_normal = - global_basis.z
|
||||||
@ -803,7 +845,7 @@ func line_intersection(start: Vector3, end: Vector3) -> Vector3:
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GODOT ENGINE INTEGRATIONS
|
#region GODOT EDITOR INTEGRATIONS
|
||||||
|
|
||||||
func _get_configuration_warnings() -> PackedStringArray:
|
func _get_configuration_warnings() -> PackedStringArray:
|
||||||
var warnings: Array[String] = []
|
var warnings: Array[String] = []
|
||||||
@ -845,7 +887,6 @@ func _get_property_list() -> Array[Dictionary]:
|
|||||||
|
|
||||||
config.append(AtExport.group("Rendering"))
|
config.append(AtExport.group("Rendering"))
|
||||||
config.append(AtExport.node("player_camera", "Camera3D"))
|
config.append(AtExport.node("player_camera", "Camera3D"))
|
||||||
config.append(AtExport.int_render_3d("portal_render_layer"))
|
|
||||||
config.append(AtExport.float_range("portal_frame_width", 0.0, 10.0, 0.01))
|
config.append(AtExport.float_range("portal_frame_width", 0.0, 10.0, 0.01))
|
||||||
|
|
||||||
config.append(AtExport.enum_(
|
config.append(AtExport.enum_(
|
||||||
@ -858,8 +899,9 @@ func _get_property_list() -> Array[Dictionary]:
|
|||||||
|
|
||||||
config.append(AtExport.enum_("view_direction", &"Portal3D.ViewDirection", ViewDirection))
|
config.append(AtExport.enum_("view_direction", &"Portal3D.ViewDirection", ViewDirection))
|
||||||
|
|
||||||
config.append(AtExport.group_end())
|
config.append(AtExport.int_render_3d("portal_render_layer"))
|
||||||
|
|
||||||
|
config.append(AtExport.group_end())
|
||||||
|
|
||||||
config.append(AtExport.bool_("is_teleport"))
|
config.append(AtExport.bool_("is_teleport"))
|
||||||
|
|
||||||
@ -869,10 +911,10 @@ func _get_property_list() -> Array[Dictionary]:
|
|||||||
config.append(
|
config.append(
|
||||||
AtExport.enum_("teleport_direction", &"Portal3D.TeleportDirection", TeleportDirection))
|
AtExport.enum_("teleport_direction", &"Portal3D.TeleportDirection", TeleportDirection))
|
||||||
config.append(AtExport.float_range("rigidbody_boost", 0, 5, 0.1, ["or_greater"]))
|
config.append(AtExport.float_range("rigidbody_boost", 0, 5, 0.1, ["or_greater"]))
|
||||||
config.append(AtExport.int_physics_3d("teleport_collision_mask"))
|
|
||||||
config.append(AtExport.float_range("teleport_tolerance", 0.0, 5.0, 0.1, ["or_greater"]))
|
config.append(AtExport.float_range("teleport_tolerance", 0.0, 5.0, 0.1, ["or_greater"]))
|
||||||
var opts: Array = TeleportInteractions.keys().map(func(s): return s.capitalize())
|
var opts: Array = TeleportInteractions.keys().map(func(s): return s.capitalize())
|
||||||
config.append(AtExport.int_flags("teleport_interactions", opts))
|
config.append(AtExport.int_flags("teleport_interactions", opts))
|
||||||
|
config.append(AtExport.int_physics_3d("teleport_collision_mask"))
|
||||||
config.append(AtExport.group_end())
|
config.append(AtExport.group_end())
|
||||||
|
|
||||||
config.append(AtExport.group("Advanced"))
|
config.append(AtExport.group("Advanced"))
|
||||||
@ -884,15 +926,15 @@ func _property_can_revert(property: StringName) -> bool:
|
|||||||
return property in [
|
return property in [
|
||||||
&"portal_size",
|
&"portal_size",
|
||||||
&"player_camera",
|
&"player_camera",
|
||||||
&"portal_render_layer",
|
|
||||||
&"portal_frame_width",
|
&"portal_frame_width",
|
||||||
&"_viewport_size_max_width_absolute",
|
&"_viewport_size_max_width_absolute",
|
||||||
&"view_direction",
|
&"view_direction",
|
||||||
|
&"portal_render_layer",
|
||||||
&"teleport_direction",
|
&"teleport_direction",
|
||||||
&"rigidbody_boost",
|
&"rigidbody_boost",
|
||||||
&"teleport_collision_mask",
|
|
||||||
&"teleport_tolerance",
|
&"teleport_tolerance",
|
||||||
&"teleport_interactions",
|
&"teleport_interactions",
|
||||||
|
&"teleport_collision_mask",
|
||||||
&"start_deactivated",
|
&"start_deactivated",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -900,24 +942,24 @@ func _property_get_revert(property: StringName) -> Variant:
|
|||||||
match property:
|
match property:
|
||||||
&"portal_size":
|
&"portal_size":
|
||||||
return Vector2(2, 2.5)
|
return Vector2(2, 2.5)
|
||||||
&"portal_render_layer":
|
|
||||||
return 1 << 19
|
|
||||||
&"portal_frame_width":
|
&"portal_frame_width":
|
||||||
return 0.0
|
return 0.0
|
||||||
&"_viewport_size_max_width_absolute":
|
&"_viewport_size_max_width_absolute":
|
||||||
return ProjectSettings.get_setting("display/window/size/viewport_width")
|
return ProjectSettings.get_setting("display/window/size/viewport_width")
|
||||||
&"view_direction":
|
&"view_direction":
|
||||||
return ViewDirection.FRONT_AND_BACK
|
return ViewDirection.FRONT_AND_BACK
|
||||||
|
&"portal_render_layer":
|
||||||
|
return 1 << 19
|
||||||
&"teleport_direction":
|
&"teleport_direction":
|
||||||
return TeleportDirection.FRONT_AND_BACK
|
return TeleportDirection.FRONT_AND_BACK
|
||||||
&"rigidbody_boost":
|
&"rigidbody_boost":
|
||||||
return 0.0
|
return 0.0
|
||||||
&"teleport_collision_mask":
|
|
||||||
return 1 << 15
|
|
||||||
&"teleport_tolerance":
|
&"teleport_tolerance":
|
||||||
return 0.5
|
return 0.5
|
||||||
&"teleport_interactions":
|
&"teleport_interactions":
|
||||||
return TeleportInteractions.CALLBACK | TeleportInteractions.PLAYER_UPRIGHT
|
return TeleportInteractions.CALLBACK | TeleportInteractions.PLAYER_UPRIGHT
|
||||||
|
&"teleport_collision_mask":
|
||||||
|
return 1 << 15
|
||||||
&"start_deactivated":
|
&"start_deactivated":
|
||||||
return false
|
return false
|
||||||
return null
|
return null
|
||||||
|
@ -2,6 +2,15 @@
|
|||||||
extends ArrayMesh
|
extends ArrayMesh
|
||||||
class_name PortalBoxMesh
|
class_name PortalBoxMesh
|
||||||
|
|
||||||
|
## Inverted box with a flipped front side
|
||||||
|
##
|
||||||
|
## This mesh class generates a mesh similar to [BoxMesh]. However, its sides are all facing
|
||||||
|
## [i]inwards[/i], except for the fron side, which is facing outwards. The origin point of this
|
||||||
|
## mesh is in the middle of its front face, instead of in the center of its volume (like you'd
|
||||||
|
## expect with a box).[br]
|
||||||
|
## It is a special mesh built for portal surfaces. The front face provides a nice flat surface and
|
||||||
|
## the other sides try to reduce clipping issues when traveling through portals. See [Portal3D]
|
||||||
|
|
||||||
@export var size: Vector3 = Vector3(1, 1, 1):
|
@export var size: Vector3 = Vector3(1, 1, 1):
|
||||||
set(v):
|
set(v):
|
||||||
size = v
|
size = v
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
class_name PortalSettings
|
class_name PortalSettings extends Object
|
||||||
|
|
||||||
## Static helper class for portal project settings.
|
## Static helper class for portal project settings.
|
||||||
##
|
##
|
||||||
@ -8,6 +8,7 @@ class_name PortalSettings
|
|||||||
static func _qual_name(setting: String) -> String:
|
static func _qual_name(setting: String) -> String:
|
||||||
return "addons/portals/" + setting
|
return "addons/portals/" + setting
|
||||||
|
|
||||||
|
## Initializes a setting, it it's not present already. The setting is [i]basic[/i] by default.
|
||||||
static func init_setting(setting: String,
|
static func init_setting(setting: String,
|
||||||
default_value: Variant,
|
default_value: Variant,
|
||||||
requires_restart: bool = false) -> void:
|
requires_restart: bool = false) -> void:
|
||||||
@ -31,6 +32,7 @@ static func add_info(config: Dictionary) -> void:
|
|||||||
|
|
||||||
ProjectSettings.add_property_info(config)
|
ProjectSettings.add_property_info(config)
|
||||||
|
|
||||||
|
## Calls [method ProjectSettings.get_setting]
|
||||||
static func get_setting(setting: String) -> Variant:
|
static func get_setting(setting: String) -> Variant:
|
||||||
setting = _qual_name(setting)
|
setting = _qual_name(setting)
|
||||||
return ProjectSettings.get_setting(setting)
|
return ProjectSettings.get_setting(setting)
|
||||||
|
Loading…
Reference in New Issue
Block a user