diff --git a/Scenes/Player.tscn b/Scenes/Player.tscn index 649872a..7350165 100644 --- a/Scenes/Player.tscn +++ b/Scenes/Player.tscn @@ -1,13 +1,32 @@ -[gd_scene load_steps=4 format=3 uid="uid://b3gx0bl43lku3"] +[gd_scene load_steps=7 format=3 uid="uid://b3gx0bl43lku3"] [ext_resource type="Script" path="res://Scripts/Player.cs" id="1_0btyt"] [ext_resource type="Texture2D" uid="uid://c4als6t3k4myc" path="res://Sprites/Player.png" id="1_cqpqa"] +[ext_resource type="Script" path="res://Scripts/Logic/PlayerInput.cs" id="3_tvoua"] [sub_resource type="CircleShape2D" id="CircleShape2D_68yf8"] radius = 26.0768 -[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("PlayerDashCountdown")] +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_bekoo"] +properties/0/path = NodePath(".:position") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath(".:PlayerId") +properties/1/spawn = true +properties/1/replication_mode = 1 + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_hojn2"] +properties/0/path = NodePath("InputSynchronizer:IsDashing") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath("InputSynchronizer:MovementDirection") +properties/1/spawn = true +properties/1/replication_mode = 1 + +[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("PlayerCamera", "PlayerInput", "PlayerDashCountdown")] script = ExtResource("1_0btyt") +PlayerCamera = NodePath("Camera2D") +PlayerInput = NodePath("InputSynchronizer") PlayerDashCountdown = NodePath("DashCountdown") [node name="Sprite2D" type="Sprite2D" parent="."] @@ -18,5 +37,18 @@ texture = ExtResource("1_cqpqa") [node name="CollisionShape2D" type="CollisionShape2D" parent="."] shape = SubResource("CircleShape2D_68yf8") +[node name="Camera2D" type="Camera2D" parent="."] +enabled = false +process_callback = 0 +position_smoothing_enabled = true +rotation_smoothing_enabled = true + [node name="DashCountdown" type="Timer" parent="."] one_shot = true + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_bekoo") + +[node name="InputSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_hojn2") +script = ExtResource("3_tvoua") diff --git a/Scenes/Root.tscn b/Scenes/Root.tscn index 3b911f1..078d053 100644 --- a/Scenes/Root.tscn +++ b/Scenes/Root.tscn @@ -1,26 +1,23 @@ -[gd_scene load_steps=4 format=3 uid="uid://bjhmjrldq4lkt"] +[gd_scene load_steps=5 format=3 uid="uid://bjhmjrldq4lkt"] [ext_resource type="Script" path="res://Scripts/Multiplayer.cs" id="1_fym13"] [ext_resource type="PackedScene" uid="uid://b3gx0bl43lku3" path="res://Scenes/Player.tscn" id="1_vby0g"] [ext_resource type="PackedScene" uid="uid://bvll23f5ibd4v" path="res://Scenes/UI/MultiplayerUI.tscn" id="2_7o53i"] +[ext_resource type="Script" path="res://Scripts/Logic/World.cs" id="3_xwguj"] [node name="Node" type="Node"] -[node name="MultiplayerNode" type="Node" parent="." node_paths=PackedStringArray("Playground")] +[node name="MultiplayerNode" type="Node" parent="." node_paths=PackedStringArray("World")] script = ExtResource("1_fym13") -Playground = NodePath("../Playground") +World = NodePath("../World") [node name="MultiplayerUi" parent="." node_paths=PackedStringArray("MultiplayerController") instance=ExtResource("2_7o53i")] MultiplayerController = NodePath("../MultiplayerNode") -[node name="Playground" type="Node2D" parent="."] +[node name="World" type="Node2D" parent="."] +script = ExtResource("3_xwguj") +PlayerScene = ExtResource("1_vby0g") -[node name="Camera2D" type="Camera2D" parent="Playground"] -enabled = false -process_callback = 0 -position_smoothing_enabled = true -rotation_smoothing_enabled = true - -[node name="Player" parent="Playground" node_paths=PackedStringArray("PlayerCamera") instance=ExtResource("1_vby0g")] -position = Vector2(0, 2) -PlayerCamera = NodePath("../Camera2D") +[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."] +_spawnable_scenes = PackedStringArray("res://Scenes/Player.tscn") +spawn_path = NodePath("../World") diff --git a/Scripts/Logic/PlayerInput.cs b/Scripts/Logic/PlayerInput.cs new file mode 100644 index 0000000..60a30a3 --- /dev/null +++ b/Scripts/Logic/PlayerInput.cs @@ -0,0 +1,33 @@ +using Godot; + +namespace CodingLand.Scripts.Logic; + +public partial class PlayerInput : MultiplayerSynchronizer +{ + [Export] public bool IsDashing; + + [Export] public Vector2 MovementDirection; + + public override void _Ready() + { + if (GetMultiplayerAuthority() != Multiplayer.GetUniqueId()) + { + SetProcess(false); + SetPhysicsProcess(false); + } + } + + [Rpc(CallLocal = true)] + private void Dash() + { + IsDashing = true; + } + + public override void _Process(double delta) + { + MovementDirection = Input.GetVector("move_left", "move_right", "move_up", "move_down"); + + if (Input.IsActionJustPressed("move_dash")) + Rpc(nameof(Dash)); + } +} \ No newline at end of file diff --git a/Scripts/Logic/World.cs b/Scripts/Logic/World.cs new file mode 100644 index 0000000..7fd4127 --- /dev/null +++ b/Scripts/Logic/World.cs @@ -0,0 +1,68 @@ +using System.Numerics; +using Godot; +using Vector2 = Godot.Vector2; + +namespace CodingLand.Scripts.Logic; + +public partial class World : Node2D +{ + [Export] public PackedScene PlayerScene; + + public void StartGame() + { + if (!Multiplayer.IsServer()) + return; + + // Handling player connect / disconnect after this client connected + Multiplayer.PeerDisconnected += RemovePlayer_Adaptor; + Multiplayer.PeerConnected += AddPlayer_Adaptor; + + // Handling player connected before this client + foreach (var id in Multiplayer.GetPeers()) + AddPlayer(id); + + // Add this client as a player if client isn't a dedicated server + if (!OS.HasFeature("dedicated_server")) + AddPlayer(1); + } + + public override void _ExitTree() + { + if (!Multiplayer.IsServer()) + return; + + Multiplayer.PeerDisconnected -= RemovePlayer_Adaptor; + Multiplayer.PeerConnected -= AddPlayer_Adaptor; + } + + private string BuildPlayerName(int id) + { + return $"Player#{id}"; + } + + private void AddPlayer_Adaptor(long id) + => AddPlayer((int)id); + + private void RemovePlayer_Adaptor(long id) + => RemovePlayer((int)id); + + private void AddPlayer(int id) + { + var player = PlayerScene.Instantiate(); + player.SetPlayerId(id); + var position = Vector2.FromAngle(GD.Randf() * 2 * Mathf.Pi); + player.Position = new Vector2(position.X * 5f * GD.Randf(), position.Y * 5f * GD.Randf()); + player.Name = BuildPlayerName(id); + + AddChild(player, true); + } + + private void RemovePlayer(int id) + { + var name = BuildPlayerName(id); + if (!HasNode(name)) + return; + + GetNode(name).QueueFree(); + } +} \ No newline at end of file diff --git a/Scripts/Multiplayer.cs b/Scripts/Multiplayer.cs index ee6a593..b3a9bd8 100644 --- a/Scripts/Multiplayer.cs +++ b/Scripts/Multiplayer.cs @@ -1,23 +1,22 @@ -using System; +using CodingLand.Scripts.Logic; using Godot; namespace CodingLand.Scripts; public partial class Multiplayer : Node { - [Export] public Node2D Playground; + [Export] public World World; private void GameFreeze() { - Playground.Hide(); GetTree().Paused = true; + World.Hide(); } private void GameUnfreeze() { - Playground.Show(); - Playground.GetNode("Camera2D").Enabled = true; GetTree().Paused = false; + World.Show(); } public override void _Ready() @@ -41,9 +40,12 @@ public partial class Multiplayer : Node OS.Alert("Failed to start multiplayer server..."); return false; } + + GD.Print("Running game as server..."); Multiplayer.MultiplayerPeer = peer; GameUnfreeze(); + World.StartGame(); return true; } @@ -62,8 +64,11 @@ public partial class Multiplayer : Node return false; } + GD.Print("Running game as client..."); + Multiplayer.MultiplayerPeer = peer; GameUnfreeze(); + World.StartGame(); return true; } diff --git a/Scripts/Player.cs b/Scripts/Player.cs index 805db43..484b903 100644 --- a/Scripts/Player.cs +++ b/Scripts/Player.cs @@ -1,39 +1,54 @@ -using System; +using CodingLand.Scripts.Logic; using Godot; namespace CodingLand.Scripts; public partial class Player : CharacterBody2D { + private int _currentPlayerId = 1; + [Export] public float MaxSpeed = 400f; [Export] public float Acceleration = 500f; [Export] public float Deceleration = 500f; [Export] public float RotationSpeed = 5f; - [Export] public float CameraSmoothness = 0.05f; - [Export] public Camera2D PlayerCamera; - + + [Export] public PlayerInput PlayerInput; + [Export] public Timer PlayerDashCountdown; [Export] public float PlayerDashAcceleration = 2f; + [Export] public int PlayerId + { + get => _currentPlayerId; + set + { + _currentPlayerId = value; + PlayerInput.SetMultiplayerAuthority(value); + } + } + + public void SetPlayerId(int id) + { + PlayerId = id; + PlayerInput.SetMultiplayerAuthority(id); + } + + public override void _Ready() + { + if (PlayerId == Multiplayer.GetUniqueId()) + PlayerCamera.Enabled = true; + } + public override void _PhysicsProcess(double delta) { - var input = Vector2.Zero; + var input = PlayerInput.MovementDirection; - if (Input.IsActionPressed("move_right")) - input.X += 1; - if (Input.IsActionPressed("move_left")) - input.X -= 1; - if (Input.IsActionPressed("move_down")) - input.Y += 1; - if (Input.IsActionPressed("move_up")) - input.Y -= 1; - - input = input.Normalized(); if (input != Vector2.Zero) { + input = input.Normalized(); Velocity = Velocity.MoveToward(input * MaxSpeed, Acceleration * (float)delta); var finalRotation = input.Angle() + Mathf.Pi / 2; @@ -44,18 +59,14 @@ public partial class Player : CharacterBody2D Velocity = Velocity.MoveToward(Vector2.Zero, Deceleration * (float)delta); } - if (Input.IsActionJustPressed("move_dash") && PlayerDashCountdown.IsStopped()) + if (PlayerInput.IsDashing && PlayerDashCountdown.IsStopped()) { + PlayerInput.IsDashing = false; Velocity *= PlayerDashAcceleration; PlayerDashCountdown.Start(); } - + Position += Velocity * (float)delta; MoveAndSlide(); - - if (PlayerCamera != null) - { - PlayerCamera.Position = GlobalPosition; - } } } diff --git a/Scripts/UI/MultiplayerUi.cs b/Scripts/UI/MultiplayerUi.cs index f4d7003..7b2cbad 100644 --- a/Scripts/UI/MultiplayerUi.cs +++ b/Scripts/UI/MultiplayerUi.cs @@ -55,7 +55,7 @@ public partial class MultiplayerUi : Control if (result) Hide(); }; - StartAsServerButton.Pressed += () => + StartAsClientButton.Pressed += () => { if (!DoValidation()) {