I’m using coordinates and a scene path to load a new location with the following Teleporter class. I’m using Marker2D to manually copy and paste the coordinates into the teleporter.
I want to make this a better experience by having the editor check the destination scene and offer a dropdown of available Marker2Ds to teleport to.
It’s kind of working, but it’s breaking when I connect the other side of the teleporter. I feel there’s a kind of loop/recursion going on there, when the scenes contain each other:
@tool
class_name Teleporter
extends Area2D
## Needs a shape to choose where the teleportation should be triggered.
@export var door_width: int = 128:
set(new_width):
door_width = new_width
# As the shape is made local to the scene it only changes for the instance
($CollisionShape2D.shape as SegmentShape2D).b.x = new_width
## The scene which contains markers to which the player should be teleported.
## Drag and drop it from the FileSystem dock in the editor.
@export var target_scene: PackedScene:
set(new_target_scene):
target_scene = new_target_scene
notify_property_list_changed()
update_configuration_warnings()
signal teleported(target_room_path: String, destination_coordinates: Vector2)
## This is needed for the enum `destination_marker` to properly show the selected item.
var selected_destination_marker_name: String
var selected_destination: Vector2
func _get_property_list():
var position_options = get_markers_from_scene().map(func(marker: Marker2D): return marker.name).filter(func(marker_name: String): return marker_name != "PlayerRespawn")
var properties = []
properties.append({
"name": "destination_marker",
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM,
"hint_string": ",".join(position_options),
})
return properties
func _set(property: StringName, value: Variant) -> bool:
if (property == "destination_marker"):
selected_destination_marker_name = value
var marker: Marker2D = get_markers_from_scene().filter(func(m): return m.name == value).front()
if marker:
selected_destination = marker.position
return true
return false
## This needs to return the enum string so it knows what to show in the editor (it's called constantly).
func _get(property: StringName):
if (property == "destination_marker"):
return selected_destination_marker_name
func get_markers_from_scene() -> Array:
var markers = []
if target_scene and target_scene is PackedScene:
var target_instance = target_scene.instantiate()
if target_instance:
for child in target_instance.get_children():
if child is Marker2D:
markers.append(child)
return markers
func _get_configuration_warnings() -> PackedStringArray:
var notifications: PackedStringArray = []
if not (target_scene):
notifications.append("Needs a target room to teleport to!")
return notifications
func _on_body_entered(body: Node2D) -> void:
if body is Player:
PlayerGlobals.facing_direction_before_teleport = body.facing_direction
PlayerGlobals.remaining_time_to_burn_before_teleport = body.burn_timer.time_left
PlayerGlobals.has_teleported_while_burning = body.is_burning
teleported.emit(target_scene.resource_path, selected_destination)
Together with some help from Claude AI I could prevent instantiating the scene. Still, using the PackedScene doesn’t seem to work properly as I’m getting these errors
@tool
class_name Teleporter
extends Area2D
## Needs a shape to choose where the teleportation should be triggered.
@export var door_width: int = 128:
set(new_width):
door_width = new_width
# As the shape is made local to the scene it only changes for the instance
if has_node("CollisionShape2D"):
($CollisionShape2D.shape as SegmentShape2D).b.x = new_width
## The scene which contains markers to which the player should be teleported.
## Drag and drop it from the FileSystem dock in the editor.
@export var target_scene: PackedScene:
set(new_target_scene):
target_scene = new_target_scene
# Clear the selected marker when the scene changes
destination_marker = ""
# Update the property list to refresh the marker options
notify_property_list_changed()
update_configuration_warnings()
var destination_marker: String:
set(new_marker):
destination_marker = new_marker
_update_destination_marker()
update_configuration_warnings()
get:
return destination_marker
signal teleported(target_room_path: String, destination_coordinates: Vector2)
var selected_destination: Vector2
func _update_destination_marker():
# Safely find the marker without instantiating the entire scene
if target_scene:
var scene_state = target_scene.get_state()
for i in scene_state.get_node_count():
var node_name = scene_state.get_node_name(i)
var node_class = scene_state.get_node_type(i)
if node_class == "Marker2D" and node_name == destination_marker:
# Use the scene's state to get the marker's position
var marker_position: Vector2
for idx in range(0, scene_state.get_node_property_count(i)):
if "position" == scene_state.get_node_property_name(i, idx):
marker_position = scene_state.get_node_property_value(i, idx)
break
if marker_position:
selected_destination = marker_position
break
func _get_property_list() -> Array:
var properties = []
# Only add marker selection if a target scene is selected
if target_scene:
var marker_names = _get_marker_names()
if not marker_names.is_empty():
properties.append({
"name": "destination_marker",
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM,
"hint_string": ",".join(marker_names),
})
return properties
func _get_marker_names() -> PackedStringArray:
var marker_names = PackedStringArray()
if target_scene:
var scene_state = target_scene.get_state()
for i in scene_state.get_node_count():
var node_name = scene_state.get_node_name(i)
var node_class = scene_state.get_node_type(i)
if node_class == "Marker2D" and node_name != "PlayerRespawn":
marker_names.append(node_name)
return marker_names
func _get_configuration_warnings() -> PackedStringArray:
var notifications: PackedStringArray = []
if not target_scene:
notifications.append("Needs a target room to teleport to!")
elif destination_marker.is_empty():
notifications.append("Select a destination marker!")
return notifications
func _on_body_entered(body: Node2D) -> void:
if body is Player:
PlayerGlobals.facing_direction_before_teleport = body.facing_direction
PlayerGlobals.remaining_time_to_burn_before_teleport = body.burn_timer.time_left
PlayerGlobals.has_teleported_while_burning = body.is_burning
teleported.emit(target_scene.resource_path, selected_destination)
EDIT: Using this together with
@export_file("*level*.tscn") var target_scene: String:
(and adding load() where needed) etc.) it is now working. I still value any thoughts on this.