瀏覽代碼

Created C# networked Pong

Duroxxigar 4 年之前
父節點
當前提交
3f8567f920
共有 43 個文件被更改,包括 886 次插入123 次删除
  1. 二進制
      2d/pong/left_pallete.png
  2. 二進制
      2d/pong/paddle.png
  3. 3 3
      2d/pong/paddle.png.import
  4. 5 4
      2d/pong/pong.tscn
  5. 二進制
      2d/pong/right_pallete.png
  6. 0 34
      2d/pong/right_pallete.png.import
  7. 6 0
      mono/multiplayer_pong/Pong Multiplayer with C#.csproj
  8. 19 0
      mono/multiplayer_pong/Pong Multiplayer with C#.sln
  9. 13 0
      mono/multiplayer_pong/README.md
  10. 二進制
      mono/multiplayer_pong/ball.png
  11. 3 3
      mono/multiplayer_pong/ball.png.import
  12. 16 0
      mono/multiplayer_pong/ball.tscn
  13. 二進制
      mono/multiplayer_pong/icon.png
  14. 3 3
      mono/multiplayer_pong/icon.png.import
  15. 102 0
      mono/multiplayer_pong/lobby.tscn
  16. 97 0
      mono/multiplayer_pong/logic/Ball.cs
  17. 143 0
      mono/multiplayer_pong/logic/Lobby.cs
  18. 70 0
      mono/multiplayer_pong/logic/Paddle.cs
  19. 81 0
      mono/multiplayer_pong/logic/Pong.cs
  20. 二進制
      mono/multiplayer_pong/paddle.png
  21. 34 0
      mono/multiplayer_pong/paddle.png.import
  22. 31 0
      mono/multiplayer_pong/paddle.tscn
  23. 92 0
      mono/multiplayer_pong/pong.tscn
  24. 56 0
      mono/multiplayer_pong/project.godot
  25. 二進制
      mono/multiplayer_pong/separator.png
  26. 3 3
      mono/multiplayer_pong/separator.png.import
  27. 二進制
      mono/pong/left_pallete.png
  28. 二進制
      mono/pong/paddle.png
  29. 34 0
      mono/pong/paddle.png.import
  30. 5 4
      mono/pong/pong.tscn
  31. 二進制
      mono/pong/right_pallete.png
  32. 10 6
      networking/multiplayer_pong/logic/ball.gd
  33. 3 2
      networking/multiplayer_pong/logic/lobby.gd
  34. 12 12
      networking/multiplayer_pong/logic/paddle.gd
  35. 4 3
      networking/multiplayer_pong/logic/pong.gd
  36. 2 7
      networking/multiplayer_pong/pong.tscn
  37. 0 1
      networking/multiplayer_pong/project.godot
  38. 二進制
      visual_script/pong/left_pallete.png
  39. 二進制
      visual_script/pong/paddle.png
  40. 34 0
      visual_script/pong/paddle.png.import
  41. 5 4
      visual_script/pong/pong.tscn
  42. 二進制
      visual_script/pong/right_pallete.png
  43. 0 34
      visual_script/pong/right_pallete.png.import

二進制
2d/pong/left_pallete.png


二進制
2d/pong/paddle.png


+ 3 - 3
mono/pong/left_pallete.png.import → 2d/pong/paddle.png.import

@@ -2,15 +2,15 @@
 
 importer="texture"
 type="StreamTexture"
-path="res://.import/left_pallete.png-bc33611074a0f886142e37c77bd2545a.stex"
+path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex"
 metadata={
 "vram_texture": false
 }
 
 [deps]
 
-source_file="res://left_pallete.png"
-dest_files=[ "res://.import/left_pallete.png-bc33611074a0f886142e37c77bd2545a.stex" ]
+source_file="res://paddle.png"
+dest_files=[ "res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" ]
 
 [params]
 

+ 5 - 4
2d/pong/pong.tscn

@@ -1,8 +1,7 @@
-[gd_scene load_steps=13 format=2]
+[gd_scene load_steps=12 format=2]
 
 [ext_resource path="res://logic/paddle.gd" type="Script" id=1]
-[ext_resource path="res://left_pallete.png" type="Texture" id=2]
-[ext_resource path="res://right_pallete.png" type="Texture" id=3]
+[ext_resource path="res://paddle.png" type="Texture" id=2]
 [ext_resource path="res://logic/ball.gd" type="Script" id=4]
 [ext_resource path="res://ball.png" type="Texture" id=5]
 [ext_resource path="res://separator.png" type="Texture" id=6]
@@ -32,6 +31,7 @@ __meta__ = {
 }
 
 [node name="Left" type="Area2D" parent="."]
+modulate = Color( 0, 1, 1, 1 )
 position = Vector2( 67.6285, 192.594 )
 script = ExtResource( 1 )
 
@@ -42,11 +42,12 @@ texture = ExtResource( 2 )
 shape = SubResource( 1 )
 
 [node name="Right" type="Area2D" parent="."]
+modulate = Color( 1, 0, 1, 1 )
 position = Vector2( 563.815, 188.919 )
 script = ExtResource( 1 )
 
 [node name="Sprite" type="Sprite" parent="Right"]
-texture = ExtResource( 3 )
+texture = ExtResource( 2 )
 
 [node name="Collision" type="CollisionShape2D" parent="Right"]
 shape = SubResource( 1 )

二進制
2d/pong/right_pallete.png


+ 0 - 34
2d/pong/right_pallete.png.import

@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="StreamTexture"
-path="res://.import/right_pallete.png-fc6e4a6a7c8197834656482b94708e47.stex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://right_pallete.png"
-dest_files=[ "res://.import/right_pallete.png-fc6e4a6a7c8197834656482b94708e47.stex" ]
-
-[params]
-
-compress/mode=0
-compress/lossy_quality=0.7
-compress/hdr_mode=0
-compress/bptc_ldr=0
-compress/normal_map=0
-flags/repeat=0
-flags/filter=false
-flags/mipmaps=false
-flags/anisotropic=false
-flags/srgb=2
-process/fix_alpha_border=true
-process/premult_alpha=false
-process/HDR_as_SRGB=false
-process/invert_color=false
-stream=false
-size_limit=0
-detect_3d=true
-svg/scale=1.0

+ 6 - 0
mono/multiplayer_pong/Pong Multiplayer with C#.csproj

@@ -0,0 +1,6 @@
+<Project Sdk="Godot.NET.Sdk/3.2.3">
+  <PropertyGroup>
+    <TargetFramework>net472</TargetFramework>
+    <RootNamespace>PongMultiplayer</RootNamespace>
+  </PropertyGroup>
+</Project>

+ 19 - 0
mono/multiplayer_pong/Pong Multiplayer with C#.sln

@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pong Multiplayer with C#", "Pong Multiplayer with C#.csproj", "{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+	Debug|Any CPU = Debug|Any CPU
+	ExportDebug|Any CPU = ExportDebug|Any CPU
+	ExportRelease|Any CPU = ExportRelease|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
+		{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
+		{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
+		{4BB6C2D0-FC11-466E-8C73-8DAD689F135A}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
+	EndGlobalSection
+EndGlobal

+ 13 - 0
mono/multiplayer_pong/README.md

@@ -0,0 +1,13 @@
+# Pong Multiplayer with C#
+
+A multiplayer demo of Pong. One player presses "host". The other presses "join". This only works locally.
+
+Language: [C#](https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html)
+
+Renderer: GLES 2
+
+Note: There is a GDScript version available [here](https://github.com/godotengine/godot-demo-projects/tree/master/networking/multiplayer_pong).
+
+## Screenshots
+
+![Screenshot](../../2d/pong/screenshots/pong.png)

二進制
mono/multiplayer_pong/ball.png


+ 3 - 3
2d/pong/left_pallete.png.import → mono/multiplayer_pong/ball.png.import

@@ -2,15 +2,15 @@
 
 importer="texture"
 type="StreamTexture"
-path="res://.import/left_pallete.png-bc33611074a0f886142e37c77bd2545a.stex"
+path="res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex"
 metadata={
 "vram_texture": false
 }
 
 [deps]
 
-source_file="res://left_pallete.png"
-dest_files=[ "res://.import/left_pallete.png-bc33611074a0f886142e37c77bd2545a.stex" ]
+source_file="res://ball.png"
+dest_files=[ "res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex" ]
 
 [params]
 

+ 16 - 0
mono/multiplayer_pong/ball.tscn

@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://ball.png" type="Texture" id=1]
+[ext_resource path="res://logic/Ball.cs" type="Script" id=2]
+
+[sub_resource type="CircleShape2D" id=1]
+radius = 4.65663
+
+[node name="Ball" type="Area2D"]
+script = ExtResource( 2 )
+
+[node name="Sprite" type="Sprite" parent="."]
+texture = ExtResource( 1 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource( 1 )

二進制
mono/multiplayer_pong/icon.png


+ 3 - 3
mono/pong/right_pallete.png.import → mono/multiplayer_pong/icon.png.import

@@ -2,15 +2,15 @@
 
 importer="texture"
 type="StreamTexture"
-path="res://.import/right_pallete.png-fc6e4a6a7c8197834656482b94708e47.stex"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
 metadata={
 "vram_texture": false
 }
 
 [deps]
 
-source_file="res://right_pallete.png"
-dest_files=[ "res://.import/right_pallete.png-fc6e4a6a7c8197834656482b94708e47.stex" ]
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
 
 [params]
 

+ 102 - 0
mono/multiplayer_pong/lobby.tscn

@@ -0,0 +1,102 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://logic/Lobby.cs" type="Script" id=1]
+
+[node name="Lobby" type="Control"]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -320.0
+margin_top = -200.0
+margin_right = 320.0
+margin_bottom = 200.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Title" type="Label" parent="."]
+margin_left = 210.0
+margin_top = 40.0
+margin_right = 430.0
+margin_bottom = 80.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "Multiplayer Pong"
+align = 1
+valign = 1
+
+[node name="LobbyPanel" type="Panel" parent="."]
+margin_left = 210.0
+margin_top = 160.0
+margin_right = 430.0
+margin_bottom = 270.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="AddressLabel" type="Label" parent="LobbyPanel"]
+margin_left = 10.0
+margin_top = 10.0
+margin_right = 62.0
+margin_bottom = 24.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "Address"
+
+[node name="Address" type="LineEdit" parent="LobbyPanel"]
+margin_left = 10.0
+margin_top = 30.0
+margin_right = 210.0
+margin_bottom = 54.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "127.0.0.1"
+
+[node name="HostButton" type="Button" parent="LobbyPanel"]
+margin_left = 10.0
+margin_top = 60.0
+margin_right = 90.0
+margin_bottom = 80.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "Host"
+
+[node name="JoinButton" type="Button" parent="LobbyPanel"]
+margin_left = 130.0
+margin_top = 60.0
+margin_right = 210.0
+margin_bottom = 80.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "Join"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="StatusOk" type="Label" parent="LobbyPanel"]
+margin_left = 10.0
+margin_top = 90.0
+margin_right = 210.0
+margin_bottom = 104.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+custom_colors/font_color = Color( 0, 1, 0.015625, 1 )
+align = 1
+
+[node name="StatusFail" type="Label" parent="LobbyPanel"]
+margin_left = 10.0
+margin_top = 90.0
+margin_right = 210.0
+margin_bottom = 104.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+custom_colors/font_color = Color( 1, 0, 0, 1 )
+align = 1
+[connection signal="pressed" from="LobbyPanel/HostButton" to="LobbyPanel" method="OnHostPressed"]
+[connection signal="pressed" from="LobbyPanel/JoinButton" to="LobbyPanel" method="OnJoinPressed"]

+ 97 - 0
mono/multiplayer_pong/logic/Ball.cs

@@ -0,0 +1,97 @@
+using Godot;
+using System;
+
+public class Ball : Area2D
+{
+    private const int DefaultSpeed = 100;
+
+    private Vector2 _direction = Vector2.Left;
+    private bool _stopped = false;
+    private float _speed = DefaultSpeed;
+    private Vector2 _screenSize;
+
+    // Called when the node enters the scene tree for the first time.
+    public override void _Ready()
+    {
+        _screenSize = GetViewportRect().Size;
+    }
+
+    // Called every frame. 'delta' is the elapsed time since the previous frame.
+    public override void _Process(float delta)
+    {
+        _speed += delta;
+        // Ball will move normally for both players,
+        // even if it's slightly out of sync between them,
+        // so each player sees the motion as smooth and not jerky.
+        if (!_stopped)
+        {
+            Translate(_speed * delta * _direction);
+        }
+
+        // Check screen bounds to make ball bounce.
+        var ballPosition = Position;
+        if ((ballPosition.y < 0 && _direction.y < 0) || (ballPosition.y > _screenSize.y && _direction.y > 0))
+        {
+            _direction.y = -_direction.y;
+        }
+
+        if (IsNetworkMaster())
+        {
+            // Only the master will decide when the ball is out in
+            // the left side (it's own side). This makes the game
+            // playable even if latency is high and ball is going
+            // fast. Otherwise ball might be out in the other
+            // player's screen but not this one.
+            if (ballPosition.x < 0)
+            {
+                GetParent().Rpc("UpdateScore", false);
+                Rpc("ResetBall", false);
+            }
+            else
+            {
+                // Only the puppet will decide when the ball is out in
+                // the right side, which is it's own side. This makes
+                // the game playable even if latency is high and ball
+                // is going fast. Otherwise ball might be out in the
+                // other player's screen but not this one.
+                if (ballPosition.x > _screenSize.x)
+                {
+                    GetParent().Rpc("UpdateScore", true);
+                    Rpc("ResetBall", true);
+                }
+            }
+        }
+    }
+
+    [Sync]
+    private void Bounce(bool left, float random)
+    {
+        // Using sync because both players can make it bounce.
+        if (left)
+        {
+            _direction.x = Mathf.Abs(_direction.x);
+        }
+        else
+        {
+            _direction.x = -Mathf.Abs(_direction.x);
+        }
+
+        _speed *= 1.1f;
+        _direction.y = random * 2.0f - 1;
+        _direction = _direction.Normalized();
+    }
+
+    [Sync]
+    private void Stop()
+    {
+        _stopped = true;
+    }
+
+    [Sync]
+    private void ResetBall(bool forLeft)
+    {
+        Position = _screenSize / 2;
+        _direction = forLeft ? Vector2.Left : Vector2.Right;
+        _speed = DefaultSpeed;
+    }
+}

+ 143 - 0
mono/multiplayer_pong/logic/Lobby.cs

@@ -0,0 +1,143 @@
+using Godot;
+using System;
+using Godot.Collections;
+
+public class Lobby : Control
+{
+    private const int DefaultPort = 8910; // An arbitrary number.
+    private const int MaxNumberOfPeers = 1; // How many people we want to have in a game
+
+    private LineEdit _address;
+    private Button _hostButton;
+    private Button _joinButton;
+    private Label _statusOk;
+    private Label _statusFail;
+    private NetworkedMultiplayerENet _peer;
+
+    public override void _Ready()
+    {
+        // Get nodes - the generic is a class, argument is node path.
+        _address = GetNode<LineEdit>("Address");
+        _hostButton = GetNode<Button>("HostButton");
+        _joinButton = GetNode<Button>("JoinButton");
+        _statusOk = GetNode<Label>("StatusOk");
+        _statusFail = GetNode<Label>("StatusFail");
+
+        // Connect all callbacks related to networking.
+        // Note: Use snake_case when talking to engine API.
+        GetTree().Connect("network_peer_connected", this, nameof(PlayerConnected));
+        GetTree().Connect("network_peer_disconnected", this, nameof(PlayerDisconnected));
+        GetTree().Connect("connected_to_server", this, nameof(ConnectedOk));
+        GetTree().Connect("connection_failed", this, nameof(ConnectedFail));
+        GetTree().Connect("server_disconnected", this, nameof(ServerDisconnected));
+    }
+
+    // Network callbacks from SceneTree
+
+    // Callback from SceneTree.
+    private void PlayerConnected(int id)
+    {
+        // Someone connected, start the game!
+        var pong = ResourceLoader.Load<PackedScene>("res://pong.tscn").Instance();
+
+        // Connect deferred so we can safely erase it from the callback.
+        pong.Connect("GameFinished", this, nameof(EndGame), new Godot.Collections.Array(), (int) ConnectFlags.Deferred);
+
+        GetTree().Root.AddChild(pong);
+        Hide();
+    }
+
+    private void PlayerDisconnected(int id)
+    {
+        EndGame(GetTree().IsNetworkServer() ? "Client disconnected" : "Server disconnected");
+    }
+
+    // Callback from SceneTree, only for clients (not server).
+    private void ConnectedOk()
+    {
+        // This function is not needed for this project.
+    }
+
+    // Callback from SceneTree, only for clients (not server).
+    private void ConnectedFail()
+    {
+        SetStatus("Couldn't connect", false);
+
+        GetTree().NetworkPeer = null; // Remove peer.
+        _hostButton.Disabled = false;
+        _joinButton.Disabled = false;
+    }
+
+    private void ServerDisconnected()
+    {
+        EndGame("Server disconnected");
+    }
+
+    // Game creation functions
+
+    private void EndGame(string withError = "")
+    {
+        if (HasNode("/root/Pong"))
+        {
+            // Erase immediately, otherwise network might show
+            // errors (this is why we connected deferred above).
+            GetNode("/root/Pong").Free();
+            Show();
+        }
+
+        GetTree().NetworkPeer = null; // Remove peer.
+        _hostButton.Disabled = false;
+        _joinButton.Disabled = false;
+
+        SetStatus(withError, false);
+    }
+
+    private void SetStatus(string text, bool isOk)
+    {
+        // Simple way to show status.
+        if (isOk)
+        {
+            _statusOk.Text = text;
+            _statusFail.Text = "";
+        }
+        else
+        {
+            _statusOk.Text = "";
+            _statusFail.Text = text;
+        }
+    }
+
+    private void OnHostPressed()
+    {
+        _peer = new NetworkedMultiplayerENet();
+        _peer.CompressionMode = NetworkedMultiplayerENet.CompressionModeEnum.RangeCoder;
+        Error err = _peer.CreateServer(DefaultPort, MaxNumberOfPeers);
+        if (err != Error.Ok)
+        {
+            // Is another server running?
+            SetStatus("Can't host, address in use.", false);
+            return;
+        }
+
+        GetTree().NetworkPeer = _peer;
+        _hostButton.Disabled = true;
+        _joinButton.Disabled = true;
+        SetStatus("Waiting for player...", true);
+    }
+
+    private void OnJoinPressed()
+    {
+        string ip = _address.Text;
+        if (!ip.IsValidIPAddress())
+        {
+            SetStatus("IP address is invalid", false);
+            return;
+        }
+
+        _peer = new NetworkedMultiplayerENet();
+        _peer.CompressionMode = NetworkedMultiplayerENet.CompressionModeEnum.RangeCoder;
+        _peer.CreateClient(ip, DefaultPort);
+        GetTree().NetworkPeer = _peer;
+        SetStatus("Connecting...", true);
+    }
+}

+ 70 - 0
mono/multiplayer_pong/logic/Paddle.cs

@@ -0,0 +1,70 @@
+using Godot;
+using System;
+
+public class Paddle : Area2D
+{
+    private const int MotionSpeed = 150;
+
+    [Export] private bool _left = false;
+
+    private float _motion = 0.0f;
+    private bool _youHidden = false;
+    private float _screenSizeY = 0.0f;
+
+    public override void _Ready()
+    {
+        _screenSizeY = GetViewportRect().Size.y;
+    }
+
+    public override void _Process(float delta)
+    {
+        // Is the master of the paddle.
+        if (IsNetworkMaster())
+        {
+            _motion = Input.GetActionStrength("move_down") - Input.GetActionStrength("move_up");
+
+            if (!_youHidden && _motion != 0)
+            {
+                HideYouLabel();
+            }
+
+            _motion *= MotionSpeed;
+
+            // Using unreliable to make sure position is updated as fast as possible,
+            // even if one of the calls is dropped
+            RpcUnreliable(nameof(SetPosAndMotion), Position, _motion);
+        }
+        else
+        {
+            if (!_youHidden)
+            {
+                HideYouLabel();
+            }
+        }
+        Translate(new Vector2(0, _motion * delta));
+
+        // Set screen limits. Can't modify structs directly, so we create a new one.
+        Position = new Vector2(Position.x, Mathf.Clamp(Position.y, 16, _screenSizeY - 16));
+    }
+
+    [Puppet]
+    private void SetPosAndMotion(Vector2 pos, float motion)
+    {
+        Position = pos;
+        _motion = motion;
+    }
+
+    private void HideYouLabel()
+    {
+        _youHidden = true;
+        GetNode<Label>("You").Hide();
+    }
+
+    private void OnPaddleAreaEnter(Area2D area)
+    {
+        if (IsNetworkMaster())
+        {
+            area.Rpc("Bounce", _left, GD.Randf());
+        }
+    }
+}

+ 81 - 0
mono/multiplayer_pong/logic/Pong.cs

@@ -0,0 +1,81 @@
+using Godot;
+using System;
+
+public class Pong : Node2D
+{
+    [Signal]
+    private delegate void GameFinished(string withError);
+
+    private const int ScoreToWin = 10;
+
+    private int _scoreLeft = 0;
+    private int _scoreRight = 0;
+    private Paddle _playerTwo;
+    private Label _scoreLeftNode;
+    private Label _scoreRightNode;
+    private Label _winnerLeft;
+    private Label _winnerRight;
+
+    public override void _Ready()
+    {
+        // Get nodes. The generic is the class, argument is path to the node.
+        _playerTwo = GetNode<Paddle>("Player2");
+        _scoreLeftNode = GetNode<Label>("ScoreLeft");
+        _scoreRightNode = GetNode<Label>("ScoreRight");
+        _winnerLeft = GetNode<Label>("WinnerLeft");
+        _winnerRight = GetNode<Label>("WinnerRight");
+
+        // By default, all nodes in server inherit from master,
+        // while all nodes in clients inherit from puppet.
+        // SetNetworkMaster is tree-recursive by default.
+        if (GetTree().IsNetworkServer())
+        {
+            _playerTwo.SetNetworkMaster(GetTree().GetNetworkConnectedPeers()[0]);
+        }
+        else
+        {
+            _playerTwo.SetNetworkMaster(GetTree().GetNetworkUniqueId());
+        }
+
+        GD.Print("Unique id: ", GetTree().GetNetworkUniqueId());
+    }
+
+    [Sync]
+    private void UpdateScore(bool addToLeft)
+    {
+        if (addToLeft)
+        {
+            _scoreLeft += 1;
+            _scoreLeftNode.Text = _scoreLeft.ToString();
+        }
+        else
+        {
+            _scoreRight += 1;
+            _scoreRightNode.Text = _scoreRight.ToString();
+        }
+
+        var gameEnded = false;
+
+        if (_scoreLeft == ScoreToWin)
+        {
+            _winnerLeft.Show();
+            gameEnded = true;
+        }
+        else if (_scoreRight == ScoreToWin)
+        {
+            _winnerRight.Show();
+            gameEnded = true;
+        }
+
+        if (gameEnded)
+        {
+            GetNode<Button>("ExitGame").Show();
+            GetNode<Ball>("Ball").Rpc("Stop");
+        }
+    }
+
+    private void OnExitGamePressed()
+    {
+        EmitSignal(nameof(GameFinished), "");
+    }
+}

二進制
mono/multiplayer_pong/paddle.png


+ 34 - 0
mono/multiplayer_pong/paddle.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://paddle.png"
+dest_files=[ "res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 31 - 0
mono/multiplayer_pong/paddle.tscn

@@ -0,0 +1,31 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://logic/Paddle.cs" type="Script" id=1]
+[ext_resource path="res://paddle.png" type="Texture" id=2]
+
+[sub_resource type="CapsuleShape2D" id=1]
+radius = 4.78568
+height = 23.6064
+
+[node name="Paddle" type="Area2D"]
+script = ExtResource( 1 )
+
+[node name="Sprite" type="Sprite" parent="."]
+texture = ExtResource( 2 )
+
+[node name="Shape" type="CollisionShape2D" parent="."]
+shape = SubResource( 1 )
+
+[node name="You" type="Label" parent="."]
+margin_left = -26.0
+margin_top = -33.0
+margin_right = 27.0
+margin_bottom = -19.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "You"
+align = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+[connection signal="area_entered" from="." to="." method="OnPaddleAreaEnter"]

+ 92 - 0
mono/multiplayer_pong/pong.tscn

@@ -0,0 +1,92 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://logic/Pong.cs" type="Script" id=1]
+[ext_resource path="res://separator.png" type="Texture" id=2]
+[ext_resource path="res://paddle.tscn" type="PackedScene" id=3]
+[ext_resource path="res://ball.tscn" type="PackedScene" id=4]
+
+[node name="Pong" type="Node2D"]
+script = ExtResource( 1 )
+
+[node name="ColorRect" type="ColorRect" parent="."]
+margin_right = 640.0
+margin_bottom = 400.0
+color = Color( 0.141176, 0.152941, 0.164706, 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Separator" type="Sprite" parent="."]
+position = Vector2( 320, 200 )
+texture = ExtResource( 2 )
+
+[node name="Player1" parent="." instance=ExtResource( 3 )]
+modulate = Color( 0, 1, 1, 1 )
+position = Vector2( 32.49, 188.622 )
+_left = true
+
+[node name="Player2" parent="." instance=ExtResource( 3 )]
+modulate = Color( 1, 0, 1, 1 )
+position = Vector2( 608.88, 188.622 )
+
+[node name="Ball" parent="." instance=ExtResource( 4 )]
+position = Vector2( 320.206, 184 )
+
+[node name="ScoreLeft" type="Label" parent="."]
+margin_left = 240.0
+margin_top = 10.0
+margin_right = 280.0
+margin_bottom = 30.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "0"
+align = 1
+
+[node name="ScoreRight" type="Label" parent="."]
+margin_left = 360.0
+margin_top = 10.0
+margin_right = 400.0
+margin_bottom = 30.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "0"
+align = 1
+
+[node name="WinnerLeft" type="Label" parent="."]
+visible = false
+margin_left = 190.0
+margin_top = 170.0
+margin_right = 267.0
+margin_bottom = 184.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "The Winner!"
+
+[node name="WinnerRight" type="Label" parent="."]
+visible = false
+margin_left = 380.0
+margin_top = 170.0
+margin_right = 457.0
+margin_bottom = 184.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "The Winner!"
+
+[node name="ExitGame" type="Button" parent="."]
+visible = false
+margin_left = 280.0
+margin_top = 340.0
+margin_right = 360.0
+margin_bottom = 360.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "Exit Game"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+offset = Vector2( 320, 200 )
+current = true
+[connection signal="pressed" from="ExitGame" to="." method="OnExitGamePressed"]
+
+[editable path="Player1"]
+
+[editable path="Player2"]

+ 56 - 0
mono/multiplayer_pong/project.godot

@@ -0,0 +1,56 @@
+; 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=4
+
+_global_script_classes=[  ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="Pong Multiplayer with C#"
+config/description="A multiplayer demo of the classical pong game.
+One of the players should press 'host', while the
+other should select the address and press 'join'."
+run/main_scene="res://lobby.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/size/width=640
+window/size/height=400
+window/stretch/mode="2d"
+window/stretch/aspect="expand"
+
+[input]
+
+move_down={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":90,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
+ ]
+}
+move_up={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
+ ]
+}
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+quality/2d/use_pixel_snap=true

二進制
mono/multiplayer_pong/separator.png


+ 3 - 3
visual_script/pong/left_pallete.png.import → mono/multiplayer_pong/separator.png.import

@@ -2,15 +2,15 @@
 
 importer="texture"
 type="StreamTexture"
-path="res://.import/left_pallete.png-bc33611074a0f886142e37c77bd2545a.stex"
+path="res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex"
 metadata={
 "vram_texture": false
 }
 
 [deps]
 
-source_file="res://left_pallete.png"
-dest_files=[ "res://.import/left_pallete.png-bc33611074a0f886142e37c77bd2545a.stex" ]
+source_file="res://separator.png"
+dest_files=[ "res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex" ]
 
 [params]
 

二進制
mono/pong/left_pallete.png


二進制
mono/pong/paddle.png


+ 34 - 0
mono/pong/paddle.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://paddle.png"
+dest_files=[ "res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 5 - 4
mono/pong/pong.tscn

@@ -1,8 +1,7 @@
-[gd_scene load_steps=13 format=2]
+[gd_scene load_steps=12 format=2]
 
 [ext_resource path="res://Logic/Paddle.cs" type="Script" id=1]
-[ext_resource path="res://left_pallete.png" type="Texture" id=2]
-[ext_resource path="res://right_pallete.png" type="Texture" id=3]
+[ext_resource path="res://paddle.png" type="Texture" id=2]
 [ext_resource path="res://Logic/Ball.cs" type="Script" id=4]
 [ext_resource path="res://ball.png" type="Texture" id=5]
 [ext_resource path="res://separator.png" type="Texture" id=6]
@@ -29,6 +28,7 @@ margin_bottom = 400.0
 color = Color( 0.141176, 0.152941, 0.164706, 1 )
 
 [node name="Left" type="Area2D" parent="."]
+modulate = Color( 0, 1, 1, 1 )
 position = Vector2( 67.6285, 192.594 )
 script = ExtResource( 1 )
 
@@ -39,11 +39,12 @@ texture = ExtResource( 2 )
 shape = SubResource( 1 )
 
 [node name="Right" type="Area2D" parent="."]
+modulate = Color( 1, 0, 1, 1 )
 position = Vector2( 563.815, 188.919 )
 script = ExtResource( 1 )
 
 [node name="Sprite" type="Sprite" parent="Right"]
-texture = ExtResource( 3 )
+texture = ExtResource( 2 )
 
 [node name="Collision" type="CollisionShape2D" parent="Right"]
 shape = SubResource( 1 )

二進制
mono/pong/right_pallete.png


+ 10 - 6
networking/multiplayer_pong/logic/ball.gd

@@ -22,16 +22,20 @@ func _process(delta):
 		direction.y = -direction.y
 
 	if is_network_master():
-		# Only master will decide when the ball is out in the left side (it's own side).
-		# This makes the game playable even if latency is high and ball is going fast.
-		# Otherwise ball might be out in the other player's screen but not this one.
+		# Only the master will decide when the ball is out in
+		# the left side (it's own side). This makes the game
+		# playable even if latency is high and ball is going
+		# fast. Otherwise ball might be out in the other
+		# player's screen but not this one.
 		if ball_pos.x < 0:
 			get_parent().rpc("update_score", false)
 			rpc("_reset_ball", false)
 	else:
-		# Only the puppet will decide when the ball is out in the right side (it's own side).
-		# This makes the game playable even if latency is high and ball is going fast.
-		# Otherwise ball might be out in the other player's screen but not this one.
+		# Only the puppet will decide when the ball is out in
+		# the right side, which is it's own side. This makes
+		# the game playable even if latency is high and ball
+		# is going fast. Otherwise ball might be out in the
+		# other player's screen but not this one.
 		if ball_pos.x > _screen_size.x:
 			get_parent().rpc("update_score", true)
 			rpc("_reset_ball", true)

+ 3 - 2
networking/multiplayer_pong/logic/lobby.gd

@@ -40,7 +40,7 @@ func _player_disconnected(_id):
 
 # Callback from SceneTree, only for clients (not server).
 func _connected_ok():
-	pass # We don't need this function.
+	pass # This function is not needed for this project.
 
 
 # Callback from SceneTree, only for clients (not server).
@@ -59,7 +59,8 @@ func _server_disconnected():
 
 func _end_game(with_error = ""):
 	if has_node("/root/Pong"):
-		# Erase immediately, otherwise network might show errors (this is why we connected deferred above).
+		# Erase immediately, otherwise network might show
+		# errors (this is why we connected deferred above).
 		get_node("/root/Pong").free()
 		show()
 

+ 12 - 12
networking/multiplayer_pong/logic/paddle.gd

@@ -4,42 +4,42 @@ const MOTION_SPEED = 150
 
 export var left = false
 
-var motion = 0
-var you_hidden = false
+var _motion = 0
+var _you_hidden = false
 
 onready var _screen_size_y = get_viewport_rect().size.y
 
 func _process(delta):
 	# Is the master of the paddle.
 	if is_network_master():
-		motion = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
+		_motion = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
 
-		if not you_hidden and motion != 0:
+		if not _you_hidden and _motion != 0:
 			_hide_you_label()
 
-		motion *= MOTION_SPEED
+		_motion *= MOTION_SPEED
 
 		# Using unreliable to make sure position is updated as fast
 		# as possible, even if one of the calls is dropped.
-		rpc_unreliable("set_pos_and_motion", position, motion)
+		rpc_unreliable("set_pos_and_motion", position, _motion)
 	else:
-		if not you_hidden:
+		if not _you_hidden:
 			_hide_you_label()
 
-	translate(Vector2(0, motion * delta))
+	translate(Vector2(0, _motion * delta))
 
 	# Set screen limits.
 	position.y = clamp(position.y, 16, _screen_size_y - 16)
 
 
 # Synchronize position and speed to the other peers.
-puppet func set_pos_and_motion(p_pos, p_motion):
-	position = p_pos
-	motion = p_motion
+puppet func set_pos_and_motion(pos, motion):
+	position = pos
+	_motion = motion
 
 
 func _hide_you_label():
-	you_hidden = true
+	_you_hidden = true
 	get_node("You").hide()
 
 

+ 4 - 3
networking/multiplayer_pong/logic/pong.gd

@@ -1,12 +1,12 @@
 extends Node2D
 
+signal game_finished()
+
 const SCORE_TO_WIN = 10
 
 var score_left = 0
 var score_right = 0
 
-signal game_finished()
-
 onready var player2 = $Player2
 onready var score_left_node = $ScoreLeft
 onready var score_right_node = $ScoreRight
@@ -23,7 +23,8 @@ func _ready():
 	else:
 		# For the client, give control of player 2 to itself.
 		player2.set_network_master(get_tree().get_network_unique_id())
-	print("unique id: ", get_tree().get_network_unique_id())
+
+	print("Unique id: ", get_tree().get_network_unique_id())
 
 
 sync func update_score(add_to_left):

+ 2 - 7
networking/multiplayer_pong/pong.tscn

@@ -21,19 +21,14 @@ position = Vector2( 320, 200 )
 texture = ExtResource( 2 )
 
 [node name="Player1" parent="." instance=ExtResource( 3 )]
+modulate = Color( 0, 1, 1, 1 )
 position = Vector2( 32.49, 188.622 )
 left = true
 
-[node name="Sprite" parent="Player1" index="0"]
-self_modulate = Color( 0, 1, 1, 1 )
-
 [node name="Player2" parent="." instance=ExtResource( 3 )]
-self_modulate = Color( 1, 0, 1, 1 )
+modulate = Color( 1, 0, 1, 1 )
 position = Vector2( 608.88, 188.622 )
 
-[node name="Sprite" parent="Player2" index="0"]
-self_modulate = Color( 1, 0, 1, 1 )
-
 [node name="Ball" parent="." instance=ExtResource( 4 )]
 position = Vector2( 320.387, 189.525 )
 

+ 0 - 1
networking/multiplayer_pong/project.godot

@@ -64,4 +64,3 @@ move_up={
 
 quality/driver/driver_name="GLES2"
 quality/2d/use_pixel_snap=true
-viewport/default_clear_color=Color( 0, 0, 0, 1 )

二進制
visual_script/pong/left_pallete.png


二進制
visual_script/pong/paddle.png


+ 34 - 0
visual_script/pong/paddle.png.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://paddle.png"
+dest_files=[ "res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

+ 5 - 4
visual_script/pong/pong.tscn

@@ -1,8 +1,7 @@
-[gd_scene load_steps=13 format=2]
+[gd_scene load_steps=12 format=2]
 
 [ext_resource path="res://scripts/paddle.vs" type="Script" id=1]
-[ext_resource path="res://left_pallete.png" type="Texture" id=2]
-[ext_resource path="res://right_pallete.png" type="Texture" id=3]
+[ext_resource path="res://paddle.png" type="Texture" id=2]
 [ext_resource path="res://scripts/ball.vs" type="Script" id=4]
 [ext_resource path="res://ball.png" type="Texture" id=5]
 [ext_resource path="res://separator.png" type="Texture" id=6]
@@ -32,6 +31,7 @@ __meta__ = {
 }
 
 [node name="Left" type="Area2D" parent="."]
+modulate = Color( 0, 1, 1, 1 )
 position = Vector2( 67.6285, 192.594 )
 script = ExtResource( 1 )
 
@@ -42,12 +42,13 @@ texture = ExtResource( 2 )
 shape = SubResource( 1 )
 
 [node name="Right" type="Area2D" parent="."]
+modulate = Color( 1, 0, 1, 1 )
 position = Vector2( 563.815, 188.919 )
 script = ExtResource( 1 )
 ball_dir = -1.0
 
 [node name="Sprite" type="Sprite" parent="Right"]
-texture = ExtResource( 3 )
+texture = ExtResource( 2 )
 
 [node name="Collision" type="CollisionShape2D" parent="Right"]
 shape = SubResource( 1 )

二進制
visual_script/pong/right_pallete.png


+ 0 - 34
visual_script/pong/right_pallete.png.import

@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="StreamTexture"
-path="res://.import/right_pallete.png-fc6e4a6a7c8197834656482b94708e47.stex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://right_pallete.png"
-dest_files=[ "res://.import/right_pallete.png-fc6e4a6a7c8197834656482b94708e47.stex" ]
-
-[params]
-
-compress/mode=0
-compress/lossy_quality=0.7
-compress/hdr_mode=0
-compress/bptc_ldr=0
-compress/normal_map=0
-flags/repeat=0
-flags/filter=false
-flags/mipmaps=false
-flags/anisotropic=false
-flags/srgb=2
-process/fix_alpha_border=true
-process/premult_alpha=false
-process/HDR_as_SRGB=false
-process/invert_color=false
-stream=false
-size_limit=0
-detect_3d=true
-svg/scale=1.0