【源碼】聯(lián)機(jī)五子棋,基于UDP可靠傳輸

前幾天在 游戲蠻牛論壇 發(fā)現(xiàn)一篇文章 《Socket聯(lián)機(jī)版五子棋》拉岁, 東方喵的版本情组,邏輯判斷在服務(wù)器端丈挟,基于 Unity TcpClient获枝。

初衷

因?yàn)閭€(gè)人挺喜歡五子棋游戲的,而且每天中午跟同事一起吃飯時(shí)馒稍,等餐時(shí)間皿哨,沒事可干,他們都是埋頭“搞機(jī)”纽谒。
所以呢,我打算重寫下五子棋這個(gè)游戲如输,在等餐的時(shí)候可以直接用手機(jī)來一局鼓黔。

現(xiàn)在每天中午我們都要來一局央勒,其樂無窮!0幕崔步!

感謝

感謝東方喵分享的資源;
感謝天梯實(shí)時(shí)對(duì)戰(zhàn)服務(wù)的商務(wù)小伙伴的熱情幫忙及聯(lián)機(jī)服務(wù)的支持缎谷。

下面呢井濒,就給大家分享下實(shí)現(xiàn)過程。

關(guān)于游戲

暫定名: GobangOnline
網(wǎng)絡(luò)協(xié)議: UDP
匹配:兩玩家隨機(jī)匹配(也有計(jì)劃加上 房間號(hào)或者密令匹配對(duì)戰(zhàn) 和 局域網(wǎng)聯(lián)機(jī)對(duì)戰(zhàn))列林;

先來張配圖:
(帶紅點(diǎn)的棋子為最后落子標(biāo)記)


游戲截圖

涉及同步數(shù)據(jù)

  • 匹配成功玩家昵稱同步
  • 玩家落子數(shù)據(jù)同步
  • 勝負(fù)同步

玩家昵稱同步

和一個(gè)朋友爭論了好幾次瑞你,還是決定把玩家輸入昵稱的邏輯保留下來。
玩家啟動(dòng)游戲希痴,輸入昵稱者甲,既可聯(lián)機(jī)對(duì)戰(zhàn)(昵稱保存到本地)。

1砌创,輸入昵稱虏缸,點(diǎn)擊“登錄”,將直接進(jìn)入匹配對(duì)手界面嫩实。

UIManager.cs

    public void onButtonClick(string name) {
        if(name == "")
            return;

        switch (name) {
        case "level":
            // 昵稱不能為空
            GameObject mainObj = ui.transform.Find ("Main").gameObject;
            GameObject textObj = mainObj.transform.Find ("InputField/Text").gameObject;
            GameObject errorTipObj = mainObj.transform.Find ("ErrorTip").gameObject;
            string nickname = textObj.GetComponent<Text> ().text;
            if (nickname == "") {
                errorTipObj.SetActive (true);
                return;
            }

            GameManager.nickname = nickname;

            // 記錄 nickname
            PlayerPrefs.SetString ("nickname", nickname);

            errorTipObj.SetActive (false);
            mainObj.SetActive (false);

            // 等級(jí)匹配
            GobangClient.connect (8);
            break;

        case "retry":
            GobangClient.disconnect ();
            break;

        }
    }

2刽辙,匹配成功后,發(fā)送握手交換名片(昵稱)數(shù)據(jù)甲献。

GobangClient.cs

    // GobangClient 主要重寫 Nanolink實(shí)時(shí)對(duì)戰(zhàn)服務(wù)已封裝的 onMessage, onResync, onStatusChanged, onConnected, onDisconnected 方法宰缤。
    // 重寫接受到數(shù)據(jù)的響應(yīng)函數(shù),在GameManager.recvMessage()中統(tǒng)一處理接收到數(shù)據(jù)
    protected override void onMessage (byte[] data, byte fromIndex) {
        // Hashtable values = GameSerialize.fromBytes (data);

        GameObject gameObj = GameObject.Find ("Game");
        if (gameObj == null)
            return;

        GameManager gameManager = gameObj.GetComponent<GameManager> ();
        if (gameManager == null)
            return;

        // 同意處理 接收到的數(shù)據(jù)
        gameManager.recvMessage(GameSerialize.fromBytes (data), fromIndex);
    }
    
    // 匹配成功時(shí)竟纳,自動(dòng)調(diào)用 onResync 方法
    protected override void onResync(byte fromIndex) {
        // 同步玩家的 名字
        {
            Debug.Log(GameManager.nickname);

            // 交換名字撵溃,握手
            {
                Hashtable values = new Hashtable ();
                values.Add ("name", "handshake");
                values.Add ("v", GameManager.nickname);

                GobangClient.send (GameSerialize.toBytes (values));
            }

            // 先進(jìn)入房間的玩家 持 黑子
            string chess = "";
            if (getInt ("client-index") == 0)
                chess = "黑子";
            else
                chess = "白子";

            GameObject ui = GameObject.Find ("UI");
            GameObject userInfoObj = ui.transform.Find ("UserInfo").gameObject;
            GameObject selfObj = userInfoObj.transform.Find ("Self").gameObject;
            selfObj.GetComponent<Text> ().text = "本人: " + GameManager.nickname + " (" + chess + ")";

            userInfoObj.SetActive (true);
        }
    }

3,收到對(duì)手的握手?jǐn)?shù)據(jù)時(shí)锥累,完成本地渲染缘挑。

GameManager.cs

    // 接收到消息
    public void recvMessage(Hashtable values, byte fromIndex) {
        string name = (string) values["name"];
        switch(name) {
        // 握手,交換名片
        case "handshake":
            {
                // 先進(jìn)入房間的玩家 持 黑子
                string theChess = "";
                if (fromIndex == 0)
                    theChess = "黑子";
                else
                    theChess = "白子";

                GameObject ui = GameObject.Find ("UI");
                GameObject userInfoObj = ui.transform.Find ("UserInfo").gameObject;
                GameObject adversaryObj = userInfoObj.transform.Find ("Adversary").gameObject;
                adversaryObj.GetComponent<Text> ().text = "對(duì)手: " + values["v"] + " (" + theChess + ")";

                userInfoObj.SetActive (true);
            }
            break;

        // 落子
        case "piece":
            {
                int x = (int) values["x"];
                int y = (int) values["y"];
                string turn = (string) values["turn"];

                GameObject chessObj = GameObject.Find ("Chess");
                if (chessObj == null)
                    return;

                // 渲染接收到的數(shù)據(jù)桶略,落子
                InputManager input = chessObj.GetComponent<InputManager> ();
                input.setPiece (x, y, turn);

                // 修正 chess 布子
                chess[x, y] = turn;
            }
            break;

        // gameover
        case "lose":
            {
                // gameover
                isOver = true;

                // UI
                GameObject ui = GameObject.Find("UI");

                GameObject resultObj = ui.transform.Find ("Result").gameObject;
                GameObject gameoverObj = resultObj.transform.Find ("GameOver").gameObject;
                gameoverObj.GetComponentInChildren<Text> () .text = "不幸慘敗";

                resultObj.SetActive(true);
            }
            break;

        default:
            Debug.Log ("無效的指令");
            break;
        }

    }

落子同步

玩家A落子操作后语淘,即刻同步落子數(shù)據(jù)給玩家B,玩家B接收到數(shù)據(jù)后际歼,渲染落子位置及對(duì)應(yīng)的棋子惶翻;
切換回合;

InputManager.cs

    ...
    private void Update () {
        // 確保連接后鹅心,操作才有效
        if(!GobangClient.isConnected())
            return;

        if (Input.GetMouseButtonDown(0) && (GameManager.curTurn == GameManager.userColor) && !GameManager.isOver) {
            RaycastHit2D hit= Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
            if(hit) {
                Debug.Log(hit.point);
                // 鼠標(biāo)位置對(duì)應(yīng)的格子按 "四舍五入"
                indexX = (int)System.Math.Round((hit.point.x - c0.transform.position.x) / cellSizeX, System.MidpointRounding.AwayFromZero);
                indexY = (int)System.Math.Round((hit.point.y - c0.transform.position.y) / cellSizeY, System.MidpointRounding.AwayFromZero);
                if (indexX >= 0 && indexX < sizeX && indexY >= 0 && indexY < sizeY) {
                    if (!isSet[indexX, indexY]) {
                        string trun = GameManager.curTurn;

                        // 發(fā)送消息通知對(duì)手吕粗,落子位置
                        {
                            Hashtable values = new Hashtable ();
                            values.Add ("name", "piece");
                            values.Add ("x", indexX);
                            values.Add ("y", indexY);
                            values.Add ("turn", trun);

                            GobangClient.send (GameSerialize.toBytes (values));
                        }

                        // 落子
                        setPiece(indexX, indexY, trun);

                        // 更新 GameManager chess
                        updateChess (indexX, indexY, trun);
                    } else {
                        Debug.Log("此格已經(jīng)下子兒了");
                    }
                }
            }
        }
    }

    // 落子
    public void setPiece(int x, int y, string turn) {
        GameObject sign = GameObject.Find ("Sign");
        if(sign != null)
            Destroy(sign);

        Vector3 v = c0.transform.position + new Vector3(x * cellSizeX, y * cellSizeY, 0);
        Instantiate(pieceHash[turn] as GameObject, v, Quaternion.identity);
        isSet[x, y] = true;

        // 標(biāo)記最后一次落子,紅點(diǎn)
        sign = Instantiate(pieceHash["Sign"] as GameObject, v, Quaternion.identity);
        sign.name = "Sign";

        // 落子后旭愧,切換回合
        if (turn == "Black")
            GameManager.curTurn = "White";

        if (turn == "White")
            GameManager.curTurn = "Black";
    }

    // 更新 chess
    private void updateChess(int x, int y, string turn) {
        GameObject gameObj = GameObject.Find ("Game");
        if (gameObj == null)
            return;

        // 渲染接收到的數(shù)據(jù)颅筋,落子
        GameManager gameManager = gameObj.GetComponent<GameManager> ();
        if (gameManager == null)
            return;

        gameManager.updateChess (x, y, turn);   
    }

接收數(shù)據(jù)處理部分可以看上文的 GameManager.cs 中 recvMessage 方法宙暇。

勝負(fù)同步

勝負(fù)邏輯判斷在本地計(jì)算,落子時(shí)檢查勝負(fù)結(jié)果议泵。
如果戰(zhàn)勝對(duì)手發(fā)送消息通知對(duì)手占贫。

GameManager.cs

    public void updateChess(int x, int y, string turn) {
        // 修正 chess 布子
        chess[x, y] = turn;

        // 檢查勝負(fù)
        if(checkChess(x, y, turn)) {
            Debug.Log ("游戲結(jié)束,獲勝方:" + turn);

            // game over
            isOver = true;
            // 結(jié)果通知給對(duì)方
            {
                Hashtable values = new Hashtable ();
                values.Add ("name", "lose");
                values.Add ("v", (byte) 1);

                GobangClient.send (GameSerialize.toBytes (values));
            }

            // UI
            GameObject ui = GameObject.Find("UI");

            GameObject resultObj = ui.transform.Find ("Result").gameObject;
            GameObject gameoverObj = resultObj.transform.Find ("GameOver").gameObject;
            gameoverObj.GetComponentInChildren<Text> () .text = "大獲全勝";

            resultObj.SetActive(true);
        }
    }

檢查勝負(fù)方法 checkChess

    // 遍歷檢查棋盤是否有勝負(fù)出現(xiàn)
    private bool checkChess(int x, int y, string turn) {
        int counta = 0; // 橫
        int countb = 0; // 豎
        int countc = 0; // 正斜
        int countd = 0; // 反斜

        // 橫先口,正方向
        for (int i = 1; i < 5; i++) {
            if (x + i >= sizeX) {
                break;
            }

            if (chess[i + x, y] == turn) {
                counta++;
            } else {
                break;
            }
        }
        // 橫型奥,負(fù)方向
        for (int i = 1; i < 5; i++) {
            if (x - i < 0) {
                break;
            }

            if (chess[x - i, y] == turn) {
                counta++;
            } else {
                break;
            }
        }
        if (counta >= 4) {
            return true;
        }
            
        // 豎,正方向
        for (int i = 1; i < 5; i++) {
            if (y + i >= sizeY) {
                break;
            }

            if (chess[x, i + y] == turn) {
                countb++;
            } else {
                break;
            }
        }
        // 豎碉京,負(fù)方向
        for (int i = 1; i < 5; i++) {
            if (y - i < 0) {
                break;
            }

            if (chess[x, y - i] == turn) {
                countb++;
            } else {
                break;
            }
        }
        if (countb >= 4) {
            return true;
        }

        // 正斜厢汹,正方向
        for (int i = 1; i < 5; i++) {
            if (x + i >= sizeX || y + i >= sizeY) {
                break;
            }

            if (chess[x + i, y + i] == turn) {
                countc++;
            } else {
                break;
            }
        }
        // 正斜,負(fù)方向
        for (int i = 1; i < 5; i++) {
            if (x - i  < 0 || y - i < 0) {
                break;
            }

            if (chess[x - i, y - i] == turn) {
                countc++;
            } else {
                break;
            }
        }
        if (countc >= 4) {
            return true;
        }

        // 反斜收夸,正方向
        for (int i = 1; i < 5; i++) {
            if (x + i >= sizeX || y - i < 0) {
                break;
            }

            if (chess[x + i, y - i] == turn) {
                countd++;
            } else {
                break;
            }
        }
        // 反斜坑匠,負(fù)方向
        for (int i = 1; i < 5; i++) {
            if (x - i < 0 || y + i >= sizeY) {
                break;
            }

            if (chess[x - i, y + i] == turn) {
                countd++;
            } else {
                break;
            }
        }
        if (countd >= 4) {
            return true;
        }

        return false;
    }

有什么疑問可以留言,一起探討卧惜。(本人QQ:836667502)厘灼。
源碼下載:
鏈接: https://pan.baidu.com/s/1bpB4PUB 密碼: nuk7

安卓.apk包下載:
鏈接:https://pan.baidu.com/s/1qXCPTRQ 密碼:xear

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咽瓷,隨后出現(xiàn)的幾起案子设凹,更是在濱河造成了極大的恐慌,老刑警劉巖茅姜,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪朱,死亡現(xiàn)場離奇詭異,居然都是意外死亡钻洒,警方通過查閱死者的電腦和手機(jī)奋姿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來素标,“玉大人称诗,你說我怎么就攤上這事⊥吩猓” “怎么了寓免?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長计维。 經(jīng)常有香客問我袜香,道長,這世上最難降的妖魔是什么鲫惶? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任蜈首,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疾就。我一直安慰自己澜术,他們只是感情好艺蝴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布猬腰。 她就那樣靜靜地躺著,像睡著了一般猜敢。 火紅的嫁衣襯著肌膚如雪姑荷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天缩擂,我揣著相機(jī)與錄音鼠冕,去河邊找鬼。 笑死胯盯,一個(gè)胖子當(dāng)著我的面吹牛懈费,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播博脑,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼憎乙,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了叉趣?” 一聲冷哼從身側(cè)響起泞边,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疗杉,沒想到半個(gè)月后阵谚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烟具,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年梢什,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朝聋。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嗡午,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玖翅,到底是詐尸還是另有隱情翼馆,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布金度,位于F島的核電站应媚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猜极。R本人自食惡果不足惜中姜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丢胚,春花似錦翩瓜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峡蟋,卻和暖如春坟桅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕊蝗。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工仅乓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蓬戚。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓夸楣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親子漩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豫喧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容