将Unity官方射击游戏(Space Shooter)改为实时对战小游戏,使用天梯实时对战服务(NanoLink)

  • io 类型游戏如此热门,有没有蠢蠢欲动?
  • 如何让自己的游戏快速实现可实时联机对战?
  • 开发实时对战游戏的时间成本?人力成本?

经朋友介绍了解到天梯的实时对战服务(NanoLink),NanoLink 基于 UDP 的低延迟可靠连接,确保发送端数据尽快到达接收端。

下面用 Unity官方实例 Space Shooter ,改成联机对战看看效果。

对战界面:

下面简单介绍下实现过程,有什么疑问大家可以一起探讨,欢迎莅临骚扰。


感谢

感谢天梯实时对战服务的商务小伙伴的热情帮忙及联机服务的支持。
具体接口情况还是联系天梯服务商吧(QQ:2803816871),不多介绍。


涉及同步数据

  • 玩家飞机位置同步
  • 掉落障碍物(石头,攻击性敌机)同步
  • 分值同步
  • 对战结束胜负同步
  • 存档回放(新功能)

基本 UI 修改

(先介绍一种数据同步 “玩家飞机位置同步“ 简要代码和逻辑吧,其它数据同步都是类似的,主要就是看游戏本身的数据同步点。大家有什么问题也可以留言,一起探讨,握手)

主要就是:
1,复制一架新的战机,分别命名为 Player-0, Player-1。这样命名也是为了跟 NanoLink 实时对战服务返回的玩家索引 getClientIndex 一致,方便控制当前玩家的战机。

2,添加 UI 对象,也就是 UI -> Canvas。
添加 4个 Button 入口,“等级匹配", “局域网匹配”, “房间号匹配”。这也是 NanoLink 实时对战服务 支持的 3中匹配连接方式。


玩家飞机位置同步

鼠标点击 Hierarchy 窗口的 Player-0 对象,可以看到 Space Shooter 战机控制脚本为 Done_PlayerController,下面重点看下这个脚本。( 简要代码 )

1,2个玩家匹配成功后,射击。
2,玩家控制战机的位置,发送数据到 NanoLink 协议

Done_PlayerController.cs

        void Update () {// if (Input.GetButton("Fire1") && Time.time > nextFire) // 连接上后自动开火if (MyClient.isConnected() && Time.time > nextFire) {nextFire = Time.time + fireRate;// Instantiate(shot, shotSpawn.position, shotSpawn.rotation);GameObject obj = GameObject.Instantiate(shot, shotSpawn.position, shotSpawn.rotation) as GameObject;obj.name = "Bolt-Player-" + playerIndex; // 当前飞机的idGetComponent<AudioSource>().Play ();}// XXX 原 FixedUpdateif(playerIndex != NanoClient.getInt ("client-index")) {return;}bool bMoved = false;// moveHorizontal = Input.GetAxis ("Horizontal");// moveVertical = Input.GetAxis ("Vertical");#region KeyboardEventsfloat deltaLR = 0;float deltaUD = 0;// 键盘操作,用于电脑上调试if (Input.GetKey (KeyCode.LeftArrow)) {deltaLR = -DELTA;} else if (Input.GetKey (KeyCode.DownArrow)) {deltaUD = -DELTA;} else if (Input.GetKey (KeyCode.RightArrow)) {deltaLR = DELTA;} else if (Input.GetKey (KeyCode.UpArrow)) {deltaUD = DELTA;}if(deltaLR != 0 || deltaUD != 0) {targetPosition = transform.position + new Vector3 (deltaLR, 0, deltaUD);bMoved = true;}#endregion#region TouchEventsif(Input.touchCount == 1) {Touch touch =Input.touches[0];if(touch.phase == TouchPhase.Moved) {Vector3 touchPosition = Camera.main.ScreenToWorldPoint (new Vector3 (touch.position.x, touch.position.y, 0));float diffTime = Time.realtimeSinceStartup - lastMove;float diffX = Mathf.Abs(touchPosition.x - targetPosition.x);float diffZ = Mathf.Abs(touchPosition.z - targetPosition.z);// 频率控制if(((diffX >= 0.25f || diffZ >= 0.25f) && diffTime >= 0.05f) || ((diffX > 0.1f || diffZ > 0.1) && diffTime > 0.1f)) {targetPosition = new Vector3 (touchPosition.x, transform.position.y, touchPosition.z);bMoved = true;}// moveHorizontal = Input.GetAxis("Mouse X");// moveVertical = Input.GetAxis("Mouse Y");}}#endregionif (bMoved) {lastMove = Time.realtimeSinceStartup;fireEvent ();}}void FixedUpdate () {transform.position = Vector3.LerpUnclamped (transform.position, targetPosition, Time.deltaTime * speed); // 10}public void fireEvent() {Hashtable values = new Hashtable ();values.Add ("name", "move");// 位置 positionvalues.Add ("x", targetPosition.x); values.Add ("z", targetPosition.z);MyClient.send (GameSerialize.toBytes(values));}

3,新版Nanolink SDK 优化,调整
新版本SDK 对 NanoClient.cs 进行了分装优化,开发者只需要实现自己的 MyClient.cs 继承 NanoClient。
然后,开发者只需要在代码中实现 已封装好的虚拟函数 (onMessage, onStatusChanged, onConnected, onDisconnected, onEvent )即可。
看了下新版本 SDK, 理论上只需要 实现 onMessage, onEvent两个函数即可,比上一个版本方便多啦。

MyClient.cs

using System;
using System.Collections;
using System.Collections.Generic;using UnityEngine;
using UnityEngine.SceneManagement;using Nanolink;// 游戏联网服务
public class MyClient : NanoClient {protected override void onMessage(byte[] data, byte fromIndex) {Hashtable values = GameSerialize.fromBytes (data);// 事件处理onEvent (values, fromIndex);}protected override void onStatusChanged(string newStatus, string oldStatus) {Debug.Log ("状态发生改变, newStatus:" + newStatus + "; oldStatus:"  + oldStatus);}protected override void onConnected() {Debug.Log ("连接成功, playerIndex:" + getInt("client-index") + "; serverId:" + getString("server-id"));// 连接上后,开火}protected override void onDisconnected(int error) {if (error == 0) {if (disconnectedBySelf)Debug.Log ("主动断开");elseDebug.Log ("对方断开");} else {// 错误代码具体参考 "Nanolink SDK 接口说明" 中 lastError 定义if (error == 501) {if (getInt ("last-time", -2) < 2000)Debug.Log ("超时断开, 可能是对方原因");elseDebug.Log ("超时断开, 可能是己方原因");}}// XXX 断开连接后自动保存 存档,用于 “回放上局”if(getInt("mode") != 0) {string archivesFilePath = "";string archivesFileName = "archives_file.dat";
#if UNITY_EDITORarchivesFilePath = Application.dataPath;
#elsearchivesFilePath = Application.persistentDataPath;
#endifsave (archivesFilePath + "/" + archivesFileName);}// 断开连接 重新reload 当前关卡SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);}protected override void onResync(byte fromIndex) {Debug.Log ("同步数据");GameObject gameObj = GameObject.Find ("Player-" + getInt ("client-index"));if(gameObj != null) {Done_PlayerController player = gameObj.GetComponent<Done_PlayerController> ();player.fireEvent ();}// 连接(或者 重新连接)后,发送随机函数数据// 主机发送if(fromIndex == 0) {Hashtable values = new Hashtable ();values.Add ("name", "seed");values.Add ("value", NanoRandom.Seed);send (GameSerialize.toBytes(values));}}void onEvent(Hashtable values, byte playerIndex) {string name = (string)values["name"];switch(name) {case "move":{// int playerIndex = (NanoClient.getClientIndex () + 1) % 2;GameObject gameObj = GameObject.Find ("Player-" + playerIndex);if (gameObj != null) {Done_PlayerController player = gameObj.GetComponent<Done_PlayerController> ();player.onEvent (values);}}break;case "die":{GameObject gameObj = GameObject.Find ("Player-" + values["player"]);if (gameObj != null) {Done_PlayerController player = gameObj.GetComponent<Done_PlayerController> ();player.onEvent (values);}}break;case "score":{GameObject gameControllerObj = GameObject.FindGameObjectWithTag ("GameController");if (gameControllerObj == null)return;Done_GameController gameController = gameControllerObj.GetComponent<Done_GameController> ();if (gameController == null)return;if(getInt("mode") == 0) {// 当前数据玩家索引 == clientIndex,为当前玩家的数据if((int)playerIndex == getInt("client-index")) {gameController.AddScore ((int)values["score"]);} else {gameController.AddScore2 ((int)values["score"]);}} else {gameController.AddScore2 ((int)values["score"]);}}break;case "seed":// 处理接收到的 同步随机数种子命令// 主要是  客机响应if(NanoClient.getInt ("client-index") == 1) {NanoRandom.Seed = (long)values["value"];}break;case "hazard":{GameObject gameControllerObj = GameObject.FindGameObjectWithTag ("GameController");if (gameControllerObj == null)return;Done_GameController gameController = gameControllerObj.GetComponent<Done_GameController> ();if (gameController == null)return;gameController.onEvent (values);}break;default:Debug.Log ("无效事件");break;}}

4,新功能:存档回放
新版本SDK 支持存档回放功能,很赞,联机对战存档文件每分钟才几K,才几K,对才几K 。。。
这样游戏实现存档回放简直太容易啦。下面分享的 Space Shooter 源码中也实现了 回放的功能。

最后,展示几张实际联机对战截图,把实际发送的基本数据和实际的延迟展示在左上角,可以留意下。我这边显示延迟有时会在10毫秒左右,很夸张。做到这点,简直 666


后续

身为技术宅的你,如果也对实时对战感兴趣的话,我们可以一起探讨交流(本人QQ:836667502),非程勿扰。

决定研究 NanoLink 的另一个重要的原因

NanoLink 实时对战服务 有完整的数据统计后台,可以观察游戏的 当前在线人数,匹配连接人次,流量,具体某个地区数据,而且支持全球区服匹配,地区数据支持更详细的次均时长,延迟分布,流量分布等等 一系列的数据指标。还是上图吧:


下面提供一个联机版本的Space Shooter 源码。 直接下载 .unitypackage 即可查看。

(注意:有开发者跟我说下载.unitypackage,运行不能联机,这是因为之前分享的包,我去掉了个人的appKey, 应要求重新分享一个新版本,直接运行就可以联机啦。)
(另外:编译到设备时,需要在 Unity 编辑器中配置 “File” - “Build Settings” - “Player Settings” - “Other Settings” - “Internet Access” 改为 Require;)

Unity 工程包:
链接: https://pan.baidu.com/s/1GGTi7lfy3izLSig64iVJtQ 提取码: ejnx

安卓.apk包下载(两台安卓设备直接对战即可):
链接: https://pan.baidu.com/s/1bpeuEjP 提取码: syq6

将Unity官方射击游戏 Space Shooter 改为实时对战小游戏,使用天梯实时对战服务(NanoLink)相关推荐

  1. Unity3D官方案例1-星际航行游戏Space Shooter

    Unity3D官方案例1-星际航行游戏Space Shooter [1]学习中的使用的类 1.Input:使用此类读取常规游戏设置中的轴,访问移动设备的多点触控和加速度. 本例使用到的方法: GetA ...

  2. Unity3D案例太空射击(Space Shooter)流程介绍与代码分析(上)

    最近开始接触游戏制作,用Unity制作一些简单的游戏进行入门.这几篇博客总结了Space Shooter的制作流程,并对代码进行了分析.一是方便自己日后进行回顾与补充,二是为了让让更多的游戏爱好者接触 ...

  3. Unity3D案例太空射击(Space Shooter)流程介绍与代码分析(下)

    这部分内容关注的是游戏性的提升,包括音效.计分等功能的实现. 传送门: 太空射击(Space Shooter)流程介绍与代码分析(上) 太空射击(Space Shooter)流程介绍与代码分析(中) ...

  4. Unity 游戏实例开发集合 之 Circus (马戏团) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 Circus (马戏团) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 Circus (马戏团) 休闲小游戏快速实现 一.简单介绍 二.Circus ( ...

  5. Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现 一.简单介绍 二.打砖块游戏内容与操作 三.游戏代码框架 四.知识点 ...

  6. Unity 游戏实例开发集合 之 FlyPin (见缝插针) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 FlyPin (见缝插针) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 FlyPin (见缝插针) 休闲小游戏快速实现 一.简单介绍 二.FlyPin ...

  7. 使用UE4制作简单的局域网对战小游戏

    原帖地址:https://arcecho.github.io/2017/04/28/使用UE4制作简单的局域网对战小游戏/ 大多数文章都是只讲到大致的UE4网络的概念,并未涉及实际使用.事实上在使用的 ...

  8. 微信小程序游戏为什么会频频爆火?和H5小游戏有什么区别?

    你的App里多久没有出现过一款新应用了?距离上一个现象级的应用抖音出现已经过去了6年.移动互联网已经相当成熟,近几年不管是游戏娱乐还是生活服务应用,很难出现爆款,反观小程序.轻应用小游戏却频频跑出黑马 ...

  9. 怎么开发联机小游戏_Q飞机游戏:空战吃鸡大乱斗游戏!好玩的联机Q飞机对战小游戏...

    20000+游戏爱好者已加入我们! 带你发现好游戏! <Q飞机>游戏小程序好玩吗? <Q飞机>小游戏怎么玩? 只有你想不到, 没有我找不到的好游戏! 「良心好游戏推荐」 搜罗了 ...

最新文章

  1. mysql proxies_priv_Mysql5.7.18利用MySQLproxies_priv实现类似用户组管理实例分享
  2. AI:大力出奇迹?Bigger is better?AI下一代浪潮?—人工智能的大语言模型(LLMs)的简介、发展以及未来趋势
  3. sqlserver常用函数/存储过程/数据库角色
  4. 利用老毛头启动盘重装win7
  5. 二叉搜索时与双向链表python_JZ26-二叉搜索树与双向链表
  6. 前端学习(1834):前端面试题之从url里面输入网址
  7. java对象前后改变_java对象改变而不设置它们
  8. 【云计算】Kubernetes、Marathon等框架需要解决什么样的问题?
  9. ETL的增量抽取机制
  10. [java]内部类的总结
  11. linux下检查是否安装过某软件包
  12. 872. Leaf-Similar Trees - LeetCode
  13. 设置tomcat 默认访问路径
  14. k2p 老毛子纯净版固件
  15. Python内存映射文件读写
  16. IE 主页被恶意篡改的解决方法
  17. 2021年焊工(初级)考试试卷及焊工(初级)复审考试
  18. 手机电视机屏幕分辨率
  19. SpringBoot集成Liquibase
  20. Telegram-This phone number is banned.手机号被禁止解决方法

热门文章

  1. java的ArrayList实现房屋出租系统
  2. python输入直角三角形的两个直角边的长度ab_输入直角三角形的两个直角边的长度a、b,求斜边c的长度。_学小易找答案...
  3. Verilog新手上路学生实验
  4. ET工业大脑 将大规模落地江苏
  5. 深度学习归一化 ——BN/FRN
  6. asp.net viewState
  7. uni-app 海康(HIKVISION)实时视频预览、录像回放、语音对讲
  8. 美丽说ipad ui 效果 模仿
  9. 《Graph Neural Networks Foundations,Frontiers and Applications》第一部分第一章第1.2.3节翻译和解读
  10. android 源码下载备忘