add 新增远程连接,实现多人游戏

This commit is contained in:
ZZY 2024-06-27 15:02:57 +08:00
parent d892cd6c0e
commit 9a79bef410
23 changed files with 1167 additions and 211 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
# Godot 4+ specific ignores
.godot/
.*/
bin/

View File

@ -0,0 +1,4 @@
[gd_resource type="Theme" format=3 uid="uid://c53fg2fg071yp"]
[resource]
default_font_size = 30

26
Main.cs
View File

@ -28,15 +28,35 @@ public partial class Main : Node2D
// GetNode<Node2D>("Chessboard").Scale = new Vector2(scaleFactor, scaleFactor);
// }
// Called when the node enters the scene tree for the first time.
// Called when the node enters the scene tree for the first time.
Global global = null;
public override void _Ready() {
// GetTree().Connect("screen_resized", ResizeChessboardToFitScreen);
// Button undo = GetNode<Button>("Undo");
// undo.Connect("pressed", (event) => {
// });
// ChessBoard board = GetNodeAndResource("res://res/ChessBoard.tscn");
// ChessBoard board1 = GetChild<ChessBoard>(0);
// board.Visible = false;
// board.Can
global = GetNode<Global>("/root/Global");
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
}
private void GoToSignlePlayer() {
// global.GotoScene("res://Scenes/SinglePlayer.tscn");
global.GotoScene("res://Scenes/ChessGame.tscn");
}
private void GoToMultiPlayer() {
// global.GotoScene("res://Scenes/MultiPlayer.tscn");
global.GotoScene("res://Scenes/Menu.tscn");
}
private void goToMenu() {
}
}

View File

@ -1,30 +1,50 @@
[gd_scene load_steps=3 format=3 uid="uid://boa4od72355o4"]
[ext_resource type="Script" path="res://Main.cs" id="1_h4cv2"]
[ext_resource type="PackedScene" uid="uid://b1tx7v3230wab" path="res://Scenes/Chessboard.tscn" id="2_bx85x"]
[ext_resource type="Theme" uid="uid://c53fg2fg071yp" path="res://Asserts/defalutTheme.tres" id="2_8jwne"]
[node name="Main" type="Node2D"]
script = ExtResource("1_h4cv2")
[node name="Chessboard" parent="." instance=ExtResource("2_bx85x")]
position = Vector2(360, 660)
scale = Vector2(2.5, 2.5)
[node name="Control" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
theme = ExtResource("2_8jwne")
[node name="Undo" type="Button" parent="."]
offset_left = 200.0
offset_top = 50.0
offset_right = 300.0
offset_bottom = 150.0
theme_override_font_sizes/font_size = 32
text = "撤回"
[node name="SignlePlayer" type="Button" parent="Control"]
layout_mode = 0
offset_left = 83.0
offset_top = 671.0
offset_right = 298.0
offset_bottom = 850.0
text = "单人"
[node name="ReInit" type="Button" parent="."]
offset_left = 450.0
offset_top = 50.0
offset_right = 550.0
offset_bottom = 150.0
theme_override_font_sizes/font_size = 32
text = "重开"
[node name="MultiPlayer" type="Button" parent="Control"]
layout_mode = 0
offset_left = 406.0
offset_top = 670.0
offset_right = 612.0
offset_bottom = 852.0
text = "多人"
[connection signal="pressed" from="Undo" to="Chessboard" method="undo"]
[connection signal="pressed" from="ReInit" to="Chessboard" method="redo"]
[node name="Label" type="Label" parent="Control"]
layout_mode = 0
offset_left = 210.0
offset_top = 312.0
offset_right = 520.0
offset_bottom = 498.0
text = "中国象棋"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Setting" type="Button" parent="Control"]
visible = false
layout_mode = 0
offset_left = 249.0
offset_top = 1034.0
offset_right = 432.0
offset_bottom = 1201.0
text = "设置"
[connection signal="pressed" from="Control/SignlePlayer" to="." method="GoToSignlePlayer"]
[connection signal="pressed" from="Control/MultiPlayer" to="." method="GoToMultiPlayer"]

38
Scenes/ChessGame.tscn Normal file
View File

@ -0,0 +1,38 @@
[gd_scene load_steps=3 format=3 uid="uid://g40y10iaf7qb"]
[ext_resource type="Script" path="res://Scripts/Controllers/ChessGame.cs" id="1_3x8ac"]
[ext_resource type="PackedScene" uid="uid://b1tx7v3230wab" path="res://Scenes/Entities/Chessboard.tscn" id="1_yheur"]
[node name="ChessGame" type="Node2D"]
script = ExtResource("1_3x8ac")
[node name="Chessboard" parent="." instance=ExtResource("1_yheur")]
position = Vector2(360, 660)
scale = Vector2(2.5, 2.5)
[node name="Undo" type="Button" parent="."]
offset_left = 200.0
offset_top = 50.0
offset_right = 300.0
offset_bottom = 150.0
theme_override_font_sizes/font_size = 32
text = "撤回"
[node name="ReInit" type="Button" parent="."]
offset_left = 450.0
offset_top = 50.0
offset_right = 550.0
offset_bottom = 150.0
theme_override_font_sizes/font_size = 32
text = "重开"
[node name="Home" type="Button" parent="."]
offset_left = 310.0
offset_top = 1108.0
offset_right = 412.0
offset_bottom = 1226.0
text = "返回主页"
[connection signal="pressed" from="Undo" to="." method="Undo"]
[connection signal="pressed" from="ReInit" to="." method="ReInit"]
[connection signal="pressed" from="Home" to="." method="GoHome"]

View File

@ -1,8 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://gkbtavjf2273"]
[ext_resource type="Script" path="res://Scripts/Entities/ChessPiece.cs" id="2_cj0n3"]
[node name="Chesspiece" type="Node2D"]
[node name="Sprite2D" type="Sprite2D" parent="."]
script = ExtResource("2_cj0n3")

View File

@ -0,0 +1,10 @@
[gd_scene load_steps=3 format=3 uid="uid://gkbtavjf2273"]
[ext_resource type="Texture2D" uid="uid://bthav6cae4fni" path="res://Asserts/ChesspieceBase.tres" id="1_8v1j6"]
[ext_resource type="Script" path="res://Scripts/Entities/ChessPiece.cs" id="2_y54gx"]
[node name="Chesspiece" type="Node2D"]
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_8v1j6")
script = ExtResource("2_y54gx")

125
Scenes/Menu.tscn Normal file
View File

@ -0,0 +1,125 @@
[gd_scene load_steps=3 format=3 uid="uid://cl8nkm6j8s8jh"]
[ext_resource type="Script" path="res://Scripts/Controllers/Menu.cs" id="1_chiyi"]
[ext_resource type="Theme" uid="uid://c53fg2fg071yp" path="res://Asserts/defalutTheme.tres" id="1_sg40w"]
[node name="Menu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_sg40w")
script = ExtResource("1_chiyi")
metadata/_edit_horizontal_guides_ = [141.0]
[node name="Home" type="Button" parent="."]
layout_mode = 0
offset_left = 49.0
offset_top = 100.0
offset_right = 171.0
offset_bottom = 170.0
text = "返回主页"
[node name="Name" type="Control" parent="."]
anchors_preset = 0
offset_left = 215.0
offset_top = 109.0
offset_right = 697.0
offset_bottom = 168.0
[node name="Label" type="Label" parent="Name"]
layout_mode = 2
offset_left = 2.0
offset_top = 1.0
offset_right = 80.0
offset_bottom = 47.0
text = "名字"
horizontal_alignment = 1
vertical_alignment = 1
[node name="LineEdit" type="LineEdit" parent="Name"]
layout_mode = 2
offset_left = 93.0
offset_right = 320.0
offset_bottom = 50.0
[node name="Button" type="Button" parent="Name"]
layout_mode = 0
offset_left = 340.0
offset_top = 3.0
offset_right = 428.0
offset_bottom = 55.0
text = "确认修改
"
[node name="Server" type="Control" parent="."]
anchors_preset = 0
offset_left = 19.0
offset_top = 184.0
offset_right = 705.0
offset_bottom = 1198.0
[node name="ItemList" type="ItemList" parent="Server"]
layout_mode = 0
offset_top = 120.0
offset_right = 663.0
offset_bottom = 965.0
[node name="ConnectServer" type="Button" parent="Server"]
layout_mode = 0
offset_left = 297.0
offset_top = 17.0
offset_right = 517.0
offset_bottom = 90.0
text = "重连服务器"
[node name="Flush" type="Button" parent="Server"]
layout_mode = 0
offset_left = 535.0
offset_top = 17.0
offset_right = 652.0
offset_bottom = 81.0
text = "Flush"
[node name="ColorRect" type="ColorRect" parent="Server"]
layout_mode = 0
offset_left = 236.0
offset_top = 35.0
offset_right = 276.0
offset_bottom = 75.0
[node name="Label" type="Label" parent="Server"]
layout_mode = 0
offset_left = 19.0
offset_top = 37.0
offset_right = 229.0
offset_bottom = 79.0
text = "服务器连接状态"
[node name="Dialogs" type="Control" parent="."]
anchors_preset = 0
[node name="AcceptDialog" type="AcceptDialog" parent="Dialogs"]
initial_position = 2
size = Vector2i(100, 118)
[node name="PopupMenu" type="PopupMenu" parent="Dialogs"]
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="Dialogs"]
initial_position = 2
size = Vector2i(200, 118)
[node name="URL" type="LineEdit" parent="."]
layout_mode = 0
offset_left = 22.0
offset_top = 1166.0
offset_right = 662.0
offset_bottom = 1247.0
[connection signal="pressed" from="Home" to="." method="goToHome"]
[connection signal="pressed" from="Name/Button" to="." method="EnterName"]
[connection signal="item_activated" from="Server/ItemList" to="." method="OnItemSelected"]
[connection signal="pressed" from="Server/ConnectServer" to="." method="Connect"]
[connection signal="pressed" from="Server/Flush" to="." method="FlushData"]

3
Scenes/web.tscn Normal file
View File

@ -0,0 +1,3 @@
[gd_scene format=3 uid="uid://dp044iptyvvh"]
[node name="Web" type="Node"]

View File

@ -0,0 +1,101 @@
using Godot;
using Godot.Collections;
public partial class ChessGame : Node2D
{
ChessBoard board;
Global global;
private bool isSession = false;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
// Init.Call();
global = GetNode<Global>("/root/Global");
board = GetNode<ChessBoard>("Chessboard");
// GetNode<Button>("Undo").Connect("pressed", Callable.From(board.Undo));
// GetNode<Button>("ReInit").Connect("pressed", Callable.From(board.ReInit));
// GetNode<Button>("Home").Connect("pressed", Callable.From(this.GoHome));
if (!global.RPClient.GetIsConnected()) {
return;
}
isSession = true;
GD.Print("ws is connected");
global.RPClient.OnPRCSessionExit += (cmd, code) => {
GoHome();
};
board.Set("Hello", Callable.From(() => {GD.PrintErr("hello");}));
board.Set("chessMoveFunc", Callable.From((Vector2 newPos, Vector2 fromPos) => {
var res = global.RPClient.SendSessionToAll(global.sessionId, new Dictionary{
{"type", "move"},
{"from", fromPos},
{"to", newPos},
{"fromX", fromPos.X},
{"fromY", fromPos.Y},
{"toX", newPos.X},
{"toY", newPos.Y},
});
GD.Print($"chessMoveFunc Callback {fromPos} -> {newPos} {res}");
return false;
}));
global.RPClient.OnPRCSessionRecv += (msg) => {
SessionMsgHandle(msg["msg"].AsGodotDictionary());
};
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
}
private void SessionMsgHandle(Dictionary msg) {
// GD.PrintErr($"session msg: {msg}");
switch (msg["type"].AsString()) {
case "move":
Vector2 to = new(GD.StrToVar(msg["toX"].ToString()).AsInt32(),
GD.StrToVar(msg["toY"].ToString()).AsInt32());
Vector2 from = new(GD.StrToVar(msg["fromX"].ToString()).AsInt32(),
GD.StrToVar(msg["fromY"].ToString()).AsInt32());
board.MoveAndRecord(to, from);
break;
case "undo":
board.Undo();
break;
case "reInit":
board.ReInit();
break;
}
}
public void GoHome() {
if (global.RPClient.IsOnline()) {
global.RPClient.ExitServer();
}
global.GotoScene("res://Main.tscn");
}
public void Undo() {
GD.PrintErr($"Undo {isSession}");
if (isSession == false) {
GD.PrintErr($"Undo ??");
board.Undo();
return;
}
global.RPClient.SendSessionToAll(global.sessionId, new Dictionary{
{"type", "undo"},
});
}
public void ReInit() {
GD.PrintErr($"ReInit {isSession}");
if (isSession == false) {
board.ReInit();
return;
}
global.RPClient.SendSessionToAll(global.sessionId, new Dictionary{
{"type", "reInit"},
});
}
}

136
Scripts/Controllers/Menu.cs Normal file
View File

@ -0,0 +1,136 @@
using Godot;
using Godot.Collections;
public partial class Menu : Control
{
Global global = null;
ItemList lists = null;
ConfirmationDialog dialog = null;
LineEdit nameLineEdit = null;
LineEdit urlLineEdit = null;
ColorRect colorRect = null;
Timer timer = null;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
global = GetNode<Global>("/root/Global");
nameLineEdit = GetNode<LineEdit>("Name/LineEdit");
urlLineEdit = GetNode<LineEdit>("URL");
urlLineEdit.Text = global.URL;
urlLineEdit.Set("text_submitted", Callable.From(()=>{
global.URL = urlLineEdit.Text;
}));
lists = GetNode<ItemList>("Server/ItemList");
dialog = GetNode<ConfirmationDialog>("Dialogs/ConfirmationDialog");
colorRect = GetNode<ColorRect>("Server/ColorRect");
dialog.DialogAutowrap = true;
dialog.MinSize = new Vector2I(400, 200);
dialog.Canceled += () => {
if (dialog.Title == "Session Created")
global.RPClient.SessionAckCreate(dialog.GetMeta("sessionId").ToString(), false);
};
dialog.Confirmed += () => {
// GD.PrintErr("confirm", dialog.GetLabel().Text);
// goToSignle();
if (dialog.Title == "Session Created")
global.RPClient.SessionAckCreate(dialog.GetMeta("sessionId").ToString(), true);
};
global.RPClient.RegSessionAckCreateCallback((
sessionId,
res,
reqUserId,
reqUserName) => {
if (reqUserId != null) {
dialog.Title = "Session Created";
dialog.SetMeta("reqUserName", reqUserName);
dialog.SetMeta("reqUserId", reqUserId);
dialog.SetMeta("sessionId", sessionId);
// dialog.GetLabel==>Text = $"{sessdata["reqUserName"]}";
dialog.DialogText = $"username: {reqUserName}\n" +
$"reqUserId: {reqUserId}\n";
dialog.Visible = true;
} else {
if (res) {
global.sessionId = sessionId;
global.GotoScene("res://Scenes/ChessGame.tscn");
} else {
dialog.Title = "Failed";
dialog.DialogText = $"session create failed";
dialog.Visible = true;
}
}
return true;
});
timer = new Timer();
AddChild(timer);
timer.Connect("timeout", Callable.From(() => {
if (global.RPClient.GetIsConnected()) {
colorRect.Color = Colors.Green;
} else {
colorRect.Color = Colors.Red;
}
}));
timer.Start(1);
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
}
private void OnItemSelected(int index) {
Dictionary item = lists.GetItemMetadata(index).AsGodotDictionary();
GD.Print($"Item {index} selected, {item}");
string[] strings = { item["id"].ToString() };
global.RPClient.SessionCreate(strings);
}
private void FlushData()
{
global.RPClient.RegionInspect("server", (data) => {
GD.Print(data);
lists.Clear();
foreach (Dictionary<string, string> user in data) {
string userId = user["id"].ToString();
string userName = user["name"].ToString();
if (userId == global.RPClient.GetUserId()) {
lists.SetItemDisabled(
lists.AddItem($"Name: {userName}"),
true);
continue;
}
var idx = lists.AddItem($"Name: {userName}");
lists.SetItemMetadata(idx, user);
lists.SetItemTooltip(idx, $"User ID: {userId}");
}
return true;
});
}
public void Connect()
{
GD.Print("Connect");
global.RPClient.ExitServer();
global.RPClient.ConnectToUrlEx(urlLineEdit.Text);
global.SetProcess(true);
}
public void EnterName() {
var newLine = nameLineEdit.Text;
global.RPClient.UserRename(newLine);
nameLineEdit.Text = newLine;
}
private void goToHome() {
global.RPClient.ExitServer();
global.GotoScene("res://Main.tscn");
}
// private void OnItemListItemClicked(int index, Vector2 atPosition, int mouse_button_index)
// {
// GD.Print($"Item {index} clicked at {atPosition} with mouse button index {mouse_button_index}");
// }
}

View File

@ -6,6 +6,10 @@ public partial class ChessBoard : Node2D {
MoveRecords Records = null;
// Called when the node enters the scene tree for the first time.
public Vector2 selectedNodePos = Vector2.Inf;
public delegate bool ChessMoveFunc(Vector2 toPos, Vector2 fromPos);
public Callable chessMoveFunc { get; set; }
public override void _Ready() {
Board = new VirtualBoard(this as Node);
Records = new MoveRecords(onUndoRecordCallback: (newNode, oldNode, newPos, oldPos) => {
@ -16,72 +20,55 @@ public partial class ChessBoard : Node2D {
if (newPiece != null) {
Board.InsertNode(newPiece, newPos);
}
// if (oldPiece != null) {
// Board.RemoveNode(oldPos);
// }
});
Board.InitChessBoard();
// this.AddChild();
}
public override void _Input(InputEvent @event) {
if (@event is InputEventMouseButton mouseEvent &&
mouseEvent.Pressed &&
mouseEvent.ButtonIndex == MouseButton.Left) {
// HandleMouseClick(GetGlobalTransformWithCanvas().AffineInverse() * mouseButton.Position);
// HandleMouseClick(GetLocalMousePosition());
ActionPos(
(PosTrans.transArrToPix.AffineInverse() *
GetLocalMousePosition()).Round()
);
HandleMouseClick(GetLocalMousePosition());
// ActionPos(
// (PosTrans.transArrToPix.AffineInverse() *
// GetLocalMousePosition()).Round()
// );
}
}
public void ActionPos(Vector2 clickArrPos) {
if (VirtualBoard.ArrPosOutOfRange(clickArrPos)) return;
GD.Print($"{clickArrPos} mouse clicked");
ChessPiece NowChess = Board.getNodeFromBoard(clickArrPos) as ChessPiece;
ChessPiece SelChess = Board.getNodeFromBoard(selectedNodePos) as ChessPiece;
if (Vector2.Inf.Equals(selectedNodePos)) {
if (NowChess == null) {
return;
}
NowChess.Selected();
GD.Print($"{clickArrPos} is selected");
selectedNodePos = clickArrPos;
} else if (selectedNodePos == clickArrPos) {
if (SelChess != null) {
SelChess.DeSelected();
}
GD.Print($"{selectedNodePos} is deselected");
selectedNodePos = Vector2.Inf;
} else {
GD.Print($"{selectedNodePos} move to {clickArrPos}");
if (SelChess != null) {
SelChess.DeSelected();
GD.Print($"{selectedNodePos} is deselected");
}
Node NowNode;
if (NowChess != null) {
GD.Print("nowchess", NowChess);
NowNode = NowChess.Duplicate();
Board.RemoveNode(clickArrPos);
} else {
NowNode = NowChess as Node;
}
Records.AddRecord(NowNode, SelChess, clickArrPos, selectedNodePos);
Board.MoveNode(clickArrPos, selectedNodePos);
selectedNodePos = Vector2.Inf;
// GD.Print($"End: {selectedNodePos} {SelChess} move to {clickArrPos} {NowChess}");
}
public void ActionPos(Vector2 newPos) {
}
public void undo() {
public void MoveAndRecord(Vector2 toPos, Vector2 fromPos) {
GD.Print($"{fromPos} move to {toPos}");
ChessPiece toChess = Board.GetNodeFromBoard(toPos) as ChessPiece;
ChessPiece fromChess = Board.GetNodeFromBoard(fromPos) as ChessPiece;
if (fromChess != null) {
fromChess.DeSelected();
}
Node NowNode;
if (toChess != null) {
GD.Print("nowchess", toChess);
NowNode = toChess.Duplicate();
Board.RemoveNode(toPos);
} else {
NowNode = toChess as Node;
}
Records.AddRecord(NowNode, fromChess, toPos, fromPos);
Board.MoveNode(toPos, fromPos);
selectedNodePos = Vector2.Inf;
}
public void Undo() {
Records.Undo();
}
public void redo() {
public void ReInit() {
Records.Clear();
Board.Clear();
selectedNodePos = Vector2.Inf;
@ -89,13 +76,39 @@ public partial class ChessBoard : Node2D {
}
private void HandleMouseClick(Vector2 clickPosition) {
Vector2 newPos = (PosTrans.transArrToPix.AffineInverse() *
clickPosition).Round();
// 如果有棋子被选中,则进行后续操作
// if (nowSelectedChess == null) {
// seletedNode = null;
// } else if (nowSelectedChess != null && seletedNode != null) {
// if (seletedNode is ChessPiece chessPiece) MoveChess(arrayPos, chessPiece.arrPos);
// }
if (VirtualBoard.ArrPosOutOfRange(newPos)) return;
GD.Print($"{newPos} mouse clicked");
ChessPiece NowChess = Board.GetNodeFromBoard(newPos) as ChessPiece;
ChessPiece SelChess = Board.GetNodeFromBoard(selectedNodePos) as ChessPiece;
if (Vector2.Inf.Equals(selectedNodePos)) {
if (NowChess == null) {
return;
}
NowChess.Selected();
selectedNodePos = newPos;
} else if (selectedNodePos == newPos) {
if (SelChess != null) {
SelChess.DeSelected();
}
selectedNodePos = Vector2.Inf;
} else {
if (chessMoveFunc.Delegate != null) {
GD.Print("chessMoveFunc Move: ", selectedNodePos, "->", newPos);
if ((bool)chessMoveFunc.Call(newPos, selectedNodePos)) {
MoveAndRecord(newPos, selectedNodePos);
}
} else {
GD.Print("default MoveFunc Move: ", selectedNodePos, "->", newPos);
MoveAndRecord(newPos, selectedNodePos);
}
// MoveAndRecord(newPos, selectedNodePos);
// GD.Print($"End: {selectedNodePos} {SelChess} move to {clickArrPos} {NowChess}");
}
}

View File

@ -35,6 +35,7 @@ public partial class ChessPiece : Sprite2D {
if (isSelected) {
return;
}
GD.Print($"{arrPos} is selected");
this.Transform *= transToSeleted;
this.isSelected = true;
}
@ -43,6 +44,7 @@ public partial class ChessPiece : Sprite2D {
if (!isSelected) {
return;
}
GD.Print($"{arrPos} is deselected");
this.Transform *= transToSeleted.AffineInverse();
this.isSelected = false;
}
@ -54,7 +56,7 @@ public partial class ChessPiece : Sprite2D {
private void InitLabel() {
// this.Texture.ResourcePath = "res://Asserts/ChesspieceBase.tres";
this.Texture = (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres");
this.Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres");
textureSize = this.Texture.GetSize();
Vector2 labalPosition = new(
-textureSize.X / 2,

85
Scripts/Global.cs Normal file
View File

@ -0,0 +1,85 @@
using Godot;
using RPPackage;
public partial class Global : Node
{
public RPClientEDWS RPClient = new();
public string sessionId;
public string URL = "wss://game.zzyxyz.com/";
public Node CurrentScene { get; set; }
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
RPClient.OnRPCError += (string errCode, string type, string cmd, string errMsg) => {
GD.PrintErr($"errCode {errCode}, type/cmd {type}/{cmd}, errMsg {errMsg}");
};
RPClient.OnClose += (string eventName, object[] args) => {
SetProcess(false);
};
RPClient.OnOpen += (string eventName, object[] args) => {
RPClient.UserInit("undefined", "godot chessboard", () => {
return RPClient.RegionAdd("server");
});
};
Viewport root = GetTree().Root;
CurrentScene = root.GetChild(root.GetChildCount() - 1);
SetProcess(false);
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
RPClient.PollEx(delta);
}
public override void _Notification(int what)
{
if (what == NotificationWMCloseRequest) {
RPClient.Close();
GetTree().Quit(); // default behavior
}
}
public delegate void ChangeSceneCallback(Node newSence);
private static ChangeSceneCallback changeSceneCallback = null;
public void GotoScene(string path, ChangeSceneCallback callback = null)
{
// This function will usually be called from a signal callback,
// or some other function from the current scene.
// Deleting the current scene at this point is
// a bad idea, because it may still be executing code.
// This will result in a crash or unexpected behavior.
// The solution is to defer the load to a later time, when
// we can be sure that no code from the current scene is running:
if (callback != null) {
changeSceneCallback = callback;
}
Callable callbackWrapper = new(null, nameof(changeSceneCallback));
CallDeferred(MethodName.DeferredGotoScene, path, callbackWrapper);
changeSceneCallback = null;
}
public void DeferredGotoScene(string path, Callable onLoaded)
{
// It is now safe to remove the current scene.
CurrentScene.Free();
// Load a new scene.
var nextScene = GD.Load<PackedScene>(path);
// Instance the new scene.
CurrentScene = nextScene.Instantiate();
if (changeSceneCallback != null) {
onLoaded.Call(CurrentScene);
}
// Add it to the active scene, as child of root.
GetTree().Root.AddChild(CurrentScene);
// Optionally, to make it compatible with the SceneTree.change_scene_to_file() API.
GetTree().CurrentScene = CurrentScene;
}
}

320
Scripts/Lib/RPClient.cs Normal file
View File

@ -0,0 +1,320 @@
using System;
using System.Threading;
using Godot;
using Godot.Collections;
namespace RPPackage {
public partial class RPClientBaseEDWS : EventDrivenWebSocket {
public delegate void RPClientEventHandler(string cmd, Dictionary data);
public delegate void RPClientErrorHandler(string errCode, string type, string cmd, string errMsg);
public event RPClientEventHandler OnRPCUser;
public event RPClientEventHandler OnRPCRegion;
public event RPClientEventHandler OnRPCSession;
public event RPClientEventHandler OnRPCMsg;
public event RPClientErrorHandler OnRPCError;
public RPClientBaseEDWS() : base() {
OnText += (text) => {
GD.Print($"response: {text}");
RPMessage msg = RPHelper.HandleIncomingMessage(text);
if (msg.Code != "0000") {
OnRPCError?.Invoke(msg.Code, msg.Type, msg.Cmd, msg.Data.ToString());
return;
}
switch (msg.Type) {
case "user":
OnRPCUser?.Invoke(msg.Cmd, msg.Data);
break;
case "region":
OnRPCRegion?.Invoke(msg.Cmd, msg.Data);
break;
case "session":
OnRPCSession?.Invoke(msg.Cmd, msg.Data);
break;
case "msg":
OnRPCMsg?.Invoke(msg.Cmd, msg.Data);
break;
default:
OnRPCError?.Invoke(msg.Code, "unknown", msg.Cmd, msg.Data.ToString());
break;
}
};
}
protected void MakeRPCError(string errCode, string type, string cmd, string errMsg) {
this.OnRPCError?.Invoke(errCode, type, cmd, errMsg ?? "null");
}
}
public partial class RPClientEDWS : RPClientBaseEDWS {
string userName;
string userId;
string userToken;
string regionId;
public string GetUserId() { return userId; }
public delegate void SessionRecvHandle(Dictionary msg);
public event SessionRecvHandle OnPRCSessionRecv;
public event RPClientEventHandler OnPRCSessionExit;
public RPClientEDWS() : base() {
OnRPCUser += (cmd, msg) => {
switch (cmd) {
case "init":
userId = msg["userId"].AsString();
userToken = msg["token"].AsString();
_userInitCallback?.Invoke();
break;
case "rename":
userName = msg["_"].AsString();
break;
default:
MakeRPCError("0000", "user", "unknown", msg?.ToString());
break;
}
};
OnRPCRegion += (cmd, msg) => {
switch (cmd) {
case "add":
break;
case "inspect":
// var regions = msg["_"].AsGodotArray<Dictionary>();
_regionRecvCallback?.Invoke(cmd, msg);
break;
case "list":
// var users = msg["_"].AsGodotArray<Dictionary>();
_regionRecvCallback?.Invoke(cmd, msg);
break;
default:
MakeRPCError("0000", "region", "unknown", msg?.ToString());
break;
}
};
OnRPCSession += (cmd, msg) => {
switch (cmd) {
case "sendAll":
OnPRCSessionRecv?.Invoke(msg);
break;
case "create":
break;
case "ackCreate":
// GD.PrintErr($"{cmd} {msg} {__SessionAckCreateCallback__ == null}");
_sessionAckCreateCallback?.Invoke(cmd, msg);
break;
case "exit":
OnPRCSessionExit?.Invoke(cmd, msg);
break;
default:
break;
}
};
OnRPCMsg += (cmd, msg) => {
switch(cmd) {
case "echo":
GD.Print(msg["_"].AsString());
break;
default:
break;
}
};
OnRPCError += (code, type, cmd, msg) => {
};
}
public void ClearRPCClientFunc() {
}
public bool IsOnline() {
return GetIsConnected() && userId != null;
}
public void ConnectServer(string url) {
}
public void ExitServer() {
this.SendRPMessage(new RPMessage {
Type = "user",
Cmd = "exit",
Uid = userId,
Token = userToken,
});
userId = null;
userToken = null;
this.Close();
}
public delegate bool UserInitCallback();
private UserInitCallback _userInitCallback;
public bool UserInit(string userName, string fingerPrint, UserInitCallback callback) {
if (this.GetIsConnected() == false) {
return false;
}
this.SendRPMessage(new RPMessage{
Type = "user",
Cmd = "init",
Data = new Dictionary {
{ "userName", userName },
{ "fingerPrint", fingerPrint }
}
});
_userInitCallback = null;
if (callback != null) _userInitCallback += callback;
return true;
}
public bool UserRename(string newName) {
if (this.GetIsConnected() == false) {
return false;
}
this.SendRPMessage(new RPMessage{
Type = "user",
Cmd = "rename",
Uid = userId,
Token = userToken,
Data = new Dictionary {
{ "_", newName }
}
});
return true;
}
public bool RegionAdd(string regionId) {
if (this.GetIsConnected() == false || this.userId == null) {
return false;
}
this.SendRPMessage(new RPMessage{
Type = "region",
Cmd = "add",
Uid = userId,
Token = userToken,
Data = new Dictionary {
{ "regionId", regionId }
}
});
return true;
}
public delegate bool RegionInspectCallback(
Array<Dictionary<string, string>> _
);
private RPClientEventHandler _regionRecvCallback;
public bool RegionInspect(string regionId, RegionInspectCallback callback) {
if (this.GetIsConnected() == false || this.userId == null) {
return false;
}
_regionRecvCallback = null;
_regionRecvCallback += (cmd, msg) => {
if (cmd != "inspect") {
return;
}
callback(msg["_"].AsGodotArray<Dictionary<string, string>>());
};
this.SendRPMessage(new RPMessage{
Type = "region",
Cmd = "inspect",
Uid = userId,
Token = userToken,
Data = new Dictionary {
{ "regionId", regionId }
}
});
return true;
}
public delegate bool SessionAckCreateCallback(
string sessionId,
bool res,
string reqUserId,
string reqUserName
);
private RPClientEventHandler _sessionAckCreateCallback;
public void RegSessionAckCreateCallback(SessionAckCreateCallback recvCallback) {
_sessionAckCreateCallback = null;
_sessionAckCreateCallback += (cmd, msg) => {
string sessionId = msg["sessionId"].AsString();
bool res = msg.ContainsKey("res") && msg["res"].AsBool();
string reqUserId = msg.ContainsKey("reqUserId") ? msg["reqUserId"].AsString() : null;
string reqUserName = msg.ContainsKey("reqUserName") ? msg["reqUserName"].AsString() : null;
recvCallback(sessionId, res, reqUserId, reqUserName);
};
}
public bool SessionCreate(string[] usersId) {
if (this.GetIsConnected() == false || this.userId == null) {
return false;
}
this.SendRPMessage(new RPMessage {
Type = "session",
Cmd = "create",
Uid = userId,
Token = userToken,
Data = new Dictionary {
{ "_", usersId }
}
});
return true;
}
public bool SessionAckCreate(string sessionId, bool response)
{
if (this.GetIsConnected() == false || this.userId == null) {
return false;
}
// 根据响应情况发送确认消息给服务器
string code = response ? "0000" : "0001";
this.SendRPMessage(new RPMessage {
Type = "session",
Cmd = "ackCreate",
Uid = userId,
Token = userToken,
Code = code,
Data = new Dictionary {
{ "sessionId", sessionId },
{ "res", response }
}
});
return true;
}
public bool SendSessionToAll(string sessionId, Variant data) {
if (this.GetIsConnected() == false || this.userId == null || sessionId == null) {
return false;
}
this.SendRPMessage(new RPMessage{
Type = "session",
Cmd = "sendAll",
Uid = userId,
Token = userToken,
Data = new Dictionary {
{ "sessionId", sessionId },
{ "msg", data },
}
});
return true;
}
// ws.OnOpen += (eventName, args) => {
// ws.SendRPMessage(new RPMessage{
// Type = "user",
// Cmd = "tmp",
// });
// };
// ws.OnError += (eventName, args) => {
// GD.PrintErr(args);
// // SetProcess(false);
// };
// ws.OnClose += (eventName, args) => {
// // GD.Print("close");
// SetProcess(false);
// };
}
}

45
Scripts/Lib/RPHelper.cs Normal file
View File

@ -0,0 +1,45 @@
using Godot;
using Godot.Collections;
namespace RPPackage {
public static class RPHelper
{
public static Dictionary SerializeRPMessage(RPMessage message)
{
return message.ToDictionary();
}
public static RPMessage DeserializeRPMessage(Dictionary data)
{
return new RPMessage
{
Type = data.ContainsKey("type") ? (string)data["type"] : null,
Cmd = data.ContainsKey("cmd") ? (string)data["cmd"] : null,
Code = data.ContainsKey("code") ? (string)data["code"] : null,
Uid = data.ContainsKey("uid") ? (string)data["uid"] : null,
Token = data.ContainsKey("token") ? (string)data["token"] : null,
Data = data.ContainsKey("data") ? data["data"].AsGodotDictionary() : null,
};
}
// 假设你有WebSocket通信的实现这里仅展示如何使用上述方法
public static void SendRPMessage(this EventDrivenWebSocket ws, RPMessage message)
{
ws.SendJsonEx(RPHelper.SerializeRPMessage(message));
}
public static RPMessage HandleIncomingMessage(string jsonMessage)
{
var dataDict = Json.ParseString(jsonMessage).AsGodotDictionary();
if (dataDict != null)
{
return RPHelper.DeserializeRPMessage(dataDict);
}
else
{
GD.PrintErr("Failed to parse incoming message.");
return null;
}
}
}
}

33
Scripts/Lib/RPMessage.cs Normal file
View File

@ -0,0 +1,33 @@
using Godot;
using Godot.Collections;
namespace RPPackage {
public class RPMessage
{
public string Type { get; set; }
public string Cmd { get; set; }
public Dictionary Data { get; set; }
public string Uid { get; set; }
public string Token { get; set; }
public string Code { get; set; }
public Dictionary ToDictionary()
{
var dict = new Dictionary();
if (Type != null)
dict.Add("type", Type);
if (Cmd != null)
dict.Add("cmd", Cmd);
if (Data != null)
dict.Add("data", Data);
if (Uid != null)
dict.Add("uid", Uid);
if (Token != null)
dict.Add("token", Token);
if (Code != null)
dict.Add("code", Code);
return dict;
}
}
}

0
Scripts/Lib/csws.cs Normal file
View File

114
Scripts/Lib/gdws.cs Normal file
View File

@ -0,0 +1,114 @@
using Godot;
using Godot.Collections;
public partial class EventDrivenWebSocket : WebSocketPeer {
public delegate void WebSocketEventHandler(string eventName, params object[] args);
public delegate void WSBinMsgEventHandler(byte[] args);
public delegate void WSMsgEventHandler(string args);
public event WebSocketEventHandler OnOpen;
public event WSBinMsgEventHandler OnMessage;
public event WSMsgEventHandler OnText;
public event WSBinMsgEventHandler OnBinary;
public event WebSocketEventHandler OnClose;
public event WebSocketEventHandler OnError;
private bool isConnected = false;
private bool isCloseEventFired = false;
private double connectingTime = double.NegativeInfinity;
public EventDrivenWebSocket() : base() {
isConnected = false;
}
public bool GetIsConnected() {
return isConnected;
}
public void PollEx(double delta) {
base.Poll();
CheckAndDispatchEvents(delta);
}
public void ConnectToUrlEx(string url, double delayTime = 3,
TlsOptions tlsClientOptions = null) {
if (connectingTime >= 0) {
return;
}
Error err = ConnectToUrl(url, tlsClientOptions);
if (err != Error.Ok) {
OnError?.Invoke("error", err);
}
connectingTime = delayTime;
}
protected void CheckAndDispatchEvents(double delta) {
var state = GetReadyState();
switch (state) {
case State.Open when !isConnected:
isConnected = true;
OnOpen?.Invoke("open", null);
break;
case State.Open:
while (GetAvailablePacketCount() > 0) {
byte[] packet = GetPacket();
OnMessage?.Invoke(packet);
if (WasStringPacket()) {
OnText?.Invoke(packet.GetStringFromUtf8());
} else {
OnBinary?.Invoke(packet);
}
}
break;
case State.Closed:
connectingTime = double.NegativeInfinity;
OnClose?.Invoke("closed", GetCloseCode(), GetCloseReason());
isConnected = false;
break;
case State.Closing:
// OnClose?.Invoke("closing");
break;
case State.Connecting:
if (connectingTime >= 0) {
connectingTime -= delta;
} else if (connectingTime != double.NegativeInfinity){
connectingTime = double.NegativeInfinity;
Close();
OnError?.Invoke("connectTimeOut", Error.Timeout);
}
break;
default:
// 可以在这里处理其他状态或错误
OnError?.Invoke("error", "Unknown WebSocket state.");
break;
}
}
public void SendBinaryEx(byte[] data) {
SendEx(data);
}
public void SendJsonEx(Dictionary msg) {
SendTextEx(Json.Stringify(msg));
}
public Error SendTextEx(string message) {
if (isConnected) {
return SendText(message);
} else {
OnError?.Invoke("error", "Attempt to send on a closed connection.");
return Error.ConnectionError;
}
}
public Error SendEx(byte[] message, WriteMode writeMode = WriteMode.Binary) {
if (isConnected) {
return Send(message, writeMode);
} else {
OnError?.Invoke("error", "Attempt to send on a closed connection.");
return Error.ConnectionError;
}
}
}

View File

@ -1,4 +1,3 @@
using System.Dynamic;
using Godot;
class VirtualBoard {
@ -11,12 +10,12 @@ class VirtualBoard {
return arrayPos.X < 0 || arrayPos.X >= boradRows || arrayPos.Y < 0 || arrayPos.Y >= boradCols;
}
public Node getNodeFromBoard(Vector2 arrayPos) {
public Node GetNodeFromBoard(Vector2 arrayPos) {
if (ArrPosOutOfRange(arrayPos)) return null;
return nodesBorad[(int)arrayPos.X, (int)arrayPos.Y];
}
public void setNodeToBoard(Vector2 arrayPos, Node node) {
public void SetNodeToBoard(Vector2 arrayPos, Node node) {
if (ArrPosOutOfRange(arrayPos)) return;
nodesBorad[(int)arrayPos.X, (int)arrayPos.Y] = node;
}
@ -30,29 +29,29 @@ class VirtualBoard {
if (ArrPosOutOfRange(newPos) || ArrPosOutOfRange(oldPos)) {
return false;
}
if (getNodeFromBoard(newPos) != null) {
if (GetNodeFromBoard(newPos) != null) {
return false;
}
if (getNodeFromBoard(oldPos) is ChessPiece chessPiece) {
if (GetNodeFromBoard(oldPos) is ChessPiece chessPiece) {
chessPiece.movePos(newPos);
}
setNodeToBoard(newPos, getNodeFromBoard(oldPos));
setNodeToBoard(oldPos, null);
SetNodeToBoard(newPos, GetNodeFromBoard(oldPos));
SetNodeToBoard(oldPos, null);
return true;
}
public bool RemoveNode(Vector2 arrayPos) {
if (ArrPosOutOfRange(arrayPos)) return false;
if (getNodeFromBoard(arrayPos) is ChessPiece chessPiece) {
if (GetNodeFromBoard(arrayPos) is ChessPiece chessPiece) {
chessPiece.QueueFree();
}
setNodeToBoard(arrayPos, null);
SetNodeToBoard(arrayPos, null);
return true;
}
public void InsertNode(ChessPiece chess, Vector2 arrayPos) {
chess.Position = PosTrans.transArrToPix * arrayPos;
setNodeToBoard(arrayPos, chess);
SetNodeToBoard(arrayPos, chess);
BoardRoot.AddChild(chess);
}

View File

@ -40,7 +40,7 @@ application/product_version=""
application/company_name=""
application/product_name=""
application/file_description=""
application/copyright=""
application/copyright="0.0.2"
application/trademarks=""
application/export_angle=0
ssh_remote_deploy/enabled=false
@ -61,7 +61,7 @@ ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debu
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
Remove-Item -Recurse -Force '{temp_dir}'"
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/include_debug_symbols=false
dotnet/embed_build_outputs=false
[preset.1]
@ -92,8 +92,8 @@ architectures/armeabi-v7a=false
architectures/arm64-v8a=true
architectures/x86=false
architectures/x86_64=false
version/code=1
version/name=""
version/code=2
version/name="0.0.2"
package/unique_name="com.example.$genname"
package/name=""
package/signed=true
@ -267,113 +267,5 @@ permissions/write_social_stream=false
permissions/write_sync_settings=false
permissions/write_user_dictionary=false
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false
[preset.2]
name="macOS"
platform="macOS"
runnable=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path=""
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
[preset.2.options]
export/distribution_type=1
binary_format/architecture="universal"
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
application/icon=""
application/icon_interpolation=4
application/bundle_identifier=""
application/signature=""
application/app_category="Games"
application/short_version=""
application/version=""
application/copyright=""
application/copyright_localized={}
application/min_macos_version="10.12"
application/export_angle=0
display/high_res=true
xcode/platform_build="14C18"
xcode/sdk_version="13.1"
xcode/sdk_build="22C55"
xcode/sdk_name="macosx13.1"
xcode/xcode_version="1420"
xcode/xcode_build="14C18"
codesign/codesign=1
codesign/installer_identity=""
codesign/apple_team_id=""
codesign/identity=""
codesign/entitlements/custom_file=""
codesign/entitlements/allow_jit_code_execution=false
codesign/entitlements/allow_unsigned_executable_memory=false
codesign/entitlements/allow_dyld_environment_variables=false
codesign/entitlements/disable_library_validation=false
codesign/entitlements/audio_input=false
codesign/entitlements/camera=false
codesign/entitlements/location=false
codesign/entitlements/address_book=false
codesign/entitlements/calendars=false
codesign/entitlements/photos_library=false
codesign/entitlements/apple_events=false
codesign/entitlements/debugging=false
codesign/entitlements/app_sandbox/enabled=false
codesign/entitlements/app_sandbox/network_server=false
codesign/entitlements/app_sandbox/network_client=false
codesign/entitlements/app_sandbox/device_usb=false
codesign/entitlements/app_sandbox/device_bluetooth=false
codesign/entitlements/app_sandbox/files_downloads=0
codesign/entitlements/app_sandbox/files_pictures=0
codesign/entitlements/app_sandbox/files_music=0
codesign/entitlements/app_sandbox/files_movies=0
codesign/entitlements/app_sandbox/files_user_selected=0
codesign/entitlements/app_sandbox/helper_executables=[]
codesign/custom_options=PackedStringArray()
notarization/notarization=0
privacy/microphone_usage_description=""
privacy/microphone_usage_description_localized={}
privacy/camera_usage_description=""
privacy/camera_usage_description_localized={}
privacy/location_usage_description=""
privacy/location_usage_description_localized={}
privacy/address_book_usage_description=""
privacy/address_book_usage_description_localized={}
privacy/calendar_usage_description=""
privacy/calendar_usage_description_localized={}
privacy/photos_library_usage_description=""
privacy/photos_library_usage_description_localized={}
privacy/desktop_folder_usage_description=""
privacy/desktop_folder_usage_description_localized={}
privacy/documents_folder_usage_description=""
privacy/documents_folder_usage_description_localized={}
privacy/downloads_folder_usage_description=""
privacy/downloads_folder_usage_description_localized={}
privacy/network_volumes_usage_description=""
privacy/network_volumes_usage_description_localized={}
privacy/removable_volumes_usage_description=""
privacy/removable_volumes_usage_description_localized={}
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="#!/usr/bin/env bash
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
open \"{temp_dir}/{exe_name}.app\" --args {cmd_args}"
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
kill $(pgrep -x -f \"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\")
rm -rf \"{temp_dir}\""
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/include_debug_symbols=false
dotnet/embed_build_outputs=false

View File

@ -15,6 +15,10 @@ run/main_scene="res://Main.tscn"
config/features=PackedStringArray("4.2", "C#", "Mobile")
config/icon="res://Asserts/icon.svg"
[autoload]
Global="*res://Scripts/Global.cs"
[display]
window/size/viewport_width=720