commit 74694080b48b163cca752812c576aff98bdd9368 Author: Fat mimir Date: Mon Feb 27 21:27:32 2023 -0600 Simple networking diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..adc26df --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..286319a --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0grjf1jatuoi" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/main.gd b/main.gd new file mode 100644 index 0000000..84ebbce --- /dev/null +++ b/main.gd @@ -0,0 +1,53 @@ +extends Node3D + +const Player := preload("res://player.tscn") + +@onready +var ui_main_menu := $UIMainMenu + +@onready +var players := $Players + + +func _ready(): + pass + + +func _process(_delta): + pass + + +func _on_button_start_as_server_pressed(): + var peer := ENetMultiplayerPeer.new() + peer.create_server(9080, 10) + peer.peer_connected.connect(self._on_peer_connect) + peer.peer_disconnected.connect(self._on_peer_disconnected) + multiplayer.multiplayer_peer = peer + ui_main_menu.visible = false + + # Server connected + _on_peer_connect(get_multiplayer_authority()) + + +func _on_button_start_as_client_pressed(): + var peer := ENetMultiplayerPeer.new() + peer.create_client("localhost", 9080) + multiplayer.multiplayer_peer = peer + ui_main_menu.visible = false + + +func _on_peer_connect(id): + print("(server) peer connected: ", id) + var player: Node3D = Player.instantiate() + player.name = str(id) + player.set_multiplayer_authority(id) + player.global_transform = players.global_transform.translated(Vector3.UP * 3) + players.add_child(player, true) + + +func _on_peer_disconnected(id): + print("(server) peer disconnected: ", id) + var player := players.get_node_or_null(str(id)) + if player: + player.queue_free() + diff --git a/main.tscn b/main.tscn new file mode 100644 index 0000000..b5336a6 --- /dev/null +++ b/main.tscn @@ -0,0 +1,62 @@ +[gd_scene load_steps=3 format=3 uid="uid://cncputkhqxo0m"] + +[ext_resource type="Script" path="res://main.gd" id="1_mw2j2"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p8g5e"] +albedo_color = Color(0.211765, 0.211765, 0.211765, 1) + +[node name="Main" type="Node3D"] +script = ExtResource("1_mw2j2") + +[node name="Map" type="CSGCombiner3D" parent="."] +use_collision = true + +[node name="Floor" type="CSGBox3D" parent="Map"] +size = Vector3(20, 0.1, 20) +material = SubResource("StandardMaterial3D_p8g5e") + +[node name="Sun" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, -4.68001, 6.95, 4.07778) +shadow_enabled = true + +[node name="MainCamera" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 10, 13) + +[node name="Players" type="Node3D" parent="."] + +[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://player.tscn") +spawn_path = NodePath("../Players") + +[node name="UIMainMenu" type="ColorRect" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 1) + +[node name="ContainerOptions" type="VBoxContainer" parent="UIMainMenu"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -20.0 +offset_right = 20.0 +offset_bottom = 20.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ButtonStartAsServer" type="Button" parent="UIMainMenu/ContainerOptions"] +layout_mode = 2 +text = "Start as server" + +[node name="ButtonStartAsClient" type="Button" parent="UIMainMenu/ContainerOptions"] +layout_mode = 2 +text = "Start as client" + +[connection signal="pressed" from="UIMainMenu/ContainerOptions/ButtonStartAsServer" to="." method="_on_button_start_as_server_pressed"] +[connection signal="pressed" from="UIMainMenu/ContainerOptions/ButtonStartAsClient" to="." method="_on_button_start_as_client_pressed"] diff --git a/player.gd b/player.gd new file mode 100644 index 0000000..78451d5 --- /dev/null +++ b/player.gd @@ -0,0 +1,86 @@ +extends CharacterBody3D + +# We don't actually want to fully interpolate between states +# since it may result in "jumpy" transitions +const STATE_INTERP_MIN = 0.2 +const STATE_INTERP_MAX = 0.8 + +var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") +var sync_state := PackedByteArray(): get = _get_sync_state, set = _set_sync_state + +@export var speed := 5.0 +@export var jump_speed := 5.0 +@onready var gametag := $Gametag +@onready var target_origin := global_transform.origin + +var last_snapshot_tag := 0 +var snapshot_interp_start := 0.0 +var snapshot_interp_end := 0.0 + +func _process(_delta): + gametag.text = "[%s %d]" % [name, get_multiplayer_authority()] + +func _physics_process(delta): + if is_multiplayer_authority(): + velocity.y += -gravity * delta + var input := Input.get_vector("left", "right", "forward", "backward") + var direction := transform.basis * Vector3(input.x, 0, input.y) + velocity.x = direction.x * speed + velocity.z = direction.z * speed + move_and_slide() + + if is_on_floor() and Input.is_action_just_pressed("jump"): + velocity.y = jump_speed + + else: + var interp_value = remap_range( + Time.get_ticks_msec(), + snapshot_interp_start, + snapshot_interp_end, + STATE_INTERP_MIN, + STATE_INTERP_MAX + ) + if interp_value >= 0.0 and interp_value <= 1.0: + global_transform.origin = global_transform.origin.slerp( + target_origin, interp_value + ) + else: + global_transform.origin = target_origin + + +func _enter_tree(): + if name == str(multiplayer.get_unique_id()): + set_multiplayer_authority(multiplayer.get_unique_id()) + + +func _get_sync_state(): + var encoder := PackedByteArray() + encoder.resize(20) + encoder.encode_u64(0, Time.get_ticks_msec()) + encoder.encode_float(8, transform.origin.x) + encoder.encode_float(12, transform.origin.y) + encoder.encode_float(16, transform.origin.z) + return encoder + +func _set_sync_state(value): + assert( + typeof(value) == TYPE_PACKED_BYTE_ARRAY and value.size() == 20, + "Invalid `sync_state` array type or size (must be TYPE_RAW_ARRAY of size 20)." + ) + var time_tag: int = value.decode_u64(0) + if time_tag > last_snapshot_tag: + var snapshot_time_diff = time_tag - last_snapshot_tag + if global_transform: + global_transform.origin = target_origin # Override last origin + + snapshot_interp_start = Time.get_ticks_msec() + snapshot_interp_end = Time.get_ticks_msec() + snapshot_time_diff + last_snapshot_tag = time_tag + target_origin = Vector3( + value.decode_float(8), + value.decode_float(12), + value.decode_float(16), + ) + +func remap_range(value, in_a, in_b, out_a, out_b): + return (value - in_a) / (in_b - in_a) * (out_b - out_a) + out_a diff --git a/player.tscn b/player.tscn new file mode 100644 index 0000000..bc9b147 --- /dev/null +++ b/player.tscn @@ -0,0 +1,32 @@ +[gd_scene load_steps=5 format=3 uid="uid://cq06n5dnq6xc2"] + +[ext_resource type="Script" path="res://player.gd" id="1_dokki"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_k38on"] + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_wtqcn"] + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_my5gd"] +properties/0/path = NodePath(".:sync_state") +properties/0/spawn = true +properties/0/sync = true + +[node name="Player" type="CharacterBody3D"] +script = ExtResource("1_dokki") + +[node name="Gametag" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.148, 0) +pixel_size = 0.03 +text = "AAAAAAAAAAAAAA" + +[node name="CollisionShape" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +shape = SubResource("CapsuleShape3D_k38on") + +[node name="MeshInstance" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +mesh = SubResource("CapsuleMesh_wtqcn") + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_interval = 0.1 +replication_config = SubResource("SceneReplicationConfig_my5gd") diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..247ad4e --- /dev/null +++ b/project.godot @@ -0,0 +1,44 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Godot4 Standard Networking" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.0", "Forward Plus") +config/icon="res://icon.svg" + +[input] + +forward={ +"deadzone": 0.5, +"events": [null, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null) +] +} +backward={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) +] +} +left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) +] +} +right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) +] +} +jump={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null) +] +}