前言

开发3D游戏听起来门槛很高,但是Unity的出现让门槛大大降低。开发联网实时对战的3D游戏门槛就更高,因为即便熟悉掌握了Unity的开发技术,联网的游戏还要涉及到熟悉网络协议栈、掌握后端知识以及面对服务器带来的高额成本。但是Bmob最近在内测一款游戏sdk,让普通开发者开发一款联网实时对战游戏这个梦想变得触手可及。


第一步,准备一个单机的Unity游戏

访问 Unity 的 Asset Store下载游戏项目,并且Import到 Unity 内。

我选择了一款可爱的射击打怪游戏:Survival Shooter Tutorial

项目导入后是这个样子的:

下面简要介绍项目结构:

  • 场景上的摆设物体都包括在Environment上,比如图上的闹钟、柜子等,它们的Layer都设置为了Shootable字段,代码在玩家开激光枪、激光碰撞检测时检测到Shootable为Layer的物体才会触发碰撞事件。
  • 怪物的生成由EnemyManage来控制,能够管理何种怪物在哪个出生点按什么时间间隔出生。
  • 怪物主要由三个脚本来管理,分别是EnemyHealth.cs(管理怪物的hp,被玩家射击到时扣血,血量小于等于0时死掉)、EnemyMovement.cs(管理怪物的移动,用UnityEngine.AI.NavMeshAgent,把玩家的坐标设为目的地,这样怪物会按设置的行走速度自动走向玩家位置)和EnemyAttack.cs(管理怪物的自动攻击,按一定的时间间隔进行发招)。
  • 玩家也主要由三个脚本来管理,分别是PlayerHealth.cs(管理玩家的hp,主要是收到怪物攻击时掉血)、PlayerMovement.cs(管理玩家的移动,当键盘按wsad时上下左右的移动)和PlayerShooting.cs(管理玩家的射击动作,当鼠标左键点击时对玩家枪口面对的方向发出激光Ray,如果在范围内碰撞检测到了怪物则对怪物进行扣血)。

这里不说太详细,想学习更多细节的童鞋可以去看Unity官方的教程。


第二步,开始改造

将玩家角色(和角色控制器)克隆一份,去掉主动操作行为,添加被动展现方法。

可以在上一步骤中看到项目中只有一个玩家Player,要改造成联网的游戏就需要多个玩家,所以我把场景中的Player物体克隆一份,命名为Player2,当然控制它的脚本也不能少,克隆克隆克隆!

  1. Player2Health.cs:
    因为Player2Health控制的是其他玩家的血量,所以把玩家收到怪物攻击时减的血量设为0,让其他玩家的血量不受本地控制。
  2. Player2Movement.cs:
    删掉根据键盘的操作引起的移动,加上传入数据时的操作角色移动方法。

    //  移动到相应坐标
    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);
    }
  3. Player2Shooting.cs:

删掉根据鼠标的操作引起的射击,加上传入射击指令时的方法,考虑到网络延时原因,是否射中怪物的判断不由这里判断,这个射击方法给怪物的伤害要设置为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的脚本改了下,把生成的每个怪物都命名,当检测到射击时,把我伤害的怪物的名称发送给其他玩家,就能同步好每个怪物的血量了。

第三步,结合Bmob Game Sdk

  1. 访问 BGS官网,注册账号并下载 Unity SDK、GameCloud SDK;
  2. 将 BmobGame_UnitySDK_vx.x.x_xxxxxx.unitypackage Import 到Unity内;
  3. 修改SDK,将游戏开始跳转的 Scene 改为本游戏的场景"_Complete-Game/_Complete-Game";

       SceneManager.LoadSceneAsync ("_Complete-Game/_Complete-Game");
  4. 在 Demo Scene 进行SDK的初始化,绑定 delegate 用于处理各种通知;
  5. 将处理事件转发的脚本绑定给本地角色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);}
  6. 将 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);
  7. 读取服务器同步的数据,渲染其它玩家的位置、角度。获取其它玩家直接发送的瞬时动作,作出射击和射中某个怪物的处理;

    //对收到其他玩家信息的处理
    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);}}
  8. 在 BGS官网 登录管理后台,创建游戏,修改服务器运行配置,包括:每秒帧率(默认60Hz)、房间最多玩家数 (2个或以上);
  9. 修改 玩家属性配置,设置各个属性的名称、类型、长度、值域、由云端/客户端编辑、其它玩家是否可见等。我这里仅有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 {@BmobGameSDKHookpublic native int getHp();@BmobGameSDKHookpublic 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 . 打包运行游戏,就可以多人同时在线对战啦~

运行效果

Demo测试运行视频 (B站无广告传送门)

怎么样,会了吗?
不服来战,不懂来问!

重要提示

如果你刚好有想法开发Unity/CocosCreator/微信小游戏项目,并想要挑战联网版本;
如果你有一定Unity/CocosCreator/微信小游戏开发经验,且热情度较高;
如果你是想入门游戏开发、热爱学习、时间充足、精力充沛的在校大学生;

来找我,加小小琪QQ:2967459363
给你提供免费试用的服务器,以及响应快速的技术支持!!!
(限人数,要尽快)

其他教程:

放大招!!!落地成盒?教你开发自己的联网"吃鸡"游戏
如何实现各种游戏的思路杂想

你们要的Unity联网对战游戏小Demo相关推荐

  1. Unity联网对战游戏小Demo

    前言 开发3D游戏听起来门槛很高,但是Unity的出现让门槛大大降低.开发联网实时对战的3D游戏门槛就更高,因为即便熟悉掌握了Unity的开发技术,联网的游戏还要涉及到熟悉网络协议栈.掌握后端知识以及 ...

  2. Unity制作2D战棋小游戏

    写在最前 这次想要做一个简单且传统的战棋小游戏,大概的玩法是:在2D世界里创建一张由六边形地块组成的战斗地图,敌我双方在地图上轮流行动,并向对方发动攻击,先消灭掉所有敌人的一方将获得胜利. 今天我们来 ...

  3. 联网对战游戏开发实例分享之《激流竞速》(附源码)

    Matchvs是一款游戏服务器引擎,<激流竞速>这款游戏是基于Cocos Creator进行前端开发的基础上,通过接入matchvs SDK完成了联网功能的快速实现.在游戏中,双方可以进行 ...

  4. “玩个球啊”,我开发的联网对战游戏

    项目预览地址: Moderate入口:zero2one.moderate.run/center/game[2] 独立入口:blog.moderate.run/[3] 我看到这个活动,我就兴奋了 首先我 ...

  5. 联网对战游戏开发实例之《斗兽棋》(附源码)

    本次,Matchvs为大家带来的是一款回合制休闲游戏的开源案例 .玩家双方在一个4X4的棋盘上,遵循食物链的规则玩法下进行翻牌与追逐,最终以场上存活的一方为获胜者. 体验地址:http://demo. ...

  6. 联网对战游戏开源实例分享之《斗兽棋》

    本次,Matchvs为大家带来的是一款回合制休闲游戏的开源案例 .玩家双方在一个4X4的棋盘上,遵循食物链的规则玩法下进行翻牌与追逐,最终以场上存活的一方为获胜者. 体验地址: http://demo ...

  7. Unity期末AI足球游戏小项目(免费开源)

    目录 游戏介绍 整体结构 部分截图 答辩论文截图 答辩问题 该游戏项目仅供参考,下载链接在文末.若需要答辩论文请私聊 版本:Unity 2018.4.36 游戏介绍 <Crazy Soccer& ...

  8. 个人笔记之面向对象--从游戏小DEMO方向了解继承

    面向对象之封装,继承,多态 1.封装 封装是面向对象的三大基本属性之一,简单理解一下就是把程序不想让别人看到的东西(比如变量,比如方法)包装隐藏起来. 这一块复杂的概念不是很多(常用private 和 ...

  9. 如何在完全不懂服务器开发的情况下做一个实时联网对战的微信小游戏

    微信小游戏即将开放?有我们在,你还赶得上! 根据微信官方对外公开的消息,微信小游戏的脚步越来越接近了.它的开发者资格门槛和使用者门槛都很低,以后必将引爆一波"全民开发小游戏"浪潮. ...

最新文章

  1. day23:shell基础介绍 alias及重定向
  2. 第一章 插件安装和代码导出
  3. 活着的理由,做事的风格
  4. java学习笔记(四)----对象、数组作为参数传递,静态变量、静态方法的使用,内部类,使用文档注释
  5. 删除链表的中间节点和a/b处的节点
  6. 第6周第4课:复习及扩展知识
  7. 如何用C#代码查找某个路径下是否包含某个文件
  8. 【303】C# 复制窗体 修改名称
  9. python3 生成器_python3基础之生成器
  10. 2017-2018-1 20155220 《信息安全系统设计基础》第十四周学习总结
  11. 利用libxml2解析xml文档
  12. runTime动态给类添加属性
  13. mysql 主从复制原理【转】
  14. 浅谈c++纯虚函数的多态与数据隐藏
  15. ubuntu下安装flash插件解决视频播放功能
  16. 约翰·冯·诺依曼及冯诺伊曼式计算机简介
  17. 办公协同:xmind8案例实战班-Array老师-专题视频课程
  18. 【剖析 | SOFARPC 框架】之SOFARPC 连接管理与心跳剖析
  19. 详细介绍 Yolov5 转 ONNX模型 + 使用ONNX Runtime 的 Python 部署(包含官方文档的介绍)
  20. 《可复制的领导力》脑图

热门文章

  1. python mayavi_python3怎么安装mayavi
  2. linux shell函数返回值问题
  3. 如何解决onclick与onmousedown,onmouseup的冲突,取消默认事件
  4. mysql 清空数据库所有表最简单的方法
  5. 计算机软件不仅仅是程序 还应该有一整套,长沙理工大学软件工程导论试卷
  6. 数据仓库【书籍推荐】
  7. 本贴为交换友情链接专用
  8. (php毕业设计)基于php校园食堂点餐管理系统源码
  9. 再见!COBOL 编程语言
  10. linux cp命令 强制覆盖,解决 Linux cp 命令加了 -f 后依然提示覆盖的问题