Nanolink 是基于 UDP 的实时联网对战服务。
Nanolink SDK支持"1对1"和"多人" 联网对战, 支持回放。
主要接口以 C/C++ 形式定义, C#, Lua, JS 版有相应语言接口实现。
int32_t init (const char* appKey, int mode=2);
确保在调用其它接口之前正确调用 init 接口。
init // 初始化
config // 参数配置,可选
connect // 连接指定设备
send, recv // 接收与发送
disconnect // 断开连接
1,登录 天梯实时对战服务 后台,在 游戏列表 界面,点击 “创建游戏”。如图:
2,输入游戏的相关信息后,点击 “提交”,即可。如图:
3,在 游戏列表,可以点击“编辑”某个游戏,查看唯一的 APPKEY,接入 SDK 时使用。如图:
下载 SDK 解压至本地,demos/*/sdk 目录下包含下面文件:
nanolink.h // Cocos-C++ 环境下加入 nanoclient.h nanoclient.cpp // Cocos-Lua 环境下加入 nanolink_lua.h nanolink_lua.cpp // Cocos-JS 环境下加入 nanolink_js.h nanolink_js.cpp
把 demos/*/sdk 目录中 nanolink.h 文件加入工程中。
Cocos-C++ 开发环境下,需要把 nanoclient.h, nanoclient.cpp 文件加入到工程中。
Cocos-Lua 开发环境下,需要把 nanolink_lua.h, nanolink_lua.cpp 文件加入到工程中。
Cocos-JS 开发环境下,需要把 nanolink_js.h, nanolink_js.cpp 文件加入到工程中。
在 Build Settings -> Search Paths 加入 libs/iOS 库路径。(Header Search Paths 和 Library Search Paths 具体配置的路径根据自身环境配置即可)
Header Search Paths 添加 $(SRCROOT)/../../nanolink/sdk
Library Search Paths 添加 $(SRCROOT)/../../nanolink/libs/iOS
Cocos-JS 环境下,Header Search Paths 还需要添加 $(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/manual
,支持 ScriptingCore.h
在 Build Phases -> Link Binary With Libraries 添加 nanolink 静态库。
使用 LLVM C++ Standard Library 或者 Complier Default 的开发者,可以直接使用 libnanolink.a。
由于开发者选用的 C++ 标准库可能有所不同,如果使用 GNU C++ Standard Library,可以联系客服咨询 (QQ: 2803816871)。
提示: 在 Build Settings 中搜索 C++ Standard Library,即可查看自己的设置。
在 Android 工程加载自己项目 .so 的地方,添加对 libnanolink.so 的引用。
static {
System.loadLibrary("nanolink");
System.loadLibrary("your_app_lib");
}
下载 SDK 解压至本地,demos/c++/sdk 目录下包含下面文件,将文件导入到工程中:
nanolink.h nanoclient.h nanoclient.cpp
/frameworks/runtime-src/proj.android/jni/Android.mk
LOCAL_PATH := $(call my-dir)
#——— 开始添加, 加载 libnanolink.so ———
#——— 确保加载的 .so 文件(路径) 正确加载 ———
include $(CLEAR_VARS)
LOCAL_MODULE:=libnanolink
LOCAL_SRC_FILES:=../../../nanolink/libs/android/$(TARGET_ARCH_ABI)/libnanolink.so
include $(PREBUILT_SHARED_LIBRARY)
#——— 结束添加 ———
include $(CLEAR_VARS)
...
#——— 开始添加, Cocos-C++ 开发环境加入以下接口文件 ———
LOCAL_SRC_FILES := hellolua/main.cpp \
../../Classes/AppDelegate.cpp \
../../../nanolink/sdk/nanoclient.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes \
$(LOCAL_PATH)/../../../nanolink/sdk
#——— 结束添加 ———
...
#——— 开始添加, 设置链接库 ———
#放在 BUILD_SHARED_LIBRARY 前
LOCAL_SHARED_LIBRARIES := libnanolink
#——— 结束添加 ———
include $(BUILD_SHARED_LIBRARY)
下载 SDK 解压至本地,demos/lua/sdk 目录下包含下面文件,将文件导入到工程中:
nanolink.h nanolink_lua.h nanolink_lua.cpp
LOCAL_PATH := $(call my-dir)
#——— 开始添加, 加载 libnanolink.so ———
#——— 确保加载的 .so 文件(路径) 正确加载 ———
include $(CLEAR_VARS)
LOCAL_MODULE:=libnanolink
LOCAL_SRC_FILES:=../../../nanolink/libs/android/$(TARGET_ARCH_ABI)/libnanolink.so
include $(PREBUILT_SHARED_LIBRARY)
#——— 结束添加 ———
include $(CLEAR_VARS)
...
ifeq ($(USE_ARM_MODE),1)
LOCAL_ARM_MODE := arm
endif
#——— 开始添加, Cocos-Lua 开发环境加入以下接口文件 ———
LOCAL_SRC_FILES := hellolua/main.cpp \
../../Classes/AppDelegate.cpp \
../../../nanolink/sdk/nanolink_lua.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes \
$(LOCAL_PATH)/../../../nanolink/sdk
#——— 结束添加 ———
...
#——— 开始添加, 设置链接库 ———
#放在 BUILD_SHARED_LIBRARY 前
LOCAL_SHARED_LIBRARIES := libnanolink
#——— 结束添加 ———
include $(BUILD_SHARED_LIBRARY)
在开始位置加上 #include "nanolink_lua.h"
。
在 register_all_packages()
后加入 nanolink_lua_init(L);
。其中 L 为当前 LuaEngine 的 lua_State。
下载 SDK 解压至本地,demos/js/sdk 目录下包含下面文件,将文件导入到工程中:
nanolink.h nanolink_js.h nanolink_js.cpp
LOCAL_PATH := $(call my-dir)
#——— 开始添加, 加载 libnanolink.so ———
#——— 确保加载的 .so 文件(路径) 正确加载 ———
include $(CLEAR_VARS)
LOCAL_MODULE:=libnanolink
LOCAL_SRC_FILES:=../../../nanolink/libs/android/$(TARGET_ARCH_ABI)/libnanolink.so
include $(PREBUILT_SHARED_LIBRARY)
#——— 结束添加 ———
include $(CLEAR_VARS)
...
ifeq ($(USE_ARM_MODE),1)
LOCAL_ARM_MODE := arm
endif
#——— 开始添加, Cocos-JS 开发环境加入以下接口文件 ———
LOCAL_SRC_FILES := hellojavascript/main.cpp \
../../Classes/AppDelegate.cpp \
../../../nanolink/sdk/nanolink_js.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes \
$(LOCAL_PATH)/../../../nanolink/sdk
#——— 结束添加 ———
...
#——— 开始添加, 设置链接库 ———
#放在 BUILD_SHARED_LIBRARY 前
LOCAL_SHARED_LIBRARIES := libnanolink
#——— 结束添加 ———
include $(BUILD_SHARED_LIBRARY)
在开始位置加上 #include "nanolink_js.h"
。
在 sc->start ()
前加入 sc->addRegisterCallback(nanolink_js::registerJSFunctions);
。
修改 Android.mk 文件 LOCAL_SRC_FILES
和 LOCAL_C_INCLUDES
时,根据自身环境添加相应的 C++, Lua,JS 接口即可。
int32_t init (const char* appKey, int mode=2);
appKey: 游戏的唯一标识,必须指定。在 天梯实时对战服务 后台创建游戏时自动生成,具体详见:获取 APPKEY
mode: 对战服务的连接方式,必须指定。定义如下:
mode=2: 1对1连接方式 (默认)
mode=3: 3人及以上连接方式
mode=0: 回放模式
mode=1: 单人录制模式
初始化 Nanolink SDK,确保在调用其它接口之前正确调用。
成功返回 0, 失败返回 -1 (mode无效)
void connect (uint8_t level, uint8_t mode = 0, int32_t timeout=15000);
level: 当前客户端的玩家等级(级别)。
mode: 等级匹配可以分为不同模式, 支持 0-15 模式, 缺省为0. 例如, mode=0, 代表 经典模式, mode=1, 代表冒险模式, ..
timeout: 匹配的等待时间(ms),超过时间取消匹配,缺省15000毫秒(15秒)。
指定等级匹配,可以指定匹配玩家的等级,等级会根据一定的宽容度匹配。
如果当前游戏没有玩家等级的概念,匹配完全随机的话,直接指定一个固定的等级即可,比如:8。
void connect2 (const char* group, int32_t timeout=15000);
group: 匹配的房间名称
如果 group 为空,通过局域网广播方式连接。
如果 group 不为空,通过服务器建立相同 group 之间用户的连接。
timeout: 匹配的等待时间(ms),超过时间取消匹配,缺省 15000 毫秒 (15 秒)。
局域网对战 或 创建房间号(或邀请好友)的对战方式。
group 名称可以重用,如果 group 在服务器不存在,创建并等待其它 玩家连接。
如果 group 首字母为 ‘+’,表示被邀请加入。此时如果 group 在服务器 不存在,立即返回连接失败,错误代码 404。
void disconnect ();
断开已经建立的连接 或 取消正在进行的匹配。
断开当前连接时,SDK 会统计本次连接的数据到统计后台。包括:连接时长,出入流量,发送的数据包数,延迟,发送成功率,重发率 等数据。所以离开本次连接或者离开当前对战场景时,尽量确保调用 disconnect。
int32_t send (const uint8_t* data, uint32_t size, uint8_t strength=255);
data: 待发送的数据 data
size: 发送数据的长度(size > 1)
strength: 发送广播强度, 仅在多人联网时有效
strength=0, 特殊值, 仅发送给主机 (is-master 获取的主机)
strength=255, 特殊值, 广播给所有空间的所有玩家
strength=254, 特殊值, 广播给相同空间的所有玩家
strength=1-253, 广播给相同空间的范围内玩家
发送指定长度 size 的 data 数据到目标设备。
成功: >= 0, 发送数据的索引值
失败:-1, 无法添加发送任务(发送队列满)
int32_t sendmark (uint8_t mark=0);
mark: 一个字节的 mark 标识, 当前可以忽略。
用在多人联网对战, 用于和 resync
指令配合使用
发送一个标记指令, 用于告知其他客户端该数据之后可以重构本客户端完整状态。
无法添加发送任务(发送队列满), 返回 -1。
多人对战时, 一个客户端数据接收遇到不可修复的问题时, 将发送 resync
指令请求重新同步完整数据。
其他客户端收到 resync
指令需要发送一个 mark
指令, 然后开始发送该客户端的数据。
int32_t recv (uint8_t* data, uint32_t size, uint8_t& from);
data: 接收数据缓冲区, 必须在外部分配。
size: 接收数据缓冲区的长度。
from: 用于获得数据发送者的 clientIndex。
接收新任务的数据。
返回接收数据长度。
返回接收数据长度:
-1: 没有接收到新数据。
0: resync 指令。
1: mark 指令。
>0: data 中返回收到的数据。
如果返回值大于参数 size, 表明 data 空间不够, 需要重新分配再次调用 recv。
通常接收数据长度小于 1024, 调用该接口的时候可以传入一个 1024 大小的 data, 如果返回值大于 1024 再根据size重新分配 data。
int32_t update (uint8_t x, uint8_t y, uint8_t z = 0, uint8_t k = 1);
更新客户端的位置(x,y), 空间(z), 接收增益(k), 用于配合 send 实现区域广播。
x, y 玩家的位置
z 玩家所在空间, 缺省为0, 0-255, 不同空间互相不会收到数据
k 接收增益, 接收范围的放大倍数
x, y 表示玩家的位置, 最大 256*256 的网格
z 表示空间, 不是 z 坐标, 例如, 不同的房间. 不同的空间互相不会收到数据, 除非全局广播
k 接收增益, 例如, 缺省接收增益是1, 玩家获得一个狙击枪, 接收增益扩大为10
成功返回 0
int32_t getInt (const char* name, int n = -1);
name:取值为 "mode", "latency", "last-time", "last-error", "client-index", "master-index", "is-master", "time-scale", "time-elapsed", "time-remaining"。
n: 用于配合 last-time 使用。
获得指定名称(name)的字符串类型参数值(value)。
name | 描述 |
---|---|
mode
|
获得 init 时传入的 mode 参数。 |
latency
|
获得当前客户端的网络延迟。 |
last-time
|
配合参数 n 使用: n >= 0: 获得指定玩家的最后指令时间; n = -1: 获得最后收到玩家(任意玩家)指令的时间; n = -2: 获得当前客户端最后收到服务器指令的时间; 可用于判断某个客户端是否堵塞, 为相对时间, n 毫秒之前。如果 last-time > 1000(ms), 通常意味着发生堵塞。 |
players
|
配合参数 n 使用: n = -1: 连接上房间的玩家数量 (有些玩家可能已经离开房间); n > 0: n 表示 timeout (毫秒), 扣除超时玩家, 当前还在线的玩家数量。 |
uptime
|
联网时间, 多人时表示房间创建到现在的时间 (毫秒)。 |
last-error
|
在 disconnect 时查询 last-error, 每次查询后 last-error 重置为 0。 错误码定义: 400 长度错误 401 版本不兼容 402 游戏不存在 403 服务器不存在 404 客户端不存在 405 战斗超时,超过设置的战斗时间, 会强行断开连接 409 无效指令 500 匹配超时(15 秒) 501 连接中断超时(15 秒) 502 链路闲置超时(300 秒) |
client-index
|
当前客户端在对战房间索引值, 0-64。 |
master-index
|
当前房间中主机的索引值, 0-64。 |
is-master
|
判断当前客户端是否是主机,主机可能根据网络延迟切换。(1为主机) |
time-scale
|
回放的时间系数, 用 getInt("time-scale") 的时候, 1.0 转换为 100。 |
time-elapsed
|
回放经过时间。 |
time-remaining
|
回放剩余时间。 |
int32_t getString (const char* name, char* data, uint32_t size); //C++接口 string getString (string name); // C#, Lua, JS接口
name:取值为 "client-id", "target-id", "server-id", "status", "stats"。
data: 用于接收参数值。
size: 接收参数值的缓冲区大小。
获得指定名称(name)的字符串类型参数值(value)。
name | 描述 |
---|---|
client-id
|
当前客户端的 id。 |
target-id
|
1 对 1 对战时,返回对方的 id; 多人对战时,返回房间 id。 |
server-id
|
分区服务器 id。 connect 过程中完成分区时可以获得 server-id, 缺省为0。分区服务器 id 可用于实现跨区邀请域或其他自定义匹配。 |
status
|
协议状态, 包括: init 初始化 matching 正在匹配 connecting 连接中 connected 已连接上 disconnect 断开连接 |
stats
|
获得协议的基本信息及统计数据。 |
void config (const char* name, const char* value);
配置协议的参数。
name | 描述 |
---|---|
client-id
|
客户端 id,16 进制,如果没有指定自动生成一个临时 id。 |
server-id
|
服务器 id, 16 进制,用于指定接入的服务器, 仅用在外部匹配或邀请匹配的时候。 |
version
|
游戏协议版本。 |
min-version
|
最小兼容版本,范围 0-255,局域网匹配时使用,缺省 0。 |
send-tasks-size
|
发送队列最大长度,范围 8-256,缺省 32。 |
timeout0
|
自动重连超时时间,超过指定时间没有回应重新连接。 |
timeout1
|
连接超时时间,超过指定时间没有连接上则断开,缺省 15000(15秒)。 |
timeout2
|
空闲断开时间,超过指定时间没有数据则断开,缺省 300000 (300秒)。 |
time-machine
|
是否记录网络传输数据,true 或 false,缺省为 false。 |
time-scale
|
回放的时间系数,读写,越大回放速度越快,缺省为 1.0。用 getInt ("time-scale") 的时候, 1.0 转换为 100。 |
time-elapsed
|
回放经过时间,读写,设置该值可以用于快进,不可倒退。 |
time-remaining
|
回放剩余时间,只读 |
void config (const char* name, const char* value);
配置房间的参数。
name | 描述 |
---|---|
room-atime
|
多人入场时间,缺省 60000(60秒)。 |
room-btime
|
多人最大战斗持续时间,缺省 360秒。 |
room-master
|
设置当前房间的主机索引号。 255 为根据延迟自动切换主机; 0-254 为指定的主机索引号; 缺省为0。 |
room-players
|
多人最大人数,范围 2-64,缺省 8。(0为解散房间) |
NanoClient 类定义了 onConnected, onDisconnected, onStatusChanged, onMessage, onResync, onPlayer 等事件处理函数接口, 开发者继承 NanoClient 类并实现以上接口可以快速实现实时对战。
连接建立完成, 可以开始进行数据收发。
onConnected ()
连接断开时,触发的事件。会返回连接断开时的错误码。
onDisconnected (int error)
error: 断开时获得的 lastError,通过 lastError 可以判断网络断开的原因。错误码说明
连接状态发生改变时触发。
onStatusChanged (string newStatus, string oldStatus)
newStatus: 最新状态。
oldStatus: 上一个状态。
接收到一个事件时的处理接口。
onMessage (byte[] data, byte fromIndex)
data: 接收到的数据缓冲区。
fromIndex: 数据发送者的索引号 clientIndex。
当某个客户端遇到不可修复的传输故障,向其他客户端请求重新发送当前最新数据。例如,某个玩家中途进入游戏,之前的数据没有接收到,SDK 会自动发送一个 resync 指令,向其他客户端请求数据。
通常在多人对战模式中使用,客户端通常在 onResync 中发送包括玩家昵称等初始信息。
onResync (byte fromIndex)
fromIndex: 出现丢包或者中途进入游戏的客户端的索引号 clientIndex。
玩家新加入房间"new", 离开房间"left", 重连回到房间"return"。
onPlayer (byte clientIndex, string e)
clientIndex: 当前事件的玩家索引号 clientIndex。
e: 事件, 包括: 玩家新加入("new"), 离开("left"), 回来("return"), "lag"
Nanolink 传输二进制数据, 开发者可以使用熟悉的序列化方式, 例如 Protobuf, JSON, XML, ....
在实时联网时, 数据收发频率高, 发送数据越小越好, 建议使用 protobuf 等二进制序列化工具, 也可以使用 Nanolink SDK 内置的 NanoBuffers 序列化工具。
NanoBuffers 是个简单的二进制序列化工具
压缩: 支持 Varint 压缩, 支持浮点数压缩
简洁: 直接通过链式 get, put 接口读写数据
方便: 直接使用, 不需要预先定义 schema再编译生成类代码
开源: 提供C++, C#, JS, Lua 等语言的实现, 可以扩展新的数据类型
// 1, 编码
NanoWriter nanoWriter = new NanoWriter ();
nanoWriter.putInt (123)
.putFloat(3.1415927f, 3)
.putString("Hello")
.put (new Vector3 (0.1f, 0.12f, 0.123f), 3);
myClient.send (nanoWriter.getBytes ());
// 2, 解码
NanoReader nanoReader = new NanoReader (buf); // buf 是接收到的数据
int v1;
float v2;
String v3;
Vector3 v4;
nanoReader.getInt (out v1).getFloat (out v2, 3).getString (out v3).get (out v4);
NanoWriter 与 NanoReader 的 get, put 必须完全对应。
在多人联网的时候, 服务器把每个客户端发送的数据直接广播(转发)给其他客户端, 当一个房间人数较多的时候, 完全广播的方式消息会导致流量消耗巨大。Nanolink 多人联网服务支持区域广播特性, 即客户端发送的数据可以仅发送给特定范围的部分客户端
如果房间人数在12人以下建议用全局广播模式, 编程相对比较简单。
如果房间人数在16人以上, 建议支持区域广播, 节约流量
NanoClient.update (x, y, z, k) 更新位置, 空间, 接收增益
x, y 是玩家的虚拟坐标, 是个256*256的网格, 通常由玩家在游戏中的实际坐标转换而来
空间(z) 和 接收增益(k) 用于实现高级的区域广播特性, 建议先用默认参数
NanoClient.send (data, strength) 其中 strength 指定"发送强度"
玩家的不同事件的影响范围不一样, 例如, 走路的强度是3, 跑步的强度是5, 枪声的强度是8, 手榴弹15...
send 的 strength 参数补充说明:
0: 特殊值, 仅发送给主机 (is-master 获取的主机)
255: 特殊值, 广播给所有空间的所有玩家
254: 特殊值, 广播给相同空间的所有玩家
1-253: 广播给相同空间的范围内玩家
// a 是发送者, b 是接收者
// strength 是a玩家发送数据的强度, 缺省为255, 全局广播模式
// k 是 b玩家的接收增益, 缺省为1
bool 判断b玩家是否在a玩家的发送范围内 (a, b, strength, k) {
int dx = abs (a.x - b.x);
int dy = abs (a.y - b.y);
if (dx <= strength*k && dy >= strength*k) {
// 在接收范围内
}
}
记录网络传输数据(录制)功能默认是关闭的,如果需要开启录制功能,需要 config("time-machine", "true") 开启。
int32_t save (uint8_t* data, int32_t size = -1);
保存协议收发等数据。
data: 保存数据缓冲区。
size: 保存数据缓冲区的长度
size = -1, data 指定的是文件路径 (data != NULL)。
size > 0, data 指定的是一块内存指针, 数据保存到该内存缓冲区中。
保存的数据长度,如果返回值大于参数 size, 表明 data 空间不够。
保存的数据包括收发数据和其他重要事件, 经过编码和压缩, 数据大小大约是客户端实际 发送流量+接收流量 的 1/4 - 1/3。
int32_t load (uint8_t* data, int32_t size = -1);
加载协议数据或保存协议数据,支持从文件或内存中加载/保存数据。
data: 加载数据缓冲区。
size: 加载数据缓冲区长度
size = -1: data 指定的是文件路径 (data != NULL)。
size > 0: data 指定的是一块内存指针, 数据将从该内存指针位置读取。
加载的数据长度。
如果返回值为-1, 表明加载失败。