【Unity】MiniGame编辑器小游戏(十五)中国象棋局域网对战【Chinese Chess】(上)

更新日期:2026年5月20日。

项目源码:获取项目源码

索引

中国象棋【Chinese Chess】

本篇的目标是开发一个中国象棋【Chinese Chess】小游戏,可以与你的好同(ji)事(you)进行局域网对战。

一、游戏最终效果

Unity编辑器小游戏:中国象棋局域网对战

二、玩法简介

本游戏的玩法与标准中国象棋的规则一致,当轮到己方走棋时,通过鼠标左键点击选择棋子,然后点击棋盘位置移动棋子,当达到获胜或失败条件时,游戏结束。

三、正式开始

1.定义游戏窗口类

首先,定义中国象棋的游戏窗口类MiniGame_ChineseChess,其继承至MiniGameWindow【小游戏窗口基类】

csharp 复制代码
    /// <summary>
    /// 中国象棋
    /// </summary>
    public class MiniGame_ChineseChess : MiniGameWindow
    {
    
    }

2.规划游戏窗口、视口区域

通过覆写虚属性实现规划游戏视口区域大小:

csharp 复制代码
        /// <summary>
        /// 游戏名称
        /// </summary>
        public override string Name => "中国象棋 [Chinese Chess]";
        /// <summary>
        /// 游戏窗体大小
        /// </summary>
        public override Vector2 WindowSize => new Vector2(480, 330);
        /// <summary>
        /// 游戏视口区域
        /// </summary>
        public override Rect ViewportRect => new Rect(5, 25, 300, 300);

注意:游戏窗体大小必须 > 游戏视口区域。

然后通过代码打开此游戏窗口:

csharp 复制代码
        [MenuItem("MiniGame/中国象棋 [Chinese Chess]", priority = 13)]
        private static void Open_MiniGame_ChineseChess()
        {
            MiniGameWindow.OpenWindow<MiniGame_ChineseChess>();
        }

便可以看到游戏的窗口、视口区域如下(左侧深色凹陷区域为视口区域):

3.游戏状态

游戏状态有如下几种:

csharp 复制代码
        /// <summary>
        /// 游戏状态
        /// </summary>
        public enum GameState : byte
        {
            /// <summary>
            /// 开始界面
            /// </summary>
            Start = 0,
            /// <summary>
            /// 创建房间
            /// </summary>
            Create = 1,
            /// <summary>
            /// 加入房间
            /// </summary>
            Join = 2,
            /// <summary>
            /// 进行中
            /// </summary>
            Playing = 3,
            /// <summary>
            /// 结束
            /// </summary>
            End = 4
        }
①.开始界面

当游戏窗口打开时,默认进入开始界面,通过在OnGameViewportGUI方法中绘制开始界面的UI内容:

csharp 复制代码
        /// <summary>
        /// 游戏状态
        /// </summary>
        public GameState State {get; private set;}

        protected override void OnInit()
        {
            base.OnInit();

			//默认进入开始界面
            State = GameState.Start;
        }
        protected override void OnGameViewportGUI()
        {
            base.OnGameViewportGUI();

            switch (State)
            {
                case GameState.Start: OnGameViewportGUI_StartState(); break;
            }
        }

		//开始界面通过此方法绘制UI内容
        private void OnGameViewportGUI_StartState()
        {
            Rect rect = new Rect(ViewportRect.width * 0.5f - 80, ViewportRect.height * 0.5f - 30, 160, 60);
            GUI.Label(rect, "中国象棋", _titleGS);

            rect.y += 60;
            rect.height = 20;
            if (GUI.Button(rect, "创建房间"))
            {
                State = GameState.Create;
            }

            rect.y += 25;
            rect.height = 20;
            if (GUI.Button(rect, "加入房间"))
            {
                State = GameState.Join;
            }
        }

开始界面如下:

玩家在开始界面可以选择创建房间还是加入房间

②.创建房间

玩家点击创建房间按钮,即切换到了创建房间状态,依然在OnGameViewportGUI方法中绘制创建房间的UI内容:

csharp 复制代码
		//房间的IP地址
        private string _createIP = "127.0.0.1";
        //房间的端口号
        private int _createPort = 12580;
        //房间的监听Socket
        private Socket _listenSocket;
        //是否正在监听中
        private bool _isListening = false;
        
        protected override void OnGameViewportGUI()
        {
            base.OnGameViewportGUI();

            switch (State)
            {
                case GameState.Start: OnGameViewportGUI_StartState(); break;
                case GameState.Create: OnGameViewportGUI_CreateState(); break;
            }
        }

        private void OnGameViewportGUI_CreateState()
        {
            Rect rect = new Rect(ViewportRect.width * 0.5f - 80, ViewportRect.height * 0.5f - 100, 160, 40);
            GUI.Label(rect, "创建房间", _createTitleGS);

            bool enabled = GUI.enabled;
            GUI.enabled = !_isListening;

            rect.x = ViewportRect.width * 0.5f - 80;
            rect.y += 60;
            rect.width = 30;
            rect.height = 20;
            GUI.Label(rect, "IP");
            rect.x += 30;
            rect.width = 130;
            _createIP = EditorGUI.TextField(rect, _createIP);

            rect.x = ViewportRect.width * 0.5f - 80;
            rect.y += 25;
            rect.width = 30;
            rect.height = 20;
            GUI.Label(rect, "端口");
            rect.x += 30;
            rect.width = 130;
            _createPort = EditorGUI.IntField(rect, _createPort);

            rect.x = ViewportRect.width * 0.5f - 80;
            rect.y += 25;
            rect.width = 160;
            if (GUI.Button(rect, "创建"))
            {
                StartListen();

                if (_isListening)
                {
                    ListenAsync();
                }
            }

            GUI.enabled = enabled;

            if (_isListening)
            {
                rect.x = ViewportRect.width * 0.5f - 80;
                rect.y += 25;
                rect.width = 160;
                GUI.Label(rect, "创建成功,等待玩家加入......");
            }
        }

创建房间界面如下:

由于中国象棋是双人对战棋牌,所以我们的房间只能容纳两人,当玩家创建了一个房间后,IP地址端口号也即是房间的唯一标识,会启动一个Socket监听该IP地址和端口号,等待另一位成员的加入。

csharp 复制代码
        /// <summary>
        /// 开始监听其他玩家连接
        /// </summary>
        private void StartListen()
        {
            if (_listenSocket == null)
                _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
            	//启动监听Socket
                _listenSocket.Bind(new IPEndPoint(IPAddress.Parse(_createIP), _createPort));
                _listenSocket.Listen(1);
                _isListening = true;
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
                _isListening = false;
            }
        }

当监听到其他玩家的加入后,创建房间成功,双方便开始棋局。

csharp 复制代码
        /// <summary>
        /// 监听其他玩家连接中
        /// </summary>
        private async void ListenAsync()
        {
            try
            {
                _connectSocket = await _listenSocket.AcceptAsync().ConfigureAwait(true);
            }
            catch (Exception)
            { 
            }

            //成员玩家成功接入
            if (IsConnected)
            {
            	//结束监听,同一房间只允许一名成员加入
                EndListen();

                //随机自身阵营
                bool isRed = UnityEngine.Random.Range(0, 10) >= 5;

                //开始游戏(传入参数:自身阵营)
                StartGame(isRed ? PieceCamp.Red : PieceCamp.Black);

                //启动接收消息循环
                ReceiveMessage();

				//发送开始游戏消息到对手(传入参数:对手阵营)
                SendMessage_StartGame(isRed ? PieceCamp.Black : PieceCamp.Red);
            }
        }

        /// <summary>
        /// 停止监听其他玩家连接
        /// </summary>
        private void EndListen()
        {
            if (_listenSocket != null)
            {
                try
                {
                    _listenSocket.Shutdown(SocketShutdown.Both);
                }
                catch (Exception)
                { 
                }
                _listenSocket.Close();
                _listenSocket = null;
            }
            _isListening = false;
        }
③.加入房间

玩家点击加入房间按钮,即切换到了加入房间状态,依然在OnGameViewportGUI方法中绘制加入房间的UI内容:

csharp 复制代码
		//房间的IP地址
		private string _joinIP = "";
		//房间的端口号
		private int _joinPort = 12580;
		//与对手保持通信的Socket
		private Socket _connectSocket;
		//是否正在连接中
		private bool _isConnecting = false;

        protected override void OnGameViewportGUI()
        {
            base.OnGameViewportGUI();

            switch (State)
            {
                case GameState.Start: OnGameViewportGUI_StartState(); break;
                case GameState.Create: OnGameViewportGUI_CreateState(); break;
                case GameState.Join: OnGameViewportGUI_JoinState(); break;
            }
        }

        private void OnGameViewportGUI_JoinState()
        {
            Rect rect = new Rect(ViewportRect.width * 0.5f - 80, ViewportRect.height * 0.5f - 100, 160, 40);
            GUI.Label(rect, "加入房间", _joinTitleGS);

            bool enabled = GUI.enabled;
            GUI.enabled = !_isConnecting;

            rect.x = ViewportRect.width * 0.5f - 80;
            rect.y += 60;
            rect.width = 30;
            rect.height = 20;
            GUI.Label(rect, "IP");
            rect.x += 30;
            rect.width = 130;
            _joinIP = EditorGUI.TextField(rect, _joinIP);

            rect.x = ViewportRect.width * 0.5f - 80;
            rect.y += 25;
            rect.width = 30;
            rect.height = 20;
            GUI.Label(rect, "端口");
            rect.x += 30;
            rect.width = 130;
            _joinPort = EditorGUI.IntField(rect, _joinPort);

            rect.x = ViewportRect.width * 0.5f - 80;
            rect.y += 25;
            rect.width = 160;
            if (GUI.Button(rect, "加入"))
            {
                StartConnect();
            }

            GUI.enabled = enabled;

            if (_isConnecting)
            {
                rect.x = ViewportRect.width * 0.5f - 80;
                rect.y += 25;
                rect.width = 160;
                GUI.Label(rect, "正在加入房间......");
            }
        }

加入房间界面如下:

通过其他途径获取到了他人创建的房间IP地址端口号后,在此处输入并点击加入按钮便可加入该房间(也即是与房主建立双向连接):

csharp 复制代码
        /// <summary>
        /// 开始连接房主
        /// </summary>
        private async void StartConnect()
        {
            if (_connectSocket == null)
                _connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                _isConnecting = true;
                await _connectSocket.ConnectAsync(new IPEndPoint(IPAddress.Parse(_joinIP), _joinPort)).ConfigureAwait(true);
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
                _isConnecting = false;
            }

            //连接房主成功
            if (IsConnected)
            {
                //启动接收消息循环
                ReceiveMessage();
            }
        }

成员玩家不会主动开始游戏,连接房主成功后,房主会发送开始游戏的消息,收到消息后成员玩家才会进入游戏棋局。

④.游戏棋局

房主成员玩家都是通过调用StartGame开始游戏棋局:

csharp 复制代码
        /// <summary>
        /// 开始棋局
        /// </summary>
        /// <param name="selfCamp">自身阵营</param>
        private void StartGame(PieceCamp selfCamp)
        {
        	//获取对手的远端地址信息,作为对手姓名
            OpponentName = (_connectSocket.RemoteEndPoint as IPEndPoint).Address.ToString();
            SelfCamp = selfCamp;
            //按中国象棋规则,红先黑后
            PlayCamp = PieceCamp.Red;
            State = GameState.Playing;
        }

只不过房主是主动调用,成员玩家则是收到房主的开始游戏消息后再调用,关于消息通信我们暂时放到后面,与游戏棋局逻辑一起讲。

游戏棋局界面如下:

⑤.结束界面

当一局棋局结束时,进入结束界面,对弈双方一方是获胜方,一方是失败方,通过调用EndGame方法结束棋局:

csharp 复制代码
        /// <summary>
        /// 结束棋局
        /// </summary>
        /// <param name="isSuccessed">自身是否获胜</param>
        /// <param name="endDetails">获胜、失败描述细节</param>
        private void EndGame(bool isSuccessed, string endDetails)
        {
            if (isSuccessed) IsGameSuccessed = true;
            else IsGameOvered = true;

            EndDetails = endDetails;
            State = GameState.End;
        }

结束界面如下:

内容接下篇:【Unity】MiniGame编辑器小游戏(十六)中国象棋局域网对战【Chinese Chess】(下)

相关推荐
伽蓝_游戏3 小时前
第四章:AssetBundle 核心机制与文件结构
unity·c#·游戏引擎·游戏程序
郝学胜-神的一滴4 小时前
中级OpenGL教程 006:高光反射原理与 Shader 实现
c++·unity·godot·图形渲染·three.js·opengl·unreal
多云的夏天4 小时前
IDE-VSCODE-Continue + DeepSeek V4
ide·vscode·编辑器·deepseek
Robot_Nav5 小时前
Claude Code cli 以及vscode版本的各种命令参考手册
ide·vscode·编辑器
神码编程7 小时前
【Unity】MiniGame编辑器小游戏(十六)中国象棋局域网对战【Chinese Chess】(下)
unity·编辑器·游戏引擎·小游戏
Maddie_Mo7 小时前
Unity 联动 Trae AI 项目开发基础教学
人工智能·unity·游戏引擎
新手unity自用笔记20 小时前
unity简单新手上手动画系统讲解
unity·游戏引擎
伽蓝_游戏20 小时前
第二章:深入 Unity 资源导入管线 (Asset Import Pipeline)
游戏·unity·c#·游戏引擎·游戏程序
我寄人间雪满头丶1 天前
Unity中对于数值游戏的大数显示
游戏·unity·游戏引擎