Simplify networking some more

- Simply Player with Rset extension methods
- Game, Network and LocalPlayer now
  have a static "Instance" property
- Port input only supports numbers
main
copygirl 5 years ago
parent d93baa7fd0
commit e1d83a4af1
  1. 1
      scene/EscapeMenu.tscn
  2. 1
      scene/GameScene.tscn
  3. 8
      src/EscapeMenuAppearance.cs
  4. 45
      src/EscapeMenuMultiplayer.cs
  5. 21
      src/Extensions.cs
  6. 14
      src/Game.cs
  7. 7
      src/LocalPlayer.cs
  8. 36
      src/Network.cs
  9. 51
      src/Player.cs
  10. 2
      src/Viewport.cs

@ -321,6 +321,7 @@ __meta__ = {
[connection signal="visibility_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" method="_on_Appearance_visibility_changed"]
[connection signal="text_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance/VBoxContainer/ContainerName/DisplayName" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" method="_on_DisplayName_text_changed"]
[connection signal="value_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance/VBoxContainer/ContainerColor/Hue" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Appearance" method="_on_Hue_value_changed"]
[connection signal="text_changed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer/ServerPort" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ServerPort_text_changed"]
[connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerServer/ServerStartStop" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ServerStartStop_pressed"]
[connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerClient/ClientDisConnect" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_ClientDisConnect_pressed"]
[connection signal="toggled" from="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer/ContainerHideAddress/HideAddress" to="CenterContainer/PanelContainer/VBoxContainer/TabContainer/Multiplayer" method="_on_HideAddress_toggled"]

@ -12,7 +12,6 @@
[node name="Game" type="Node"]
script = ExtResource( 3 )
LocalPlayerPath = NodePath("Players/LocalPlayer")
BlockScene = ExtResource( 6 )
[node name="Viewport" type="Node" parent="."]

@ -7,21 +7,19 @@ public class EscapeMenuAppearance : CenterContainer
[Export] public NodePath ColorPreviewPath { get; set; }
[Export] public NodePath ColorSliderPath { get; set; }
public Game Game { get; private set; }
public LineEdit DisplayName { get; private set; }
public TextureRect ColorPreview { get; private set; }
public Slider ColorSlider { get; private set; }
public override void _Ready()
{
Game = GetNode<Game>("/root/Game");
DisplayName = GetNode<LineEdit>(DisplayNamePath);
ColorPreview = GetNode<TextureRect>(ColorPreviewPath);
ColorSlider = GetNode<Slider>(ColorSliderPath);
ColorSlider.Value = GD.RandRange(0.0, 1.0);
var color = Color.FromHsv((float)ColorSlider.Value, 1.0F, 1.0F);
Game.LocalPlayer.Color = ColorPreview.Modulate = color;
LocalPlayer.Instance.Color = ColorPreview.Modulate = color;
}
@ -48,7 +46,7 @@ public class EscapeMenuAppearance : CenterContainer
private void _on_Appearance_visibility_changed()
{
if (IsVisibleInTree()) return;
Game.LocalPlayer.DisplayName = DisplayName.Text;
Game.LocalPlayer.Color = ColorPreview.Modulate;
LocalPlayer.Instance.DisplayName = DisplayName.Text;
LocalPlayer.Instance.Color = ColorPreview.Modulate;
}
}

@ -1,3 +1,4 @@
using System.Text.RegularExpressions;
using Godot;
public class EscapeMenuMultiplayer : Container
@ -14,8 +15,6 @@ public class EscapeMenuMultiplayer : Container
public Button ClientDisConnect { get; private set; }
public LineEdit ClientAddress { get; private set; }
public Network Network { get; private set; }
public override void _Ready()
{
Status = GetNode<Label>(StatusPath);
@ -24,10 +23,9 @@ public class EscapeMenuMultiplayer : Container
ClientDisConnect = GetNode<Button>(ClientDisConnectPath);
ClientAddress = GetNode<LineEdit>(ClientAddressPath);
Network = GetNode<Network>("/root/Game/Network");
Network.Connect(nameof(Network.StatusChanged), this, nameof(OnNetworkStatusChanged));
ServerPort.PlaceholderText = Network.DefaultPort.ToString();
ClientAddress.PlaceholderText = $"{Network.DefaultAddress}:{Network.DefaultPort}";
Network.Instance.Connect(nameof(Network.StatusChanged), this, nameof(OnNetworkStatusChanged));
ServerPort.PlaceholderText = Network.DEFAULT_PORT.ToString();
ClientAddress.PlaceholderText = $"localhost:{Network.DEFAULT_PORT}";
}
@ -57,10 +55,10 @@ public class EscapeMenuMultiplayer : Container
}
var noConnection = status == NetworkStatus.NoConnection;
ServerPort.Editable = noConnection;
ServerStartStop.Disabled = noConnection;
ClientAddress.Editable = noConnection;
ServerStartStop.Text = (status == NetworkStatus.ServerRunning) ? "Stop Server" : "Start Server";
ServerPort.Editable = noConnection;
ServerStartStop.Text = (status == NetworkStatus.ServerRunning) ? "Stop Server" : "Start Server";
ServerStartStop.Disabled = status > NetworkStatus.ServerRunning;
ClientAddress.Editable = noConnection;
ClientDisConnect.Text = (status < NetworkStatus.Connecting) ? "Connect" : "Disconnect";
ClientDisConnect.Disabled = status == NetworkStatus.ServerRunning;
if (Visible) GetTree().Paused = noConnection;
@ -70,29 +68,42 @@ public class EscapeMenuMultiplayer : Container
#pragma warning disable IDE0051
#pragma warning disable IDE1006
private static readonly Regex INVALID_CHARS = new Regex(@"[^0-9]");
private void _on_ServerPort_text_changed(string text)
{
var validText = INVALID_CHARS.Replace(text, "");
validText = validText.TrimStart('0');
if (validText != text) {
var previousCaretPos = ServerPort.CaretPosition;
ServerPort.Text = validText;
ServerPort.CaretPosition = previousCaretPos - (text.Length - validText.Length);
}
}
private void _on_ServerStartStop_pressed()
{
if (GetTree().NetworkPeer == null) {
var port = Network.DefaultPort;
var port = Network.DEFAULT_PORT;
if (ServerPort.Text.Length > 0)
port = ushort.Parse(ServerPort.Text);
Network.StartServer(port);
} else Network.StopServer();
Network.Instance.StartServer(port);
} else Network.Instance.StopServer();
}
private void _on_ClientDisConnect_pressed()
{
if (GetTree().NetworkPeer == null) {
var address = Network.DefaultAddress;
var port = Network.DefaultPort;
var address = "localhost";
var port = Network.DEFAULT_PORT;
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;
}
Network.ConnectToServer(address, port);
} else Network.DisconnectFromServer();
Network.Instance.ConnectToServer(address, port);
} else Network.Instance.DisconnectFromServer();
}
private void _on_HideAddress_toggled(bool pressed)

@ -14,6 +14,27 @@ public static class Extensions
}
public static void Rset(this Node @this, int except, string property, string method, object value)
{
if (@this.IsInsideTree() && @this.GetTree().NetworkPeer != null) {
if (@this.GetTree().IsNetworkServer())
@this.RsetExcept(except, property, value);
else if (Network.Status == NetworkStatus.ConnectedToServer)
@this.RpcId(1, method, value);
}
}
public static void RsetUnreliable(this Node @this, int except, string property, string method, object value)
{
if (@this.IsInsideTree() && @this.GetTree().NetworkPeer != null) {
if (@this.GetTree().IsNetworkServer())
@this.RsetUnreliableExcept(except, property, value);
else if (Network.Status == NetworkStatus.ConnectedToServer)
@this.RpcUnreliableId(1, method, value);
}
}
public static void RpcExcept(this Node @this, int except, string method, params object[] args)
{
foreach (var peer in @this.GetTree().GetNetworkConnectedPeers())

@ -2,23 +2,19 @@ using Godot;
public class Game : Node
{
public static Game Instance { get; private set; }
[Export] public Vector2 RoomSize { get; set; } = new Vector2(32, 18) * 16;
[Export] public NodePath LocalPlayerPath { get; set; }
[Export] public PackedScene BlockScene { get; set; }
public LocalPlayer LocalPlayer { get; private set; }
public Game() => Instance = this;
// Using _EnterTree to make sure this code runs before any other.
public override void _EnterTree()
{
GD.Randomize();
LocalPlayer = GetNode<LocalPlayer>(LocalPlayerPath);
}
=> GD.Randomize();
public override void _Ready()
{
SpawnBlocks();
}
=> SpawnBlocks();
private void SpawnBlocks()
{

@ -1,8 +1,10 @@
using Godot;
using System;
using Godot;
public class LocalPlayer : Player
{
public static LocalPlayer Instance { get; private set; }
public TimeSpan JumpEarlyTime { get; } = TimeSpan.FromSeconds(0.2F);
public TimeSpan JumpCoyoteTime { get; } = TimeSpan.FromSeconds(0.2F);
@ -19,6 +21,9 @@ public class LocalPlayer : Player
private DateTime? _jumpPressed = null;
private DateTime? _lastOnFloor = null;
public override void _EnterTree() => Instance = this;
public override void _ExitTree() => Instance = null;
public override void _PhysicsProcess(float delta)
{
var moveDir = Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left");

@ -1,6 +1,6 @@
using Godot;
using System;
using System.Collections.Generic;
using Godot;
public enum NetworkStatus
{
@ -13,23 +13,29 @@ public enum NetworkStatus
public class Network : Node
{
private readonly Dictionary<int, Player> _playersById = new Dictionary<int, Player>();
public const ushort DEFAULT_PORT = 42005;
public static Network Instance { get; private set; }
public static NetworkStatus Status { get; private set; } = NetworkStatus.NoConnection;
public static bool IsServer => Instance.GetTree().IsNetworkServer();
public static int LocalNetworkId => Instance.GetTree().GetNetworkUniqueId();
[Export] public ushort DefaultPort { get; set; } = 42005;
[Export] public string DefaultAddress { get; set; } = "localhost";
private readonly Dictionary<int, Player> _playersById = new Dictionary<int, Player>();
[Export] public NodePath PlayerContainerPath { get; set; }
[Export] public PackedScene OtherPlayerScene { get; set; }
public Game Game { get; private set; }
public Node PlayerContainer { get; private set; }
public NetworkStatus Status { get; private set; } = NetworkStatus.NoConnection;
[Signal] public delegate void StatusChanged(NetworkStatus status);
public Network() => Instance = this;
public override void _Ready()
{
Game = GetNode<Game>("/root/Game");
PlayerContainer = GetNode(PlayerContainerPath);
GetTree().Connect("connected_to_server", this, nameof(OnClientConnected));
@ -46,7 +52,7 @@ public class Network : Node
public void ClearPlayers()
{
Game.LocalPlayer.NetworkId = -1;
LocalPlayer.Instance.NetworkId = -1;
foreach (var player in _playersById.Values)
if (!player.IsLocal)
player.RemoveFromParent();
@ -66,8 +72,8 @@ public class Network : Node
Status = NetworkStatus.ServerRunning;
EmitSignal(nameof(StatusChanged), Status);
Game.LocalPlayer.NetworkId = 1;
_playersById.Add(1, Game.LocalPlayer);
LocalPlayer.Instance.NetworkId = 1;
_playersById.Add(1, LocalPlayer.Instance);
return Error.Ok;
}
@ -120,10 +126,10 @@ public class Network : Node
EmitSignal(nameof(StatusChanged), Status);
var id = GetTree().GetNetworkUniqueId();
Game.LocalPlayer.NetworkId = id;
_playersById.Add(id, Game.LocalPlayer);
LocalPlayer.Instance.NetworkId = id;
_playersById.Add(id, LocalPlayer.Instance);
Rpc(nameof(OnClientAuthenticate), Game.LocalPlayer.DisplayName, Game.LocalPlayer.Color);
Rpc(nameof(OnClientAuthenticate), LocalPlayer.Instance.DisplayName, LocalPlayer.Instance.Color);
}
[Master]
@ -155,8 +161,8 @@ public class Network : Node
Status = NetworkStatus.ConnectedToServer;
EmitSignal(nameof(StatusChanged), Status);
Game.LocalPlayer.Position = position;
return Game.LocalPlayer;
LocalPlayer.Instance.Position = position;
return LocalPlayer.Instance;
}
private Player SpawnOtherPlayerInternal(int id, Vector2 position, string displayName, Color color)

@ -1,6 +1,5 @@
using Godot;
// FIXME: Player name should not be stored in "Name".
public class Player : KinematicBody2D, IInitializer
{
[Export] public NodePath DisplayNamePath { get; set; }
@ -15,17 +14,26 @@ public class Player : KinematicBody2D, IInitializer
private int _networkId = -1;
public int NetworkId {
get => _networkId;
set => SetNetworkId(value);
set {
_networkId = value;
Name = (_networkId > 0) ? value.ToString() : "LocalPlayer";
}
}
public Color Color {
get => Sprite.Modulate;
set => SetColor(value);
set {
Sprite.Modulate = value;
Sprite.Rset(NetworkId, "modulate", nameof(OnColorChanged), value);
}
}
public string DisplayName {
get => DisplayNameLabel.Text;
set => SetDisplayName(value);
set {
DisplayNameLabel.Text = value;
DisplayNameLabel.Rset(NetworkId, "text", nameof(OnDisplayNameChanged), value);
}
}
@ -46,47 +54,16 @@ public class Player : KinematicBody2D, IInitializer
}
public override void _Process(float delta)
{
if (GetTree().NetworkPeer != null) {
// TODO: Only send position if it changed.
// Send unreliable messages while moving, and a reliable once the player stopped.
if (GetTree().IsNetworkServer())
this.RsetUnreliableExcept(NetworkId, "position", Position);
else if (Network.Status == NetworkStatus.ConnectedToServer)
RpcUnreliable(nameof(OnPositionChanged), Position);
}
}
=> this.RsetUnreliable(NetworkId, "position", nameof(OnPositionChanged), Position);
[Master]
private void OnPositionChanged(Vector2 value)
{ if (GetTree().GetRpcSenderId() == NetworkId) Position = value; }
private void SetNetworkId(int value)
{
_networkId = value;
Name = (_networkId > 0) ? value.ToString() : "LocalPlayer";
}
private void SetColor(Color value)
{
Sprite.Modulate = value;
if (IsInsideTree() && GetTree().NetworkPeer != null) {
if (GetTree().IsNetworkServer()) Sprite.RsetExcept(NetworkId, "modulate", value);
else Rpc(nameof(OnColorChanged), value);
}
}
[Master]
private void OnColorChanged(Color value)
{ if (GetTree().GetRpcSenderId() == NetworkId) Color = value; }
private void SetDisplayName(string value)
{
DisplayNameLabel.Text = value;
if (IsInsideTree() && GetTree().NetworkPeer != null) {
if (GetTree().IsNetworkServer()) DisplayNameLabel.RsetExcept(NetworkId, "text", value);
else Rpc(nameof(OnDisplayNameChanged), value);
}
}
[Master]
private void OnDisplayNameChanged(string value)
{ if (GetTree().GetRpcSenderId() == NetworkId) DisplayName = value; }

@ -1,5 +1,5 @@
using Godot;
using System;
using Godot;
public class Viewport : Node
{

Loading…
Cancel
Save