前幾天在 游戲蠻牛論壇 發(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