Skip to content

Commit 6ccd444

Browse files
authored
Merge pull request #80 from endlessm/wjt/web-deep-link
Sync current scene with URL hash
2 parents da03d70 + 0dbd993 commit 6ccd444

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

project.godot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ config/icon="res://icon.svg"
2020

2121
Global="*res://scripts/global.gd"
2222
Actions="*res://scripts/actions.gd"
23+
WebSceneSelector="uid://cag3i6auntfwn"
2324

2425
[display]
2526

scripts/web_scene_selector.gd

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
extends Node
2+
## Global script to sync current scene with URL hash on web platform
3+
##
4+
## On the web platform, this script allows loading a specific scene by placing its filename in the
5+
## URL hash; and updates the URL hash when the scene changes.
6+
7+
# Prefixes to try adding to non-absolute path in URL hash, which may have been stripped to make it
8+
# more human-readable.
9+
const _SCENE_PREFIXES = [
10+
"res://",
11+
]
12+
13+
# Suffix stripped from path to make it more human-readable
14+
const _SCENE_SUFFIX = ".tscn"
15+
16+
# Proxy object for the 'window' DOM object, or null if not running on the web
17+
var _window: JavaScriptObject
18+
19+
# Proxy object for the [method _on_hash_changed] callback, or null if not running on the web
20+
var _on_hash_changed_ref: JavaScriptObject
21+
22+
# The last URL that was set by [method _set_hash], if running on the web.
23+
# If we observe the URL changing to something different, the user has edited the URL manually.
24+
var _current_url: String
25+
26+
# Matches the expected absolute path for a scene, with a capture group
27+
# representing a more human-readable substring.
28+
var _scene_rx := RegEx.create_from_string(
29+
"^" + _SCENE_PREFIXES[-1] + "(?<scene>.+)\\" + _SCENE_SUFFIX + "$"
30+
)
31+
32+
# The main scene of the game. The URL hash will be cleared if this is the current scene.
33+
@onready var _main_scene: String = ProjectSettings.get("application/run/main_scene")
34+
35+
36+
func _ready() -> void:
37+
if OS.has_feature("web"):
38+
_window = JavaScriptBridge.get_interface("window")
39+
# Load any scene specified in the URL hash
40+
_restore_from_hash.call_deferred()
41+
42+
# Monitor the URL hash for changes
43+
_on_hash_changed_ref = JavaScriptBridge.create_callback(_on_hash_changed)
44+
_window.onhashchange = _on_hash_changed_ref
45+
46+
# Monitor for the current scene changing. There is no built-in way to switch scenes but this
47+
# may change when the game is modded!
48+
get_tree().scene_changed.connect(_on_scene_changed)
49+
50+
51+
# On the web, load the world indicated by the URL hash, if any.
52+
func _restore_from_hash() -> void:
53+
var url_hash: String = _window.location.hash as String
54+
if url_hash:
55+
var path: String = url_hash.right(-1).uri_decode()
56+
57+
if path.is_relative_path():
58+
if not path.ends_with(_SCENE_SUFFIX):
59+
path += _SCENE_SUFFIX
60+
61+
for prefix: String in _SCENE_PREFIXES:
62+
if ResourceLoader.exists(prefix + path, "PackedScene"):
63+
path = prefix + path
64+
break
65+
# otherwise, this is an absolute uid:// or res:// path
66+
67+
if ResourceLoader.exists(path, "PackedScene"):
68+
get_tree().change_scene_to_file(path)
69+
else:
70+
prints("Path", path, "from URL hash", url_hash, "is not a scene; ignoring")
71+
72+
73+
# On the web, update or clear the URL hash to indicate the current scene.
74+
func _set_hash(resource_path: String) -> void:
75+
if _window:
76+
var rx_match: RegExMatch = _scene_rx.search(resource_path)
77+
var url_hash: String
78+
79+
if resource_path == _main_scene:
80+
url_hash = ""
81+
elif rx_match:
82+
url_hash = rx_match.get_string("scene")
83+
else:
84+
url_hash = resource_path
85+
86+
var url: JavaScriptObject = JavaScriptBridge.create_object("URL", _window.location.href)
87+
url.hash = "#" + url_hash
88+
# Replace the current URL rather than simply updating window.location to
89+
# avoid creating misleading history entries that don't work if you press
90+
# the browser's back button.
91+
_current_url = url.href
92+
_window.location.replace(url.href)
93+
94+
95+
# When the browser tells us the hash has changed, potentially switch scene.
96+
func _on_hash_changed(args: Array) -> void:
97+
var event := args[0] as JavaScriptObject
98+
var new_url := event.newURL as String
99+
if new_url != _current_url:
100+
_restore_from_hash()
101+
102+
103+
# When Godot tells us the current scene has changed, update the URL hash.
104+
func _on_scene_changed() -> void:
105+
_set_hash(get_tree().current_scene.scene_file_path)

scripts/web_scene_selector.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://cag3i6auntfwn

0 commit comments

Comments
 (0)