開發3D遊戲聽起來門檻很高,可是Unity的出現讓門檻大大下降。開發聯網實時對戰的3D遊戲門檻就更高,由於即使熟悉掌握了Unity的開發技術,聯網的遊戲還要涉及到熟悉網絡協議棧、掌握後端知識以及面對服務器帶來的高額成本。可是Bmob最近在內測一款遊戲sdk,讓普通開發者開發一款聯網實時對戰遊戲這個夢想變得觸手可及。java
訪問 Unity 的 Asset Store下載遊戲項目,而且Import到 Unity 內。json
我選擇了一款可愛的射擊打怪遊戲:Survival Shooter Tutorial後端
項目導入後是這個樣子的:
api
下面簡要介紹項目結構:數組
這裏不說太詳細,想學習更多細節的童鞋能夠去看Unity官方的教程。服務器
將玩家角色(和角色控制器)克隆一份,去掉主動操做行爲,添加被動展示方法。微信
能夠在上一步驟中看到項目中只有一個玩家Player,要改形成聯網的遊戲就須要多個玩家,因此我把場景中的Player物體克隆一份,命名爲Player2,固然控制它的腳本也不能少,克隆克隆克隆!網絡
Player2Movement.cs:
刪掉根據鍵盤的操做引發的移動,加上傳入數據時的操做角色移動方法。ide
// 移動到相應座標 public void MoveTo(float x, float z){ playerRigidbody.MovePosition (new Vector3(x, playerRigidbody.position.y,z)); Animating (x, z); } // 旋轉到相應角度 public void TurnTo(float y){ transform.eulerAngles = new Vector3 (0, y, 0); }
刪掉根據鼠標的操做引發的射擊,加上傳入射擊指令時的方法,考慮到網絡延時緣由,是否射中怪物的判斷不禁這裏判斷,這個射擊方法給怪物的傷害要設置爲0,僅顯示出UI效果。學習
以上改好後再把對應的腳本替換掉原腳本放到Player2物體上,將物體拖進Project一欄中,這樣物體就變成預設物體,能夠隨時調用啦。除了克隆、修改玩家以外,還須要修改一些細節:
上面咱們有提到怪物是根據玩家的位置來自動尋路的,那如今有兩個玩家了怎麼辦呢?根據玩遊戲的經驗告訴咱們,怪物會跟着離他更近的玩家走喲。下面貼出代碼:
// UnityEngine.AI.NavMeshAgent nav; // nav = GetComponent <UnityEngine.AI.NavMeshAgent> (); void Update () { // If the enemy and the player have health left... if(enemyHealth.currentHealth > 0) { Vector3 enemyPosition = transform.position, tempPosition; float minDist = float.MaxValue, tempFloat; Vector3 target = Vector3.zero; for (int i = 0; i < targetHealths.Length; i++) { if (targetHealths [i].currentHealth > 0) { tempPosition = trackTargets [i].position; tempFloat = Vector3.Distance (enemyPosition, tempPosition); if (tempFloat < minDist) { minDist = tempFloat; target = tempPosition; } } } if (minDist != float.MaxValue) { // ... set the destination of the nav mesh agent to the player. nav.SetDestination (target); } } // Otherwise... else { // ... disable the nav mesh agent. nav.enabled = false; } }
上面提到,其餘玩家射擊到怪物的事件不能在我這裏減血,那麼怪物的血量怎麼控制呢,我把EnemyManager.cs的腳本改了下,把生成的每一個怪物都命名,當檢測到射擊時,把我傷害的怪物的名稱發送給其餘玩家,就能同步好每一個怪物的血量了。
修改SDK,將遊戲開始跳轉的 Scene 改成本遊戲的場景"_Complete-Game/_Complete-Game";
SceneManager.LoadSceneAsync ("_Complete-Game/_Complete-Game");
將處理事件轉發的腳本綁定給本地角色Player,將Player的移動、旋轉、hp等數據,調用SDK接口同步到服務器;
void Update () { BmobGame.UpdateFrame (); if (isOver) return; Vector3 position = transform.position; BmobGame.EditMyStatus ("position", new float[]{ position.x, position.z }); BmobGame.EditMyStatus ("rotation", transform.eulerAngles.y); BmobGame.EditMyStatus ("hp", GetComponent<PlayerHealthBase>().currentHealth<0?0:GetComponent<PlayerHealthBase>().currentHealth); }
將 Player 的瞬時動做射擊、射中的怪物名經過transfer接口直接發送到其餘玩家;
// Game_BmobSDKTest裏面SendFireEvent和SendDamageEvent方法, // 都是把傳來的參數轉成byte數組(數組第一位設爲事件類別), // 經過transfer接口傳遞數組給其餘玩家:BmobGame.SendTransferToAllExceptSelf (notify); // Game_BmobSDKTest mBGS; // mBGS = GetComponentInParent<Game_BmobSDKTest> (); // 把發射起點、角度、長度,用transfer接口傳 mBGS.SendFireEvent (transform.position.x, transform.position.z, transform.eulerAngles.y, range); // 把射中的怪物名發送出去,用transfer接口傳 mBGS.SendDamageEvent (shootHit.collider.name);
讀取服務器同步的數據,渲染其它玩家的位置、角度。獲取其它玩家直接發送的瞬時動做,做出射擊和射中某個怪物的處理;
//對收到其餘玩家信息的處理 void OnOthersGameStatus (int no, ArrayList attrNames, Hashtable status) { Debug.Log ("Player[" + no + "] game status is changed: " + status.Count); if(attrNames.Contains("position")){ float[] position = status ["position"] as float[]; mOtherPlayers [no].GetComponent<Player2Movement> ().MoveTo (position [0], position [1]) ; } if(attrNames.Contains("rotation")){ float y = (float)(status ["rotation"]); mOtherPlayers [no].GetComponent<Player2Movement> ().TurnTo (y) ; } if(attrNames.Contains("hp")){ int hp = (int)(status ["hp"]); mOtherPlayers [no].GetComponent<Player2Health> ().currentHealth = hp; } } //對收到transfer接口的信息的處理 void OnTransfer (int fromNo, byte[] data) { Debug.Log ("Get transfer data flag = " + data[0] + " & len = " + data.Length + " & from: " + fromNo); switch(data[0]){ case 1: ReceiveFireEvent (fromNo, data);//開火事件 break; case 2: ReceiveDamageEvent (fromNo, data);//擊中怪物事件 break; } Debug.Log ("Player[" + fromNo + "] transfer: " + data [0] + ", len = " + data.Length); } //對收到雲端通知的處理 void OnCloudNotifyJson(string jsonStr){ Debug.Log ("Handle cloud notify: " + jsonStr); JSONNode json = JSON.Parse (jsonStr); if (json == null) { return; } string a = json ["action"]; if (a == null || a.Length == 0) { return; } if ("gameover".Equals (a)) { // 遊戲結束,3秒後回到房間 Invoke ("BackToRoom", 3); } }
修改 玩家眷性配置,設置各個屬性的名稱、類型、長度、值域、由雲端/客戶端編輯、其它玩家是否可見等。我這裏僅有hp、position和rotation。
"player": { // 玩家的相關信息
"attributes": { // 玩家在遊戲內的屬性,下面的都是示例,實際狀況由開發者自定義 "hp": { // 玩家的HP "type": "int", // HP屬性類型爲數字 "max": 101 // HP的上限,int類型的屬性,均可以設置其max,設置得越緊密,運行效率越高 }, "position": { "type": "float[]", "count": 2, "editable": true, "export": true }, "rotation": { "type": "float", "editable": true, "export": true } }
}
10 . 打開 Eclipse 或 Android Studio,建立Java項目,導入 BmobGame_JavaCloud_vx.x.x_xxxxxx.jar,並建立 Player.java 和 Room.java,分別繼承自 PlayerBase.class 和 RoomBase.class 後,編寫遊戲邏輯代碼。
Player.java :
package cn.bmob.gamesdk.server.been; import cn.bmob.gamesdk.server.api.BmobGameSDKHook; import cn.bmob.gamesdk.server.api.JSON; import cn.bmob.gamesdk.server.api.PlayerBase; public class Player extends PlayerBase { @BmobGameSDKHook public native int getHp(); @BmobGameSDKHook public strictfp void onUpdate_Hp() { for (Player p : roommates) if (p.getHp() != 0) return; gameOver(); } private final void gameOver() { sendToAll(JSON.toJson("action", "gameover").toString().getBytes()); room.dispatchGameOver(); } }
Room.java :
package cn.bmob.gamesdk.server.been; import cn.bmob.gamesdk.server.api.RoomBase; public class Room extends RoomBase{ }
11 . 打包運行遊戲,就能夠多人同時在線對戰啦~
怎麼樣,會了嗎?
不服來戰,不懂來問!
若是你恰好有想法開發Unity/CocosCreator/微信小遊戲項目,並想要挑戰聯網版本;
若是你有必定Unity/CocosCreator/微信小遊戲開發經驗,且熱情度較高;
若是你是想入門遊戲開發、熱愛學習、時間充足、精力充沛的在校大學生;
來找我,加小小琪QQ:2967459363
給你提供免費試用的服務器,以及響應快速的技術支持!!!
(限人數,要儘快)