开发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
给你提供免费试用的服务器,以及响应快速的技术支持!!!
(限人数,要尽快)