| … | … | @@ -31,6 +31,7 @@ A Godot plugin and example project for loading assets from the [hypercore-protoc |
| 31 | 31 | - [ ] Create custom maps from an image and a JSON file |
| 32 | 32 | - [ ] Auto-discover which maps are active |
| 33 | 33 | - [ ] Enable code to work in Godot Web in Agregore |
| 34 | - [ ] Use new [JavaScriptObject](https://godotengine.org/article/godot-web-progress-report-9) API from Godot 4 | |
| 34 | 35 | - [ ] Enable code to work in Godot Web in regular browsers |
| 35 | 36 | - [ ] Load gltf scene from hyper |
| 36 | 37 | - [ ] Load VRM avatar from hyper (social multiplayer) [VRM stuff](https://github.com/V-Sekai/godot-vrm/blob/godot3/addons/vrm/import_vrm.gd) |
| … | … | @@ -3,8 +3,8 @@ extends Node2D |
| 3 | 3 | var RemotePlayer = preload("res://src/MultiplayerDemo/RemotePlayer.tscn") |
| 4 | 4 | var RemoteBullet = preload("res://src/MultiplayerDemo/RemoteBullet.tscn") |
| 5 | 5 | |
| 6 | #export var url: String = "hyper://blog.mauve.moe/" | |
| 7 | export var url: String = "hyper://70338a94d7415990ab4a3160cf98a5d940243d7fa24dd297352e66b52d3ff841/" | |
| 6 | export var url: String = "hyper://blog.mauve.moe/" | |
| 7 | #export var url: String = "hyper://70338a94d7415990ab4a3160cf98a5d940243d7fa24dd297352e66b52d3ff841/" | |
| 8 | 8 | export var profile : String = "" |
| 9 | 9 | export var spawn_gateway = true |
| 10 | 10 | |
| … | … | @@ -19,7 +19,16 @@ const knownPlayers = {} |
| 19 | 19 | |
| 20 | 20 | func _ready(): |
| 21 | 21 | if spawn_gateway: gateway.start() |
| 22 | elif profile.length() != 0: player.load_profile(profile) | |
| 22 | else: _perform_setup() | |
| 23 | ||
| 24 | func _perform_setup(): | |
| 25 | if profile.length() != 0: player.load_profile(profile) | |
| 26 | ||
| 27 | gossip.url = url | |
| 28 | gossip.start_listening() | |
| 29 | ||
| 30 | $LevelInfo.url = url | |
| 31 | $LevelInfo.load_info() | |
| 23 | 32 | |
| 24 | 33 | func get_player_object(id): |
| 25 | 34 | if knownPlayers.has(id): return knownPlayers[id] |
| … | … | @@ -32,6 +41,13 @@ func get_player_object(id): |
| 32 | 41 | |
| 33 | 42 | return remotePlayer |
| 34 | 43 | |
| 44 | func set_background(image_path): | |
| 45 | var texture = ImageTexture.new() | |
| 46 | var image = Image.new() | |
| 47 | image.load(image_path) | |
| 48 | texture.create_from_image(image) | |
| 49 | $Backround.texture = texture | |
| 50 | ||
| 35 | 51 | func _broadcast_player(): |
| 36 | 52 | var position = player.position |
| 37 | 53 | var rotation = player.rotation_degrees |
| … | … | @@ -104,10 +120,7 @@ func _on_Player_shoot(Bullet, direction, location): |
| 104 | 120 | _broadcast_shoot(direction, location) |
| 105 | 121 | |
| 106 | 122 | func _on_HyperGateway_started_gateway(_pid): |
| 107 | if profile.length() != 0: player.load_profile(profile) | |
| 108 | ||
| 109 | gossip.url = url | |
| 110 | gossip.start_listening() | |
| 123 | _perform_setup() | |
| 111 | 124 | |
| 112 | 125 | func _on_HyperGossip_listening(_extension_name): |
| 113 | 126 | print('Started listening on events') |
| … | … | @@ -124,3 +137,6 @@ func _on_HyperGossip_event(type, data, from): |
| 124 | 137 | func _on_Player_moved(_position, _rotation, _velocity): |
| 125 | 138 | # TODO: Don't broadcast too often to avoid flooding the system |
| 126 | 139 | _broadcast_player() |
| 140 | ||
| 141 | func _on_LevelInfo_image(path): | |
| 142 | set_background(path) |
| … | … | @@ -1,14 +1,25 @@ |
| 1 | [gd_scene load_steps=6 format=2] | |
| 1 | [gd_scene load_steps=8 format=2] | |
| 2 | 2 | |
| 3 | 3 | [ext_resource path="res://src/MultiplayerDemo/PlayerSprite.tscn" type="PackedScene" id=1] |
| 4 | 4 | [ext_resource path="res://src/MultiplayerDemo/Player.gd" type="Script" id=2] |
| 5 | 5 | [ext_resource path="res://src/MultiplayerDemo/FlyBalls.gd" type="Script" id=3] |
| 6 | 6 | [ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=4] |
| 7 | 7 | [ext_resource path="res://src/multiplayer/HyperGossip.gd" type="Script" id=5] |
| 8 | [ext_resource path="res://src/MultiplayerDemo/LevelInfo.tscn" type="PackedScene" id=6] | |
| 9 | [ext_resource path="res://src/MultiplayerDemo/background.png" type="Texture" id=7] | |
| 8 | 10 | |
| 9 | 11 | [node name="FlyBalls" type="Node2D"] |
| 10 | 12 | script = ExtResource( 3 ) |
| 11 | 13 | |
| 14 | [node name="Backround" type="TextureRect" parent="."] | |
| 15 | margin_right = 665.0 | |
| 16 | margin_bottom = 665.0 | |
| 17 | texture = ExtResource( 7 ) | |
| 18 | stretch_mode = 2 | |
| 19 | __meta__ = { | |
| 20 | "_edit_use_anchors_": false | |
| 21 | } | |
| 22 | ||
| 12 | 23 | [node name="Player" parent="." instance=ExtResource( 1 )] |
| 13 | 24 | position = Vector2( 333, 333 ) |
| 14 | 25 | script = ExtResource( 2 ) |
| … | … | @@ -21,8 +32,12 @@ extension_name = "flyballs-v1" |
| 21 | 32 | script = ExtResource( 4 ) |
| 22 | 33 | autoStart = false |
| 23 | 34 | |
| 35 | [node name="LevelInfo" parent="." instance=ExtResource( 6 )] | |
| 36 | autoload_image = true | |
| 37 | ||
| 24 | 38 | [connection signal="moved" from="Player" to="." method="_on_Player_moved"] |
| 25 | 39 | [connection signal="shoot" from="Player" to="." method="_on_Player_shoot"] |
| 26 | 40 | [connection signal="event" from="HyperGossip" to="." method="_on_HyperGossip_event"] |
| 27 | 41 | [connection signal="listening" from="HyperGossip" to="." method="_on_HyperGossip_listening"] |
| 28 | 42 | [connection signal="started_gateway" from="HyperGateway" to="." method="_on_HyperGateway_started_gateway"] |
| 43 | [connection signal="image" from="LevelInfo" to="." method="_on_LevelInfo_image"] |
| … | … | @@ -0,0 +1,143 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | var URLParser = preload("res://src/utils/URLParser.gd") | |
| 4 | var parser = URLParser.new() | |
| 5 | ||
| 6 | signal info(title, image) | |
| 7 | signal no_info() | |
| 8 | signal image(path) | |
| 9 | signal updated() | |
| 10 | signal resolved(url) | |
| 11 | signal error(err) | |
| 12 | ||
| 13 | export var url = 'hyper://multiplayer-level/' | |
| 14 | export var cache_folder: String = 'user://hyper-file-cache/' | |
| 15 | export var level_file: String = 'level.json' | |
| 16 | ||
| 17 | export var autoload = false | |
| 18 | export var autoload_image = false | |
| 19 | ||
| 20 | const DEFAULT_TITLE = 'New Level' | |
| 21 | ||
| 22 | var info_cache = null | |
| 23 | var has_loaded = false | |
| 24 | var picture_path = '' | |
| 25 | ||
| 26 | func _ready(): | |
| 27 | var dir = Directory.new() | |
| 28 | var err = dir.make_dir_recursive(cache_folder) | |
| 29 | if err != OK: | |
| 30 | printerr("Unable to create cache directory ", err) | |
| 31 | ||
| 32 | print("Level info setup") | |
| 33 | if autoload: | |
| 34 | load_info() | |
| 35 | ||
| 36 | func get_title(): | |
| 37 | if info_cache == null: return DEFAULT_TITLE | |
| 38 | if !info_cache.has('title'): return DEFAULT_TITLE | |
| 39 | return info_cache.title | |
| 40 | ||
| 41 | func set_title(title): | |
| 42 | update_info({ | |
| 43 | "title": title | |
| 44 | }) | |
| 45 | ||
| 46 | func update_info(updates): | |
| 47 | var data = {} | |
| 48 | ||
| 49 | if info_cache != null: | |
| 50 | for key in info_cache.keys(): | |
| 51 | data[key] = info_cache[key] | |
| 52 | ||
| 53 | for key in updates.keys(): | |
| 54 | data[key] = info_cache[key] | |
| 55 | ||
| 56 | var json = JSON.print(data) | |
| 57 | var info_url = url + level_file | |
| 58 | ||
| 59 | $updateInfoRequest.request(info_url, [], true, HTTPClient.METHOD_PUT, json) | |
| 60 | ||
| 61 | func load_info(): | |
| 62 | var info_url = url + level_file | |
| 63 | $loadInfoRequest.request(info_url) | |
| 64 | ||
| 65 | func resolve_url(): | |
| 66 | var url_record = url + '.well-known/hyper' | |
| 67 | $resolveURLRequest.request(url_record) | |
| 68 | ||
| 69 | func download_image(url: String): | |
| 70 | print('Loading image: ', url) | |
| 71 | var file_name = url.get_file() | |
| 72 | var key = parser.parse(url).host | |
| 73 | # Save the picture with the profile key prefixed to it | |
| 74 | picture_path = cache_folder + key + '-' + file_name | |
| 75 | $loadImageRequest.download_file = picture_path | |
| 76 | $loadImageRequest.request(url) | |
| 77 | ||
| 78 | func upload_image(path: String): | |
| 79 | var picture_name = path.get_file() | |
| 80 | var picture_url = url + picture_name | |
| 81 | ||
| 82 | $uploader.upload_file(picture_url, path) | |
| 83 | ||
| 84 | func _on_loadInfoRequest_request_completed(_result, response_code, _headers, body): | |
| 85 | print("Loaded info", _result, " ", response_code) | |
| 86 | if response_code != 200: | |
| 87 | printerr('Cannot load existing level info: ', response_code) | |
| 88 | emit_signal("no_info") | |
| 89 | return | |
| 90 | ||
| 91 | var parsed = JSON.parse(body.get_string_from_utf8()) | |
| 92 | if parsed.error != OK: | |
| 93 | printerr(parsed.error_string) | |
| 94 | emit_signal("no_info") | |
| 95 | return | |
| 96 | ||
| 97 | info_cache = parsed.result | |
| 98 | ||
| 99 | var title = info_cache.title | |
| 100 | var image = info_cache.image | |
| 101 | ||
| 102 | emit_signal("info", title, image) | |
| 103 | ||
| 104 | if image != null and autoload_image: | |
| 105 | download_image(image) | |
| 106 | ||
| 107 | func _on_uploader_uploaded(resolved_url, response_code): | |
| 108 | if response_code != 200: | |
| 109 | printerr('Unable to upload file ') | |
| 110 | return | |
| 111 | ||
| 112 | if !url || (url.length() == 0): | |
| 113 | printerr("Unable to load Link header for image.") | |
| 114 | return | |
| 115 | ||
| 116 | update_info({ | |
| 117 | "image": resolved_url | |
| 118 | }) | |
| 119 | ||
| 120 | download_image(resolved_url) | |
| 121 | ||
| 122 | func _on_loadImageRequest_request_completed(result, response_code, _headers, body): | |
| 123 | if result != OK or response_code != 200: | |
| 124 | printerr('Unable to load image: ', result, ": ", body.get_string_from_utf8()) | |
| 125 | return | |
| 126 | ||
| 127 | print(result, ' Image got downloaded: ', picture_path) | |
| 128 | ||
| 129 | emit_signal('image', picture_path) | |
| 130 | ||
| 131 | func _on_updateInfoRequest_request_completed(_result, _response_code, _headers, _body): | |
| 132 | emit_signal("updated") | |
| 133 | ||
| 134 | func _on_resolveURLRequest_request_completed(result, response_code, _headers, body: PoolByteArray): | |
| 135 | if result != OK or response_code != 200: | |
| 136 | printerr('Unable to load final profile URL') | |
| 137 | return | |
| 138 | ||
| 139 | var lines = body.get_string_from_utf8() | |
| 140 | var url = lines.split('\n')[0] | |
| 141 | if !url.ends_with('/'): | |
| 142 | url += '/' | |
| 143 | emit_signal('resolved', url) |
| … | … | @@ -0,0 +1,29 @@ |
| 1 | [gd_scene load_steps=4 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/LevelInfo.gd" type="Script" id=1] | |
| 4 | [ext_resource path="res://src/HyperRequest/HyperRequest.gd" type="Script" id=2] | |
| 5 | [ext_resource path="res://src/HyperRequest/HyperUploader.gd" type="Script" id=3] | |
| 6 | ||
| 7 | [node name="LevelInfo" type="Node"] | |
| 8 | script = ExtResource( 1 ) | |
| 9 | ||
| 10 | [node name="loadInfoRequest" type="HTTPRequest" parent="."] | |
| 11 | script = ExtResource( 2 ) | |
| 12 | ||
| 13 | [node name="loadImageRequest" type="HTTPRequest" parent="."] | |
| 14 | script = ExtResource( 2 ) | |
| 15 | ||
| 16 | [node name="updateInfoRequest" type="HTTPRequest" parent="."] | |
| 17 | script = ExtResource( 2 ) | |
| 18 | ||
| 19 | [node name="resolveURLRequest" type="HTTPRequest" parent="."] | |
| 20 | script = ExtResource( 2 ) | |
| 21 | ||
| 22 | [node name="uploader" type="Node" parent="."] | |
| 23 | script = ExtResource( 3 ) | |
| 24 | ||
| 25 | [connection signal="request_completed" from="loadInfoRequest" to="." method="_on_loadInfoRequest_request_completed"] | |
| 26 | [connection signal="request_completed" from="loadImageRequest" to="." method="_on_loadImageRequest_request_completed"] | |
| 27 | [connection signal="request_completed" from="updateInfoRequest" to="." method="_on_updateInfoRequest_request_completed"] | |
| 28 | [connection signal="request_completed" from="resolveURLRequest" to="." method="_on_resolveURLRequest_request_completed"] | |
| 29 | [connection signal="uploaded" from="uploader" to="." method="_on_uploader_uploaded"] |
| … | … | @@ -0,0 +1,18 @@ |
| 1 | extends VBoxContainer | |
| 2 | ||
| 3 | # Use gossip to discover levels (including your own?) | |
| 4 | # Button to open up LoadLevel to create your own level to enter | |
| 5 | ||
| 6 | # Declare member variables here. Examples: | |
| 7 | # var a = 2 | |
| 8 | # var b = "text" | |
| 9 | ||
| 10 | ||
| 11 | # Called when the node enters the scene tree for the first time. | |
| 12 | func _ready(): | |
| 13 | pass # Replace with function body. | |
| 14 | ||
| 15 | ||
| 16 | # Called every frame. 'delta' is the elapsed time since the previous frame. | |
| 17 | #func _process(delta): | |
| 18 | # pass |
| … | … | @@ -0,0 +1,11 @@ |
| 1 | [gd_scene load_steps=2 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/LevelSelect.gd" type="Script" id=1] | |
| 4 | ||
| 5 | [node name="Control" type="VBoxContainer"] | |
| 6 | anchor_right = 1.0 | |
| 7 | anchor_bottom = 1.0 | |
| 8 | script = ExtResource( 1 ) | |
| 9 | __meta__ = { | |
| 10 | "_edit_use_anchors_": false | |
| 11 | } |
| … | … | @@ -0,0 +1,87 @@ |
| 1 | extends "res://src/ResizeRoot.gd" | |
| 2 | ||
| 3 | # TODO: Better error dialogs | |
| 4 | ||
| 5 | signal profile_ready(url) | |
| 6 | ||
| 7 | export var level_url = 'hyper://flyballs-level/' | |
| 8 | export var spawn_gateway = true | |
| 9 | ||
| 10 | func _ready(): | |
| 11 | if spawn_gateway: $HyperGateway.start() | |
| 12 | ||
| 13 | func load_level(): | |
| 14 | print("Initial level load") | |
| 15 | $LevelInfo.url = level_url | |
| 16 | $LevelInfo.load_info() | |
| 17 | ||
| 18 | func hide_loader(): | |
| 19 | $Loader.hide() | |
| 20 | $Form.show() | |
| 21 | ||
| 22 | func show_loader(): | |
| 23 | $Loader.show() | |
| 24 | $Form.hide() | |
| 25 | ||
| 26 | func get_title(): | |
| 27 | return $Form/TitleInput.text | |
| 28 | ||
| 29 | func load_image(image: String): | |
| 30 | print('Loading image') | |
| 31 | show_loader() | |
| 32 | $LevelInfo.download_image(image) | |
| 33 | ||
| 34 | func _on_ImageChooser_pressed(): | |
| 35 | print('Choosing image') | |
| 36 | $FileDialog.popup() | |
| 37 | ||
| 38 | func _on_FileDialog_file_selected(path: String): | |
| 39 | print('Selected image, uploading:', path) | |
| 40 | show_loader() | |
| 41 | $LevelInfo.upload_image(path) | |
| 42 | ||
| 43 | func _on_HyperGateway_started_gateway(_pid): | |
| 44 | load_level() | |
| 45 | ||
| 46 | func _on_StartButton_pressed(): | |
| 47 | print("Starting next step") | |
| 48 | show_loader() | |
| 49 | $LevelInfo.resolve_url() | |
| 50 | ||
| 51 | func _on_LevelInfo_error(err): | |
| 52 | # TODO: Show an error dialog? | |
| 53 | print("Error interacting with profile:", err) | |
| 54 | ||
| 55 | func _on_LevelInfo_image(path): | |
| 56 | print("Image loaded, setting texture") | |
| 57 | hide_loader() | |
| 58 | var texture = ImageTexture.new() | |
| 59 | var image = Image.new() | |
| 60 | image.load(path) | |
| 61 | texture.create_from_image(image) | |
| 62 | texture.set_size_override(Vector2(64, 64)) | |
| 63 | ||
| 64 | $Form/ImageChooser.icon = texture | |
| 65 | ||
| 66 | func _on_LevelInfo_info(title: String, image: String): | |
| 67 | print("Loaded level info:", {"title": title, "image": image}) | |
| 68 | $Form/StartButton.disabled = false | |
| 69 | $Form/TitleInput.text = title | |
| 70 | ||
| 71 | if image != null && image.length() != 0: | |
| 72 | load_image(image) | |
| 73 | else: hide_loader() | |
| 74 | ||
| 75 | func _on_LevelInfo_updated(): | |
| 76 | print('Updated level info"') | |
| 77 | hide_loader() | |
| 78 | $Form/StartButton.disabled = false | |
| 79 | ||
| 80 | func _on_LevelInfo_resolved(url): | |
| 81 | print('Profile ready') | |
| 82 | print(url) | |
| 83 | emit_signal('profile_ready', url) | |
| 84 | ||
| 85 | func _on_LevelInfo_no_info(): | |
| 86 | print('No existing profile info') | |
| 87 | hide_loader() |
| … | … | @@ -0,0 +1,93 @@ |
| 1 | [gd_scene load_steps=4 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/LoadLevel.gd" type="Script" id=1] | |
| 4 | [ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=2] | |
| 5 | [ext_resource path="res://src/MultiplayerDemo/LevelInfo.tscn" type="PackedScene" id=3] | |
| 6 | ||
| 7 | [node name="LoadLevel" type="VBoxContainer"] | |
| 8 | anchor_right = 1.0 | |
| 9 | anchor_bottom = 1.0 | |
| 10 | script = ExtResource( 1 ) | |
| 11 | __meta__ = { | |
| 12 | "_edit_use_anchors_": false | |
| 13 | } | |
| 14 | ||
| 15 | [node name="Loader" type="CenterContainer" parent="."] | |
| 16 | margin_right = 666.0 | |
| 17 | margin_bottom = 666.0 | |
| 18 | size_flags_horizontal = 3 | |
| 19 | size_flags_vertical = 3 | |
| 20 | ||
| 21 | [node name="Label" type="Label" parent="Loader"] | |
| 22 | margin_left = 308.0 | |
| 23 | margin_top = 326.0 | |
| 24 | margin_right = 357.0 | |
| 25 | margin_bottom = 340.0 | |
| 26 | text = "Loading" | |
| 27 | ||
| 28 | [node name="Form" type="VBoxContainer" parent="."] | |
| 29 | visible = false | |
| 30 | margin_top = 335.0 | |
| 31 | margin_right = 666.0 | |
| 32 | margin_bottom = 666.0 | |
| 33 | size_flags_vertical = 3 | |
| 34 | ||
| 35 | [node name="TitleLabel" type="Label" parent="Form"] | |
| 36 | margin_right = 666.0 | |
| 37 | margin_bottom = 14.0 | |
| 38 | text = "Level Title" | |
| 39 | ||
| 40 | [node name="TitleInput" type="LineEdit" parent="Form"] | |
| 41 | margin_top = 18.0 | |
| 42 | margin_right = 666.0 | |
| 43 | margin_bottom = 42.0 | |
| 44 | text = "New Level" | |
| 45 | ||
| 46 | [node name="PictureLabel" type="Label" parent="Form"] | |
| 47 | margin_top = 46.0 | |
| 48 | margin_right = 666.0 | |
| 49 | margin_bottom = 60.0 | |
| 50 | text = "Picture" | |
| 51 | ||
| 52 | [node name="ImageChooser" type="Button" parent="Form"] | |
| 53 | margin_top = 64.0 | |
| 54 | margin_right = 666.0 | |
| 55 | margin_bottom = 84.0 | |
| 56 | text = "Choose Image" | |
| 57 | ||
| 58 | [node name="StartButton" type="Button" parent="Form"] | |
| 59 | margin_top = 88.0 | |
| 60 | margin_right = 666.0 | |
| 61 | margin_bottom = 108.0 | |
| 62 | disabled = true | |
| 63 | text = "Start" | |
| 64 | ||
| 65 | [node name="HyperGateway" type="Node" parent="."] | |
| 66 | script = ExtResource( 2 ) | |
| 67 | autoStart = false | |
| 68 | ||
| 69 | [node name="FileDialog" type="FileDialog" parent="."] | |
| 70 | margin_top = 536.0 | |
| 71 | margin_right = 666.0 | |
| 72 | margin_bottom = 666.0 | |
| 73 | popup_exclusive = true | |
| 74 | window_title = "Open a File" | |
| 75 | resizable = true | |
| 76 | dialog_autowrap = true | |
| 77 | mode = 0 | |
| 78 | access = 2 | |
| 79 | current_dir = "/home/mauve/programming/godot/hypergodot" | |
| 80 | current_path = "/home/mauve/programming/godot/hypergodot/" | |
| 81 | ||
| 82 | [node name="LevelInfo" parent="." instance=ExtResource( 3 )] | |
| 83 | ||
| 84 | [connection signal="pressed" from="Form/ImageChooser" to="." method="_on_ImageChooser_pressed"] | |
| 85 | [connection signal="pressed" from="Form/StartButton" to="." method="_on_StartButton_pressed"] | |
| 86 | [connection signal="started_gateway" from="HyperGateway" to="." method="_on_HyperGateway_started_gateway"] | |
| 87 | [connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"] | |
| 88 | [connection signal="error" from="LevelInfo" to="." method="_on_LevelInfo_error"] | |
| 89 | [connection signal="image" from="LevelInfo" to="." method="_on_LevelInfo_image"] | |
| 90 | [connection signal="info" from="LevelInfo" to="." method="_on_LevelInfo_info"] | |
| 91 | [connection signal="no_info" from="LevelInfo" to="." method="_on_LevelInfo_no_info"] | |
| 92 | [connection signal="resolved" from="LevelInfo" to="." method="_on_LevelInfo_resolved"] | |
| 93 | [connection signal="updated" from="LevelInfo" to="." method="_on_LevelInfo_updated"] |
| … | … | @@ -0,0 +1,34 @@ |
| 1 | [remap] | |
| 2 | ||
| 3 | importer="texture" | |
| 4 | type="StreamTexture" | |
| 5 | path="res://.import/background.png-4cff8a31e972161bb385b3e416e2e00b.stex" | |
| 6 | metadata={ | |
| 7 | "vram_texture": false | |
| 8 | } | |
| 9 | ||
| 10 | [deps] | |
| 11 | ||
| 12 | source_file="res://src/MultiplayerDemo/background.png" | |
| 13 | dest_files=[ "res://.import/background.png-4cff8a31e972161bb385b3e416e2e00b.stex" ] | |
| 14 | ||
| 15 | [params] | |
| 16 | ||
| 17 | compress/mode=0 | |
| 18 | compress/lossy_quality=0.7 | |
| 19 | compress/hdr_mode=0 | |
| 20 | compress/bptc_ldr=0 | |
| 21 | compress/normal_map=0 | |
| 22 | flags/repeat=0 | |
| 23 | flags/filter=true | |
| 24 | flags/mipmaps=false | |
| 25 | flags/anisotropic=false | |
| 26 | flags/srgb=2 | |
| 27 | process/fix_alpha_border=true | |
| 28 | process/premult_alpha=false | |
| 29 | process/HDR_as_SRGB=false | |
| 30 | process/invert_color=false | |
| 31 | stream=false | |
| 32 | size_limit=0 | |
| 33 | detect_3d=true | |
| 34 | svg/scale=1.0 |
| … | … | @@ -0,0 +1,39 @@ |
| 1 | ||
| 2 | <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 69 69" width="69" height="69"> | |
| 3 | <style> | |
| 4 | .dot { | |
| 5 | fill: rgb(165, 24, 201); | |
| 6 | stroke: #FCFCFC; | |
| 7 | stroke-width: 1; | |
| 8 | } | |
| 9 | .bg { | |
| 10 | fill: #111; | |
| 11 | } | |
| 12 | </style> | |
| 13 | <rect class="bg" width="69" height="69" /> | |
| 14 | <g transform="rotate(-90, 34.5 34.5) translate(34.5, 34.5)"> | |
| 15 | <!--<circle class="dot" r="5.75" cx="11.5" cy="0" /> | |
| 16 | <circle class="dot" r="5.75" cx="5.750000000000001" cy="9.959292143521044" /> | |
| 17 | <circle class="dot" r="5.75" cx="-5.749999999999997" cy="9.959292143521045" /> | |
| 18 | <circle class="dot" r="5.75" cx="-11.5" cy="1.4083438190194562e-15" /> | |
| 19 | <circle class="dot" r="5.75" cx="-5.750000000000005" cy="-9.959292143521042" /> | |
| 20 | <circle class="dot" r="5.75" cx="5.750000000000001" cy="-9.959292143521044" />--> | |
| 21 | ||
| 22 | <!--<circle class="dot" r="5.75" cx="19.91858428704209" cy="11.499999999999998" /> | |
| 23 | <circle class="dot" r="5.75" cx="1.4083438190194562e-15" cy="23" /> | |
| 24 | <circle class="dot" r="5.75" cx="-19.91858428704209" cy="11.499999999999998" /> | |
| 25 | <circle class="dot" r="5.75" cx="-19.918584287042087" cy="-11.500000000000002" /> | |
| 26 | <circle class="dot" r="5.75" cx="-4.225031457058368e-15" cy="-23" /> | |
| 27 | <circle class="dot" r="5.75" cx="19.918584287042084" cy="-11.50000000000001" />--> | |
| 28 | ||
| 29 | <circle class="dot" r="5.75" cx="25.875" cy="0" /> | |
| 30 | <circle class="dot" r="5.75" cx="12.937500000000004" cy="22.408407322922347" /> | |
| 31 | <circle class="dot" r="5.75" cx="-12.937499999999995" cy="22.40840732292235" /> | |
| 32 | <circle class="dot" r="5.75" cx="-25.875" cy="3.1687735927937763e-15" /> | |
| 33 | <circle class="dot" r="5.75" cx="-12.93750000000001" cy="-22.408407322922347" /> | |
| 34 | <circle class="dot" r="5.75" cx="12.937500000000004" cy="-22.408407322922347" /> | |
| 35 | ||
| 36 | <circle class="dot" r="5.75"/> | |
| 37 | </g> | |
| 38 | </svg> | |
| 39 |
| … | … | @@ -0,0 +1,34 @@ |
| 1 | [remap] | |
| 2 | ||
| 3 | importer="texture" | |
| 4 | type="StreamTexture" | |
| 5 | path="res://.import/background.svg-6e4643371d3cbed7c38493d11a0b6df3.stex" | |
| 6 | metadata={ | |
| 7 | "vram_texture": false | |
| 8 | } | |
| 9 | ||
| 10 | [deps] | |
| 11 | ||
| 12 | source_file="res://src/MultiplayerDemo/background.svg" | |
| 13 | dest_files=[ "res://.import/background.svg-6e4643371d3cbed7c38493d11a0b6df3.stex" ] | |
| 14 | ||
| 15 | [params] | |
| 16 | ||
| 17 | compress/mode=0 | |
| 18 | compress/lossy_quality=0.7 | |
| 19 | compress/hdr_mode=0 | |
| 20 | compress/bptc_ldr=0 | |
| 21 | compress/normal_map=0 | |
| 22 | flags/repeat=0 | |
| 23 | flags/filter=true | |
| 24 | flags/mipmaps=false | |
| 25 | flags/anisotropic=false | |
| 26 | flags/srgb=2 | |
| 27 | process/fix_alpha_border=true | |
| 28 | process/premult_alpha=false | |
| 29 | process/HDR_as_SRGB=false | |
| 30 | process/invert_color=false | |
| 31 | stream=false | |
| 32 | size_limit=0 | |
| 33 | detect_3d=true | |
| 34 | svg/scale=1.0 |
| … | … | @@ -0,0 +1,68 @@ |
| 1 | const BLACK = '#111' | |
| 2 | const WHITE = '#FCFCFC' | |
| 3 | const PURPLE = 'rgb(165, 24, 201)' | |
| 4 | const PINK = '#E62EA5' | |
| 5 | ||
| 6 | const WIDTH = 69 | |
| 7 | const HEIGHT = WIDTH | |
| 8 | ||
| 9 | const OUTLINE_WIDTH = 1 | |
| 10 | ||
| 11 | const DOT_SIZE = WIDTH / 12 | |
| 12 | ||
| 13 | console.log(` | |
| 14 | <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 ${WIDTH} ${HEIGHT}" width="${WIDTH}" height="${HEIGHT}"> | |
| 15 | <style> | |
| 16 | .dot { | |
| 17 | fill: ${PURPLE}; | |
| 18 | stroke: ${WHITE}; | |
| 19 | stroke-width: ${OUTLINE_WIDTH}; | |
| 20 | } | |
| 21 | .bg { | |
| 22 | fill: ${BLACK}; | |
| 23 | } | |
| 24 | </style> | |
| 25 | <rect class="bg" width="${WIDTH}" height="${HEIGHT}" /> | |
| 26 | <g transform="rotate(-90, ${WIDTH/2} ${HEIGHT/2}) translate(${WIDTH/2}, ${HEIGHT/2})"> | |
| 27 | <!--${makeDots(6, WIDTH / 6)}--> | |
| 28 | ||
| 29 | <!--${makeDots(6, (WIDTH / 6) * 2, 30)}--> | |
| 30 | ||
| 31 | ${makeDots(6, (WIDTH / 6) * 2 + DOT_SIZE / 2)} | |
| 32 | ||
| 33 | <circle class="dot" r="${DOT_SIZE}"/> | |
| 34 | </g> | |
| 35 | </svg> | |
| 36 | `) | |
| 37 | ||
| 38 | function makeDots (n, scale, offset = 0, size = DOT_SIZE) { | |
| 39 | return makeCorners(n, offset).map((theta) => { | |
| 40 | return `<circle class="dot" r="${size}" ${centerPoint(theta, scale)} />` | |
| 41 | }).join('\n') | |
| 42 | } | |
| 43 | ||
| 44 | function makeCorners (n, offset = 0) { | |
| 45 | const increment = 360 / n | |
| 46 | const corners = [] | |
| 47 | for (let i = 0; i < n; i++) { | |
| 48 | corners.push(i * increment + offset) | |
| 49 | } | |
| 50 | ||
| 51 | return corners | |
| 52 | } | |
| 53 | ||
| 54 | function centerPoint (theta, scale = 1) { | |
| 55 | return `cx="${xAt(theta) * scale}" cy="${yAt(theta) * scale}"` | |
| 56 | } | |
| 57 | ||
| 58 | function xAt (theta) { | |
| 59 | return Math.cos(toRad(theta)) | |
| 60 | } | |
| 61 | ||
| 62 | function yAt (theta) { | |
| 63 | return Math.sin(toRad(theta)) | |
| 64 | } | |
| 65 | ||
| 66 | function toRad (theta) { | |
| 67 | return Math.PI / 180 * theta | |
| 68 | } |