Add basic multiplayer functionality

- Can now create servers and connect with clients
- Add OtherPlayer scene
- Add static Extension class for extension methods
main
copygirl 5 years ago
parent e1ecd7f01e
commit 167c5b19eb
  1. 32
      scene/EscapeMenu.tscn
  2. 15
      scene/OtherPlayer.tscn
  3. 5
      scene/Player.tscn
  4. 186
      src/EscapeMenu.cs
  5. 7
      src/Extensions.cs

@ -28,22 +28,22 @@ anchor_right = 1.0
anchor_bottom = 1.0
[node name="PanelContainer" type="PanelContainer" parent="CenterContainer"]
margin_left = 527.0
margin_left = 522.0
margin_top = 274.0
margin_right = 752.0
margin_right = 757.0
margin_bottom = 446.0
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/PanelContainer"]
margin_left = 7.0
margin_top = 7.0
margin_right = 218.0
margin_right = 228.0
margin_bottom = 165.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Label" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_right = 211.0
margin_right = 221.0
margin_bottom = 9.0
text = "Pause Menu"
align = 1
@ -53,12 +53,12 @@ __meta__ = {
[node name="HSeparator" type="HSeparator" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 13.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 17.0
[node name="ContainerStatus" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 21.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 34.0
[node name="Label" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/ContainerStatus"]
@ -70,8 +70,9 @@ text = "Status:"
align = 2
[node name="Status" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/ContainerStatus"]
modulate = Color( 1, 0, 0, 1 )
margin_left = 40.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 13.0
rect_min_size = Vector2( 0, 13 )
size_flags_horizontal = 3
@ -85,7 +86,7 @@ __meta__ = {
[node name="ContainerServer" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 38.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 57.0
[node name="Label" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/ContainerServer"]
@ -110,7 +111,7 @@ __meta__ = {
[node name="ServerStartStop" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/ContainerServer"]
margin_left = 94.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 19.0
size_flags_horizontal = 3
text = "Start Server"
@ -120,7 +121,7 @@ __meta__ = {
[node name="ContainerClient" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 61.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 80.0
[node name="Label" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/ContainerClient"]
@ -141,14 +142,15 @@ caret_blink = true
[node name="ClientDisConnect" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/ContainerClient"]
margin_left = 164.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 19.0
rect_min_size = Vector2( 57, 0 )
size_flags_horizontal = 3
text = "Connect"
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 84.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 108.0
[node name="HideAddress" type="CheckBox" parent="CenterContainer/PanelContainer/VBoxContainer/HBoxContainer"]
@ -166,12 +168,12 @@ text = "(for streamers etc.)"
[node name="HSeparator2" type="HSeparator" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 112.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 116.0
[node name="Quit" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 120.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 137.0
rect_min_size = Vector2( 0, 17 )
@ -196,7 +198,7 @@ __meta__ = {
[node name="Return" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer"]
margin_top = 141.0
margin_right = 211.0
margin_right = 221.0
margin_bottom = 158.0
rect_min_size = Vector2( 0, 17 )
__meta__ = {

@ -0,0 +1,15 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://gfx/player.png" type="Texture" id=2]
[sub_resource type="CircleShape2D" id=1]
radius = 8.0
[node name="OtherPlayer" type="KinematicBody2D"]
collision_layer = 0
[node name="CircleShape" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
[node name="Sprite" type="Sprite" parent="."]
texture = ExtResource( 2 )

@ -8,11 +8,8 @@
radius = 8.0
[node name="Player" type="KinematicBody2D"]
position = Vector2( 640, 360 )
collision_layer = 0
script = ExtResource( 3 )
__meta__ = {
"_edit_group_": true
}
[node name="Camera2D" type="Camera2D" parent="."]
current = true

@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Godot;
// TODO: Split network and escape menu logic.
public class EscapeMenu : Container
{
[Export] public int DefaultPort { get; set; } = 25565;
[Export] public ushort DefaultPort { get; set; } = 42005;
[Export] public string DefaultAddress { get; set; } = "localhost";
[Export] public NodePath StatusPath { get; set; }
@ -19,6 +23,15 @@ public class EscapeMenu : Container
public LineEdit ClientAddress { get; private set; }
public Button Return { get; private set; }
public Node PlayerContainer { get; private set; }
public Player OwnPlayer { get; private set; }
public PackedScene OtherPlayer { get; private set; }
public override void _Ready()
{
OtherPlayer = GD.Load<PackedScene>("res://scene/OtherPlayer.tscn");
}
public override void _EnterTree()
{
Status = GetNode<Label>(StatusPath);
@ -27,9 +40,23 @@ public class EscapeMenu : Container
ClientDisConnect = GetNode<Button>(ClientDisConnectPath);
ClientAddress = GetNode<LineEdit>(ClientAddressPath);
Return = GetNode<Button>(ReturnPath);
PlayerContainer = GetNode("/root/Game");
ServerPort.PlaceholderText = DefaultPort.ToString();
ClientAddress.PlaceholderText = $"{DefaultAddress}:{DefaultPort}";
GetTree().Connect("connected_to_server", this, "OnClientConnected");
GetTree().Connect("connection_failed", this, "DisconnectFromServer");
GetTree().Connect("server_disconnected", this, "DisconnectFromServer");
GetTree().Connect("network_peer_connected", this, "OnPeerConnected");
GetTree().Connect("network_peer_disconnected", this, "OnPeerDisconnected");
}
public override void _Process(float delta)
{
if (OwnPlayer == null) return;
RpcUnreliable("OnPlayerMoved", OwnPlayer.Position);
}
public override void _Input(InputEvent @event)
@ -46,7 +73,8 @@ public class EscapeMenu : Container
public void Open()
{
if (Visible) return;
GetTree().Paused = true;
if (GetTree().NetworkPeer == null)
GetTree().Paused = true;
Return.GrabFocus();
Visible = true;
}
@ -54,21 +82,171 @@ public class EscapeMenu : Container
public void Close()
{
if (!Visible) return;
GetTree().Paused = false;
if (GetTree().NetworkPeer == null)
GetTree().Paused = false;
Visible = false;
}
public void StartServer(ushort port)
{
if (GetTree().NetworkPeer != null) throw new InvalidOperationException();
var peer = new NetworkedMultiplayerENet();
// TODO: Somehow show there was an error.
if (peer.CreateServer(port) != Error.Ok) return;
GetTree().NetworkPeer = peer;
OwnPlayer = FindOwnPlayer();
Status.Text = "Server Running";
Status.Modulate = Colors.Green;
ServerPort.Editable = false;
ServerStartStop.Text = "Stop Server";
ClientAddress.Editable = false;
ClientDisConnect.Disabled = true;
if (Visible) GetTree().Paused = false;
}
public void StopServer()
{
if ((GetTree().NetworkPeer == null) || !GetTree().IsNetworkServer()) throw new InvalidOperationException();
// TODO: Disconnect players gracefully.
((NetworkedMultiplayerENet)GetTree().NetworkPeer).CloseConnection();
GetTree().NetworkPeer = null;
OwnPlayer = null;
foreach (var player in GetOtherPlayers())
player.RemoveFromParent();
Status.Text = "No Connection";
Status.Modulate = Colors.Red;
ServerPort.Editable = true;
ServerStartStop.Text = "Start Server";
ClientAddress.Editable = true;
ClientDisConnect.Disabled = false;
if (Visible) GetTree().Paused = true;
}
public void ConnectToServer(string address, ushort port)
{
if (GetTree().NetworkPeer != null) throw new InvalidOperationException();
var peer = new NetworkedMultiplayerENet();
// TODO: Somehow show there was an error.
if (peer.CreateClient(address, port) != Error.Ok) return;
GetTree().NetworkPeer = peer;
Status.Text = "Connecting ...";
Status.Modulate = Colors.Yellow;
ServerPort.Editable = false;
ServerStartStop.Disabled = true;
ClientAddress.Editable = false;
ClientDisConnect.Text = "Disconnect";
if (Visible) GetTree().Paused = false;
}
public void DisconnectFromServer()
{
if ((GetTree().NetworkPeer == null) || GetTree().IsNetworkServer()) throw new InvalidOperationException();
// TODO: Disconnect from server gracefully.
((NetworkedMultiplayerENet)GetTree().NetworkPeer).CloseConnection();
GetTree().NetworkPeer = null;
OwnPlayer = null;
foreach (var player in GetOtherPlayers())
player.RemoveFromParent();
Status.Text = "No Connection";
Status.Modulate = Colors.Red;
ServerPort.Editable = true;
ServerStartStop.Disabled = false;
ClientAddress.Editable = true;
ClientDisConnect.Disabled = false;
ClientDisConnect.Text = "Connect";
if (Visible) GetTree().Paused = true;
}
private Player FindOwnPlayer()
=> GetTree().Root.GetChild(0).GetChildren().OfType<Player>().First();
private Node2D GetPlayerWithId(int id)
=> PlayerContainer.GetNodeOrNull<Node2D>(id.ToString());
private Node2D GetOrCreatePlayerWithId(int id)
{
var player = GetPlayerWithId(id);
if (player == null) {
player = (Node2D)OtherPlayer.Instance();
// TODO: Use "set_network_master".
player.Name = id.ToString();
PlayerContainer.AddChild(player);
}
return player;
}
// TODO: This assumes that any node whose name starts with a digit is a player.
private IEnumerable<Node2D> GetOtherPlayers()
=> PlayerContainer.GetChildren().OfType<Node2D>()
.Where(node => char.IsDigit(node.Name[0]));
#pragma warning disable IDE0051
private void OnClientConnected()
{
OwnPlayer = FindOwnPlayer();
Status.Text = "Connected to Server";
Status.Modulate = Colors.Green;
}
private void OnPeerConnected(int id)
{
}
private void OnPeerDisconnected(int id)
=> GetPlayerWithId(id)?.RemoveFromParent();
[Remote]
private void OnPlayerMoved(Vector2 position)
{
var id = GetTree().GetRpcSenderId();
var player = GetOrCreatePlayerWithId(id);
player.Position = position;
}
#pragma warning disable IDE1006
private void _on_ServerStartStop_pressed()
{
if (GetTree().NetworkPeer == null)
StartServer((ServerPort.Text.Length > 0) ? ushort.Parse(ServerPort.Text) : DefaultPort);
else StopServer();
}
private void _on_ClientDisConnect_pressed()
{
if (GetTree().NetworkPeer == null) {
var address = DefaultAddress;
var port = DefaultPort;
if (ClientAddress.Text.Length > 0) {
// TODO: Verify input some more, support IPv6?
var split = address.Split(':');
address = (split.Length > 1) ? split[0] : address;
port = (split.Length > 1) ? ushort.Parse(split[1]) : port;
}
ConnectToServer(address, port);
} else DisconnectFromServer();
}
private void _on_HideAddress_toggled(bool pressed)

@ -0,0 +1,7 @@
using Godot;
public static class Extensions
{
public static void RemoveFromParent(this Node node)
=> node.GetParent().RemoveChild(node);
}
Loading…
Cancel
Save