16 expvp-联网对战游戏
作者:王世豪 | 最后修改:2026-06-10
一、概述
expvp 是一个基于 LuatOS 框架开发的通用联网对战游戏扩展库,该库封装了 MQTT 网络通信、房间系统、匹配系统、游戏数据同步和积分排行榜等功能。
命名说明:
ex = extension(扩展),表示这是 LuatOS 的扩展库
pvp = Player versus Player(玩家对战),表示该库用于实现玩家之间的联网对战
二、业务解释
本节介绍 expvp 联网对战系统的核心概念、业务流程及各个名词的含义。
2.1 核心概念
游戏名称 (game_name)
定义: 用于区分不同游戏的唯一标识符。
作用:
- 作为 MQTT Topic 的前缀,实现不同游戏之间的数据隔离
- 相同
game_name的设备才能互相匹配和对战 - 排行榜数据按
game_name独立存储
示例:
"tank_battle"- 坦克大战"stick_fighter"- 火柴人格斗"racing_game"- 赛车游戏
积分分类 ID (score_cls)
定义: 用于区分同一游戏内不同模式/关卡的积分分类标识,取值范围 1-255。
作用:
- 同一游戏可以有多个独立的排行榜
- 不同分类的积分互不影响,独立排名
- 例如:简单模式、困难模式、无尽模式分别使用不同的
score_cls
如何确定填什么值:
| 场景 | score_cls 设置 | 说明 |
| 单模式游戏 | 1 | 游戏只有一种玩法,固定填 1 |
| 多难度模式 | 1, 2, 3... | 简单=1,普通=2,困难=3 |
| 多关卡游戏 | 1, 2, 3... | 每个关卡独立排行榜 |
| 多种玩法 | 1, 2, 3... | 竞速模式=1,生存模式=2 |
示例:
-- 坦克大战 - 简单模式
local config = {
game_name = "tank_battle",
score_cls = 1, -- 简单模式排行榜
}
-- 坦克大战 - 困难模式(另一个程序或切换配置)
local config = {
game_name = "tank_battle",
score_cls = 2, -- 困难模式排行榜(与简单模式分开排名)
}
设备 ID (device_id)
定义: 设备的 IMEI 或 MAC 地址。
作用:
- 标识每个参与对战的设备
- 作为 MQTT 客户端 ID 的一部分
- 用于点对点消息发送的目标地址
房间 (Room)
定义: 玩家进行对战的虚拟空间,每个房间有唯一的房间 ID。
组成:
- 房间 ID (room_id): 系统生成的唯一标识,如 "room_abc123",用于分享和加入
- 房主 (Host): 创建房间的玩家,拥有开始游戏的权限
- 玩家列表: 房间内所有玩家的信息
匹配系统 (Matchmaking)
定义: 自动为玩家寻找对手的系统。
流程:
- 玩家调用
start_match()进入匹配队列 - 系统根据
game_name寻找正在匹配的对手 - 匹配成功后自动创建房间并加入
- 超时未匹配到对手触发
match_timeout事件
MQTT 通信
定义: 基于发布/订阅模式的消息传输协议。
在 expvp 中的应用:
Topic: 消息频道,如 "tank_battle/room/abc123"
订阅 (Subscribe): 接收特定 Topic 的消息
发布 (Publish): 向特定 Topic 发送消息
QoS: 消息服务质量等级(0=最多一次,1=至少一次,2=恰好一次)
2.2 业务流程时序图
场景一:创建房间并邀请好友对战

场景二:游戏数据同步

场景三:积分上传与排行榜查询

2.3 关键名词对照表
| 名词 | 英文/代码 | 说明 |
| 游戏名称 | `game_name` | 区分不同游戏的标识,如 "tank_battle" |
| 积分分类 ID | `score_cls` | 1-255,区分同一游戏的不同模式/关卡排行榜 |
| 设备 ID | `device_id` | 设备唯一标识,设备的 IMEI 或 MAC |
| 设备型号 | `device_model` | 如 "Air780EPM",用于显示双方机型信息 |
| 玩家昵称 | `nickname` | 显示名称,优先使用 IOT 账号昵称 |
| 房间 ID | `room_id` | 房间唯一标识,如 "room_abc123",用于分享 |
| 房主 | `host` | 创建房间的玩家,有开始游戏权限 |
| 玩家编号 | `player_number` | 游戏内分配,如 1P、2P |
| MQTT Topic | - | 消息频道路径,如 "tank_battle/room/abc123" |
| QoS | - | 消息可靠性等级:0=最多一次,1=至少一次,2=恰好一次 |
| 本地积分 | `local_score` | 尚未上传到服务器的累计积分 |
| 服务器总分 | `total_score` | 服务器上该账号的历史总积分 |
| 排行榜 | `leaderboard` | 按积分排序的玩家排名列表 |
2.4 数据隔离规则
expvp 通过以下层级实现数据隔离:
游戏名称 (game_name)
└── 积分分类 (score_cls)
└── 房间 (room_id)
└── 玩家通信
隔离原则:
- 游戏隔离: 不同
game_name的设备无法互相发现和通信 - 排行榜隔离: 不同
score_cls的积分独立计算排名 - 房间隔离: 不同房间的玩家无法直接通信(需通过房间 Topic)
示例场景:
坦克大战 (tank_battle)
├── 简单模式 (score_cls=1) → 排行榜A
│ └── 房间A、房间B、房间C(互不相通)
├── 困难模式 (score_cls=2) → 排行榜B(与A独立)
│ └── 房间D、房间E
└── 无尽模式 (score_cls=3) → 排行榜C(与A、B独立)
└── 房间F、房间G
火柴人格斗 (stick_fighter) → 与坦克大战完全隔离
└── ...
三、核心示例
-- 加载扩展库
local expvp = require("expvp")
-- =============== 配置参数 ===============
local config = {
game_name = "tank_battle", -- 游戏名称,用于MQTT topic前缀
score_cls = 1, -- 积分分类ID,用于排行榜
}
-- =============== 事件回调函数 ===============
local function game_event_handler(event, payload)
if event == "connect" then
log.info("网络", "MQTT连接成功")
elseif event == "disconnect" then
log.info("网络", "MQTT断开连接")
elseif event == "peer_join" then
log.info("房间", "玩家加入:", payload.peer_id)
elseif event == "peer_leave" then
log.info("房间", "玩家离开:", payload.peer_id)
elseif event == "game_start" then
log.info("游戏", "游戏开始")
local my_number = payload.player_assignments[expvp.get_player_info().device_id]
log.info("游戏", "我是玩家", my_number)
elseif event == "game_data" then
log.info("游戏", "收到数据:", payload.data)
end
end
-- =============== 初始化示例 ===============
local function game_init()
log.info("main", "========== 初始化示例 ==========")
-- 1. 初始化网络框架
expvp.init({
game_name = config.game_name,
score_cls = config.score_cls,
})
-- 2. 注册事件回调
expvp.on(game_event_handler)
-- 3. 启动网络服务
expvp.start()
-- 4. 等待网络就绪
sys.waitUntil("expvp_ready", 10000)
-- 5. 获取玩家信息
local player_info = expvp.get_player_info()
log.info("main", "设备ID:", player_info.device_id)
log.info("main", "设备型号:", player_info.device_model)
log.info("main", "昵称:", player_info.nickname)
return true
end
-- =============== 房间系统示例 ===============
local function room_demo()
log.info("main", "========== 房间系统示例 ==========")
-- 创建房间(同步API,直接返回房间ID)
local room_id = expvp.create_room()
log.info("房间", "创建成功:", room_id)
-- 等待其他玩家加入
sys.wait(5000)
-- 获取房间信息
local room_info = expvp.get_room_info()
log.info("房间", "房间ID:", room_info.room_id)
log.info("房间", "是否房主:", room_info.is_host)
log.info("房间", "玩家列表:", room_info.players)
-- 设置准备状态
expvp.set_ready(true)
-- 如果是房主且所有人都准备了,开始游戏
if room_info.is_host and expvp.is_all_ready() then
expvp.start_game()
end
end
-- =============== 匹配系统示例 ===============
local function match_demo()
log.info("main", "========== 匹配系统示例 ==========")
-- 开始匹配
expvp.start_match()
log.info("匹配", "开始匹配...")
-- 匹配结果会在事件回调中处理
-- 匹配成功:触发 "match_found" 事件
-- 匹配超时:触发 "match_timeout" 事件
-- 等待匹配结果(最多30秒)
sys.wait(30000)
-- 如果还未匹配成功,取消匹配
if expvp.is_matching() then
expvp.stop_match()
log.info("匹配", "取消匹配")
end
end
-- =============== 游戏数据同步示例 ===============
local function game_sync_demo()
log.info("main", "========== 游戏数据同步示例 ==========")
-- 根据 game_start 事件中的 player_assignments 获取对手 device_id
-- 或直接使用 send_to_device 发送游戏数据
expvp.send_to_device("866987654321098", {
type = "move",
x = 100,
y = 200,
direction = "up"
})
end
-- =============== 积分系统示例 ===============
-- 上传积分回调函数
local function upload_score_callback(success, total_score)
if success then
log.info("积分", "上传成功,服务器总分:", total_score)
else
log.error("积分", "上传失败")
end
end
-- 查询排行榜回调函数
local function query_leaderboard_callback(success, data)
if success and data.records then
log.info("排行榜", "排名数据:")
for i, rec in ipairs(data.records) do
log.info("排行榜", i, rec.s1, rec.i1)
end
end
end
local function score_demo()
log.info("main", "========== 积分系统示例 ==========")
-- 游戏过程中增加积分
expvp.add_local_score(100)
expvp.add_local_score(50)
-- 获取当前积分
local current_score = expvp.get_local_score()
log.info("积分", "当前积分:", current_score)
-- 上传积分到服务器
expvp.upload_local_score(upload_score_callback)
-- 查询排行榜前10名
expvp.query_leaderboard(query_leaderboard_callback, 10)
end
-- =============== 主任务 ===============
local function main_task()
log.info("main", "====================================")
log.info("main", "expvp 联机对战示例启动")
log.info("main", "====================================")
-- 初始化
local result = game_init()
if not result then
log.error("main", "初始化失败")
return
end
-- 演示房间系统
room_demo()
-- 演示匹配系统
-- match_demo()
-- 演示游戏数据同步
-- game_sync_demo()
-- 演示积分系统
-- score_demo()
end
-- 启动主任务
sys.taskInit(main_task)
四、常量详解
expvp 扩展库暂无固定常量定义,所有配置项通过 expvp.init() 函数的参数传入。
五、函数详解
函数使用总体说明:
在使用 expvp 扩展库的 API 函数时,需要注意以下重要事项:
1、可在任意上下文使用的函数
所有函数不包含延时操作,可在任意上下文(包括回调函数)中调用:
- expvp.init() -- 初始化配置和设备信息
- expvp.start() -- 启动 MQTT 连接任务
- expvp.stop() -- 停止 MQTT 连接
- expvp.get_player_info() -- 仅读取本地状态
- expvp.get_network_status() -- 仅读取本地状态
- expvp.get_room_info() -- 仅读取本地状态
- expvp.is_all_ready() -- 仅读取本地状态
- expvp.is_matching() -- 仅读取本地状态
- expvp.get_local_score() -- 仅读取本地状态
- expvp.add_local_score() -- 仅修改本地状态
- expvp.send_to_device() -- 发送消息(非阻塞)
- expvp.send_presence() -- 发送在线状态(非阻塞)
- expvp.broadcast_to_room() -- 房间广播(非阻塞)
- expvp.join_room() -- 加入房间(非阻塞)
- expvp.leave_room() -- 离开房间(非阻塞)
- expvp.set_ready() -- 设置准备状态(非阻塞)
- expvp.start_game() -- 开始游戏(非阻塞)
- expvp.start_match() -- 开始匹配(非阻塞)
- expvp.stop_match() -- 停止匹配(非阻塞)
- expvp.upload_local_score() -- 上传积分(非阻塞)
- expvp.query_total_score() -- 查询总分(非阻塞)
- expvp.delete_score() -- 删除积分(非阻塞)
- expvp.query_leaderboard() -- 查询排行榜(非阻塞)
5.1 expvp.init(user_config)
功能:
初始化网络框架,配置游戏参数;
注意事项:
必须在其他所有 API 之前调用;
参数:
user_config
参数含义:配置参数表,用于配置expvp网络对战参数;
参数为table类型时,table内容格式说明如下:
{
参数含义:游戏名称,用于区分不同游戏;
数据类型:string;
取值范围:任意字符串;
是否必选:是;
注意事项:不同游戏之间数据隔离,无法互通;
参数示例:game_name = "tank_battle"
参数名称:user_config.game_name
参数含义:积分分类ID;
数据类型:number;
取值范围:1-255;
是否必选:是;
注意事项:不同分类ID的积分独立计算排行榜;
参数示例:score_cls = 1
参数名称:user_config.score_cls
参数含义:MQTT服务器地址;
数据类型:string;
取值范围:有效的MQTT服务器地址;
是否必选:否;
注意事项:默认使用合宙MQTT服务器"lbsmqtt.airm2m.com";
参数示例:mqtt_server = "lbsmqtt.airm2m.com"
参数名称:user_config.mqtt_server
参数含义:MQTT服务器端口;
数据类型:number;
取值范围:1-65535;
是否必选:否;
注意事项:默认1884;
参数示例:mqtt_port = 1884
参数名称:user_config.mqtt_port
参数含义:MQTT QoS等级;
数据类型:number;
取值范围:0/1/2;
是否必选:否;
注意事项:0=最多一次,1=至少一次,2=恰好一次;默认0;
参数示例:mqtt_qos = 0
参数名称:user_config.mqtt_qos
参数含义:房间最大人数;
数据类型:number;
取值范围:大于等于2;
是否必选:否;
注意事项:默认2人,即1v1对战;
参数示例:room_max_players = 2
参数名称:user_config.room_max_players
参数含义:匹配超时时间;
数据类型:number;
取值范围:5000-300000,单位:毫秒;
是否必选:否;
注意事项:默认30000ms(30秒);
参数示例:match_timeout = 30000
参数名称:user_config.match_timeout
}
数据类型:table;
取值范围:无;
是否必选:是;
注意事项:无;
参数示例:配置游戏名称和积分分类:
{
game_name = "tank_battle",
score_cls = 1
}
返回值:
local success = expvp.init(user_config)
success
含义说明:是否初始化成功;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示初始化成功,false表示初始化失败(如缺少必选参数);
返回示例:true
示例:
local success = expvp.init({
game_name = "tank_battle",
score_cls = 1,
})
if success then
log.info("main", "初始化成功")
else
log.error("main", "初始化失败")
end
5.2 expvp.on(callback)
功能:
注册事件回调函数,用于接收和处理 expvp 相关事件;
注意事项:
1、多次调用此函数会覆盖之前注册的回调函数;
参数:
callback
参数含义:事件回调函数;格式为:
function callback(event, payload)
-- 用户代码
end
该回调函数接收 event 和 payload 两个参数,参数说明如下:
参数含义:事件类型
数据类型:string;
取值范围:- "connect":MQTT 连接成功并订阅完成
- "disconnect":MQTT 连接断开
- "peer_join":有玩家加入房间
- "peer_leave":有玩家离开房间
- "peer_ready":玩家准备状态发生变化
- "join_rejected":加入房间被拒绝(如房间已满)
- "match_found":匹配系统找到对手
- "game_start":房主开始游戏
- "game_data":收到对手发送的游戏数据
- "presence":收到其他玩家的在线状态广播
- "message":收到自定义消息(结构由用户定义)
是否必选:必选;
参数名称:event
参数含义:事件数据
该参数为 table 类型,包含以下字段(不同事件返回的字段不同):
参数含义:设备ID,标识相关设备;
数据类型:string;
取值范围:任意字符串;
注意事项:"peer_join"、"peer_leave"、"peer_ready"、"match_found"、"presence" 事件会返回此字段;
参数示例:"866234567890123"(设备的唯一标识,IMEI 或 MAC 地址),在多人房间中,通过此字段区分不同玩家,例如判断消息是谁发送的;
参数名称:payload.device_id
参数含义:玩家昵称;
数据类型:string;
取值范围:任意字符串;
注意事项:"peer_join"、"match_found"、"presence" 事件会返回此字段;
参数示例:"张三",优先使用 IOT 账号昵称,否则使用设备 ID 后 6 位;可用于 UI 上显示对手名字、排行榜中的玩家名等;
参数名称:payload.nickname
参数含义:设备型号;
数据类型:string;
取值范围:任意字符串;
注意事项:"peer_join"、"match_found"、"presence" 事件会返回此字段;
参数示例:"Air8000W",表示对手所使用的硬件型号,可用于设备列表展示"谁在用哪种设备";
参数名称:payload.device_model
参数含义:是否房主;
数据类型:boolean;
取值范围:true/false;
注意事项:"peer_join" 事件中存在;
参数示例:true(表示刚加入的玩家是房主),只有房主才能调用 start_game 开始游戏;
参数名称:payload.is_host
参数含义:是否已准备;
数据类型:boolean;
取值范围:true/false;
注意事项:"peer_ready" 事件中存在;
参数示例:true(对手已点击准备按钮),当房间内所有玩家都准备就绪后,房主可以开始游戏;
参数名称:payload.ready
参数含义:拒绝原因;
数据类型:string;
取值范围:"房间已满"、"房间不存在" 等;
注意事项:"join_rejected" 事件中存在;
参数示例:"房间已满"(当前房间人数已达上限,无法再加入),用于向用户提示加入失败的具体原因;
参数名称:payload.reason
参数含义:玩家编号分配表,格式 {device_id = player_number};
数据类型:table;
注意事项:"game_start" 事件中存在;
参数示例:{["866234567890"] = 1, ["866234567899"] = 2},表示房主为 1 号玩家、对手为 2 号玩家;未传时自动按加入顺序分配,游戏代码根据编号决定谁先手、控制哪个角色等;
参数名称:payload.player_assignments
参数含义:房间ID;
数据类型:string;
注意事项:"game_start" 事件中存在;
参数示例:"room_560123",为创建房间时生成的唯一标识,可用于 debug 日志中追踪所属房间;
参数名称:payload.room_id
参数含义:房主设备ID;
数据类型:string;
注意事项:"game_start" 事件中存在;
参数示例:"866234567890123",标识发起游戏的房主,非房主玩家可通过与此字段对比判断自己是否是房主;
参数名称:payload.host_id
参数含义:收到的游戏数据,为对方发送的原始数据;
数据类型:string;
取值范围:任意字符串;
注意事项:"game_data" 事件中存在;
参数示例:'{"action":"move","x":100,"y":200}',对手把自己操作的数据通过 send_to_device 发送过来,游戏代码解析后同步到本地画面,例如让对手的飞机移动到坐标 (100, 200);
参数名称:payload.data
参数含义:发送方设备ID;
数据类型:string;
取值范围:任意字符串;
注意事项:"game_data" 事件中存在,标识数据的发送者;
参数示例:"866234567890123",表示这条 game_data 是哪个玩家发来的,在多人场景下可据此区分数据来源;
参数名称:payload.from
注意事项:"connect"、"disconnect" 事件的 payload 为空表 {};
"presence" 事件除上述字段外,还会包含用户调用 send_presence 时传入的自定义字段:
例如设备 A 调用(自定义字段数量和名称不限):expvp.send_presence({my_score = 1500, map_id = 3})
设备 B 收到"presence"事件,payload 结构为:
{
device_id = "866234567890123", -- 系统自动添加
nickname = "张三", -- 系统自动添加
device_model = "Air8000W", -- 系统自动添加
my_score = 1500, -- 用户自定义(当前积分)
map_id = 3, -- 用户自定义(想玩第3关)
}
系统字段和用户自定义字段合并在同一个 payload 中,直接读取即可;
自定义字段的典型用法:A 广播 {map_id = 3, score = 1500} 告诉其他人"我在第3关、积分1500",B 收到后在设备列表中展示,决定是否邀请 A 组队;
"message" 事件无固定字段结构,由用户自定义
数据类型:table;
是否必选:必选;
参数名称:payload
数据类型:function;
取值范围:暂无;
是否必选:必选;
参数示例:-- 1. 定义事件处理函数
function callback(event, payload)
-- 用户代码
end
-- 2. 注册事件回调函数
expvp.on(callback)
返回值:
local result1, result2 = expvp.on(callback)
result1
含义说明:回调事件注册结果
数据类型:boolean;
注意事项:成功返回true,失败返回false;
result2
含义说明:回调事件注册失败的错误信息;
数据类型:string|nil;
注意事项:成功返回nil,失败返回错误信息;
示例:
-- 定义事件处理函数
local function game_event_handler(event, payload)
log.info("expvp事件", "事件类型:", event)
if event == "connect" then
-- MQTT连接成功
log.info("expvp事件", "网络已连接")
elseif event == "disconnect" then
-- MQTT连接断开
log.info("expvp事件", "网络已断开,请检查网络")
-- 可以在这里尝试重连或提示用户
elseif event == "peer_join" then
-- 有玩家加入房间
log.info("expvp事件", "玩家加入:", payload.device_id, "昵称:", payload.nickname)
log.info("expvp事件", "设备型号:", payload.device_model, "是否房主:", payload.is_host)
-- 可以在这里更新UI显示玩家列表
elseif event == "peer_leave" then
-- 有玩家离开房间
log.info("expvp事件", "玩家离开:", payload.device_id)
-- 可以在这里更新UI,如果离开的是房主可能需要处理房间解散
elseif event == "peer_ready" then
-- 玩家准备状态发生变化
log.info("expvp事件", "玩家准备状态变化:", payload.device_id, "是否准备:", payload.ready)
-- 可以在这里检查是否所有玩家都已准备,如果是房主可以开始游戏
elseif event == "join_rejected" then
-- 加入房间被拒绝(如房间已满)
log.warn("expvp事件", "加入房间被拒绝:", payload.reason)
-- payload.reason 可能是 "房间已满"、"房间不存在" 等
elseif event == "match_found" then
-- 匹配系统找到对手
log.info("expvp事件", "匹配成功,对手:", payload.device_id)
log.info("expvp事件", "对手昵称:", payload.nickname, "设备型号:", payload.device_model)
-- 匹配成功后,系统会自动创建房间并加入,等待 game_start 事件
elseif event == "game_start" then
-- 房主开始游戏
local my_number = payload.player_assignments[expvp.get_player_info().device_id]
log.info("expvp事件", "游戏开始,我是玩家", my_number)
log.info("expvp事件", "房间ID:", payload.room_id, "房主ID:", payload.host_id)
-- 可以在这里初始化游戏状态,根据 player_number 分配阵营或位置
elseif event == "game_data" then
-- 收到对手发送的游戏数据
log.info("expvp事件", "收到数据:", payload.data)
log.info("expvp事件", "来自:", payload.from)
-- payload.data 是对方通过 expvp.send_to_device() 发送的原始数据
-- 根据数据结构解析并处理游戏逻辑
elseif event == "presence" then
-- 收到其他玩家的在线状态广播
log.info("expvp事件", "收到在线状态:", payload.device_id, "昵称:", payload.nickname)
log.info("expvp事件", "设备型号:", payload.device_model)
-- payload 还包含用户自定义字段,例如:
-- payload.is_matching = true -- 表示对方正在匹配中
-- payload.game_status = "waiting" -- 表示对方正在等待
-- 可以根据自定义字段更新UI显示
elseif event == "message" then
-- 收到自定义消息(结构由用户定义)
log.info("expvp事件", "收到自定义消息:", payload)
-- payload 的结构完全由用户定义,例如:
-- {type = "chat", message = "你好"}
-- {type = "invite", room_id = "abc123"}
-- 需要根据实际发送的数据结构进行解析
end
end
-- 注册事件回调
local success, err = expvp.on(game_event_handler)
if success then
log.info("main", "事件回调注册成功")
else
log.error("main", "事件回调注册失败:", err)
end
5.3 expvp.start()
功能:
启动网络服务,开始连接 MQTT 服务器;
注意事项:
必须在 expvp.init 之后调用;
参数:
无
返回值:
无
示例:
expvp.start()
5.4 expvp.stop()
功能:
停止网络服务,断开 MQTT 连接;
注意事项:
应用退出时调用此 API 释放资源;
参数:
无
返回值:
无
示例:
expvp.stop()
5.5 expvp.get_player_info()
功能:
获取玩家自己的信息;
注意事项:
无;
参数:
无
返回值:
local info = expvp.get_player_info()
info
含义说明:玩家信息或错误信息;
信息表结构(成功时):
{
参数含义:设备ID;
数据类型:string;
取值范围:IMEI或MAC地址;
注意事项:无;
参数名称:device_id
参数含义:设备型号;
数据类型:string;
取值范围:设备型号字符串;
注意事项:无;
参数名称:device_model
参数含义:玩家昵称;
数据类型:string;
取值范围:昵称字符串;
注意事项:优先使用IOT账号昵称,否则使用设备ID后6位;
参数名称:nickname
}
数据类型:table/string;
取值范围:信息表或错误信息;
注意事项:成功时返回信息表,失败时返回错误信息;
返回示例:-- 成功时举例
{
device_id = "860123456789012",
device_model = "Air780EPM",
nickname = "Player123"
}
-- 失败时举例
"获取玩家信息失败"
示例:
local info = expvp.get_player_info()
log.info("玩家", "ID:", info.device_id)
log.info("玩家", "型号:", info.device_model)
log.info("玩家", "昵称:", info.nickname)
5.6 expvp.get_network_status()
功能:
获取网络连接状态;
注意事项:
无;
参数:
无
返回值:
local status = expvp.get_network_status()
status
含义说明:网络状态或错误信息;
状态表结构(成功时):
{
参数含义:MQTT是否已连接并就绪;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示MQTT已连接并就绪,false表示未就绪;
参数名称:ready
参数含义:网络服务是否正在运行;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示网络服务正在运行,false表示未运行;
参数名称:running
}
数据类型:table/string;
取值范围:状态表或错误信息;
注意事项:成功时返回状态表,失败时返回错误信息;
返回示例:-- 成功时举例
{
ready = true,
running = true
}
-- 失败时举例
"获取网络状态失败"
示例:
local status = expvp.get_network_status()
if status.ready then
log.info("网络", "已就绪")
else
log.warn("网络", "未就绪")
end
5.7 expvp.send_to_device(device_id, data)
功能:
发送消息到指定设备(点对点);
注意事项:
需要 MQTT 已连接;
参数:
device_id
参数含义:目标设备ID;
数据类型:string;
取值范围:有效的设备ID字符串;
是否必选:是;
注意事项:目标设备的唯一标识符,设备的IMEI或MAC地址;对方收到后触发 "message" 事件;
参数示例:"861234567890123"
data
参数含义:要发送的数据;
数据类型:table;
取值范围:任意Lua表;
是否必选:是;
注意事项:数据表会被自动序列化为JSON格式发送,数据结构由开发者自行定义;
参数示例:-- 移动/位置同步
{type = "move", x = 100, y = 200}
-- 状态同步
{type = "sync", hp = 80, score = 1500}
-- 动作事件
{type = "action", action = "attack", target = "p2"}
-- 游戏事件
{type = "event", event = "game_over"}
返回值:
local success = expvp.send_to_device(device_id, data)
success
含义说明:是否发送成功;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示数据已成功发送到MQTT服务器,false表示发送失败;
返回示例:成功返回:true 失败返回:false
示例:
-- 发送移动数据
expvp.send_to_device("861234567890123", {
type = "move",
x = 100,
y = 200
})
-- 同步自身状态(血量、积分等)
expvp.send_to_device("861234567890123", {
type = "sync",
hp = 80,
score = 1500
})
5.8 expvp.send_presence(data)
功能:
广播在线状态(用于设备发现和邀请);
注意事项:
用于"设备列表"模式的联机;
参数:
data
参数含义:在线状态数据;
数据类型:table;
取值范围:任意Lua表;
是否必选:否;
注意事项:会被自动添加device_id、nickname、device_model三个系统字段,其余字段由开发者自由定义;
参数示例:-- 传入:
{is_matching = true}
-- 实际发送(自动添加系统字段后):
{
device_id = "861234567890123",
nickname = "张三",
device_model = "Air8000W",
is_matching = true -- 自定义:正在匹配中
}
-- 传入:
{score = 1500, map_id = 3}
-- 实际发送:
{
device_id = "861234567890123",
nickname = "张三",
device_model = "Air8000W",
score = 1500, -- 自定义:当前积分
map_id = 3 -- 自定义:所在关卡
}
返回值:
local success = expvp.send_presence(data)
success
含义说明:是否发送成功;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示数据已成功发送,false表示发送失败;
返回示例:成功返回:true 失败返回:false
示例:
local success = expvp.send_presence({is_matching = true})
if success then
log.info("发送在线状态成功")
else
log.error("发送在线状态失败")
end
5.9 expvp.broadcast_to_room(data)
功能:
广播消息到当前房间内所有人;
注意事项:
需要先加入房间;
参数:
data
参数含义:要广播的数据;
数据类型:table;
取值范围:任意Lua表;
是否必选:是;
注意事项:数据表会被自动序列化为JSON格式发送给房间内所有玩家;
参数示例:-- 聊天消息
{type = "chat", text = "hello"}
-- 发起再来一局
{type = "rematch", accept = true}
-- 自定义游戏事件
{type = "coin_collected", player = "p1", count = 5}
返回值:
local success = expvp.broadcast_to_room(data)
success
含义说明:是否发送成功;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示数据已成功发送到MQTT服务器,false表示发送失败或未加入房间;
返回示例:成功返回:true 失败返回:false
示例:
local success = expvp.broadcast_to_room({type = "chat", message = "hello"})
if success then
log.info("广播消息成功")
else
log.error("广播消息失败")
end
5.10 expvp.create_room()
功能:
创建新房间并成为房主;
注意事项:
创建后会自动订阅房间 topic 并广播创建事件;
参数:
无
返回值:
local room_id = expvp.create_room()
room_id
含义说明:房间ID;
数据类型:string;
取值范围:有效的房间ID字符串;
注意事项:创建成功返回房间ID,失败返回nil;
返回示例:成功返回:"room_abc123" 失败返回:nil
示例:
-- 创建房间
local room_id = expvp.create_room()
log.info("房间", "创建成功:", room_id)
-- 使用房间ID邀请好友
-- 可以通过UI显示 room_id 让好友加入
5.11 expvp.join_room(room_id)
功能:
加入指定房间;
注意事项:
加入后会自动订阅房间 topic 并发送加入请求;
参数:
room_id
参数含义:要加入的房间ID;
数据类型:string;
取值范围:有效的房间ID字符串;
是否必选:是;
注意事项:房间ID可通过以下方式获取:
1. 房主调用 create_room() 的返回值;
2. 非房主通过 presence 事件收到房主广播的 room_id;
3. 房主将房间代码(room_code)当面或者通过其他方式分享给好友;
参数示例:"room_abc123"
返回值:
无
示例:
-- 加入指定房间
expvp.join_room("room_123456")
log.info("房间", "已发送加入请求")
5.12 expvp.leave_room()
功能:
离开当前房间;
注意事项:
会通知其他玩家并清理房间状态;
参数:
无
返回值:
local success = expvp.leave_room()
success
含义说明:是否成功执行离开操作;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示本地状态已清理、离开通知已加入发送队列;
false表示未加入房间,无法离开;
返回示例:成功返回:true 失败返回:false
示例:
local success = expvp.leave_room()
if success then
log.info("房间", "离开成功")
else
log.warn("房间", "未加入房间")
end
5.13 expvp.get_room_info()
功能:
获取当前房间信息;
注意事项:
无;
参数:
无
返回值:
local info = expvp.get_room_info()
info
含义说明:房间信息或nil;
信息表结构(成功时):
{
参数含义:房间ID;
数据类型:string;
取值范围:有效的房间ID字符串;
注意事项:房间的唯一标识符;
参数名称:room_id
参数含义:是否为房主;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示当前玩家是房主,false表示不是;
参数名称:is_host
参数含义:玩家列表;
数据类型:table;
取值范围:以 device_id 为 key 的玩家信息表;
注意事项:包含房间内所有玩家的信息,每个玩家包含以下字段:
- device_id:设备ID(string)
- nickname:玩家昵称(string)
- device_model:设备型号(string)
- ready:是否准备(boolean)
- is_host:是否为房主(boolean);
参数名称:players
参数含义:自己是否准备;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示已准备,false表示未准备;
参数名称:my_ready
参数含义:游戏是否已开始;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示游戏已开始,false表示未开始;
参数名称:game_started
}
数据类型:table/nil;
取值范围:信息表或nil;
注意事项:未加入房间时返回nil;
返回示例:-- 成功时举例
{
room_id = "room_abc123",
is_host = true,
players = {
["866123456789012"] = {
device_id = "866123456789012",
nickname = "Player1",
device_model = "Air780EPM",
ready = false,
is_host = true
}
},
my_ready = false,
game_started = false
}
-- 未加入房间时
nil
示例:
local info = expvp.get_room_info()
log.info("房间", "ID:", info.room_id)
log.info("房间", "是否房主:", info.is_host)
5.14 expvp.set_ready(ready)
功能:
设置自己的准备状态;
注意事项:
所有玩家都准备后,房主调用 start_game 会触发所有玩家的回调函数,event 参数为"game_start";
参数:
ready
参数含义:是否准备;
数据类型:boolean;
取值范围:true/false;
是否必选:是;
注意事项:true表示准备就绪,false表示取消准备;
参数示例:true
返回值:
无
示例:
-- 设置准备状态
expvp.set_ready(true)
log.info("房间", "已设置准备状态")
5.15 expvp.is_all_ready()
功能:
检查房间内所有玩家是否都已准备;
注意事项:
房主调用 start_game 前用于检查条件;
参数:
无
返回值:
local ready = expvp.is_all_ready()
ready
含义说明:是否全部准备;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示房间内所有玩家都已准备,false表示自己未准备/有玩家未准备/房间内只有自己无其他玩家;
返回示例:全部准备返回:true 有玩家未准备返回:false
示例:
if expvp.is_all_ready() then
expvp.start_game()
end
5.16 expvp.start_game(player_assignments)
功能:
房主开始游戏,广播游戏开始通知给房间内所有玩家;
注意事项:
只有房主可以调用;会广播游戏开始通知,触发所有玩家在 expvp.on 中注册的回调函数的"game_start"事件;
参数:
player_assignments
参数含义:玩家编号分配表;
数据类型:table;
取值范围:{[device_id] = player_number} 格式的表;
是否必选:否;
注意事项:可选,自动分配时可不传;手动分配时用于指定每个玩家的编号;
参数示例:{["device_id_1"] = 1, ["device_id_2"] = 2}
返回值:
local success = expvp.start_game(player_assignments)
success
含义说明:是否成功;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示游戏开始命令已发送,false表示发送失败或非房主调用;
返回示例:成功返回:true 失败返回:false
示例:
-- 自动分配
expvp.start_game()
-- 手动分配
expvp.start_game({
["device_id_1"] = 1,
["device_id_2"] = 2
})
5.17 expvp.start_match()
功能:
开始自动匹配对手;
注意事项:
1、匹配超时时间通过 config.match_timeout 配置(默认 30 秒);
2、匹配结果通过事件回调通知:匹配成功触发"match_found"事件,超时触发"match_timeout"事件;
参数:
无
返回值:
无
示例:
-- 开始匹配
expvp.start_match()
log.info("匹配", "开始匹配...")
-- 在事件回调中处理匹配结果
expvp.on(function(event, payload)
if event == "match_found" then
log.info("匹配", "匹配成功,对手:", payload.device_id)
log.info("匹配", "对手昵称:", payload.nickname)
elseif event == "match_timeout" then
log.warn("匹配", "匹配超时")
end
end)
5.18 expvp.stop_match()
功能:
停止匹配(用户取消匹配时调用);
注意事项:
无;
参数:
无;
返回值:
无;
示例:
expvp.stop_match()
5.19 expvp.is_matching()
功能:
检查是否正在匹配中;
注意事项:
无;
参数:
无;
返回值:
local matching = expvp.is_matching()
matching
含义说明:是否正在匹配;
数据类型:boolean;
取值范围:true/false;
注意事项:true表示正在匹配中,false表示未在匹配;
返回示例:匹配中返回:true 未匹配返回:false
示例:
if expvp.is_matching() then
log.info("匹配", "匹配中...")
end
5.20 expvp.get_local_score()
功能:
获取本地累计积分(尚未上传到服务器的积分);
注意事项:
无;
参数:
无;
返回值:
local score = expvp.get_local_score()
score
含义说明:本地积分值;
数据类型:number;
取值范围:任意数值;
注意事项:返回当前本地累计的积分,尚未上传到服务器;
返回示例:100
示例:
local score = expvp.get_local_score()
log.info("积分", "当前:", score)
5.21 expvp.add_local_score(delta)
功能:
增加本地积分(游戏过程中获得积分时调用);
注意事项:
无;
参数:
delta
参数含义:要增加的积分值;
数据类型:number;
取值范围:任意数值;
是否必选:是;
注意事项:可以为负数,表示扣分;
参数示例:100
返回值:
local new_score = expvp.add_local_score(delta)
new_score
含义说明:增加后的总积分;
数据类型:number;
取值范围:任意数值;
注意事项:返回增加后的本地累计积分;
返回示例:150
示例:
local total = expvp.add_local_score(100)
log.info("积分", "获得100分,总计:", total)
5.22 expvp.upload_local_score(callback)
功能:
上传本地积分到服务器(自动累加服务器历史积分);
注意事项:
需要用户已登录 IOT 账号(非访客模式);本地积分为 0 时不会上传;上传成功后本地积分自动清零;
参数:
callback
参数含义:上传回调函数;格式为:
function callback(success, total_score)
-- 用户代码
end
该回调函数接收 success 和 total_score 两个参数,参数说明如下:
参数含义:是否上传成功
数据类型:boolean;
取值范围:true/false;
注意事项:true表示上传成功,false表示上传失败;
是否必选:必选;
参数名称:success
参数含义:服务器上的总积分
数据类型:number;
取值范围:非负整数;
注意事项:上传成功时返回服务器上的总积分,失败时为0;
是否必选:必选;
参数名称:total_score
注意事项:上传完成后异步调用此回调函数;
数据类型:function;
是否必选:否;
参数名称:callback
返回值:
无;
示例:
-- 1. 定义上传回调函数
local function upload_score_callback(success, total_score)
if success then
log.info("积分", "上传成功,总分:", total_score)
else
log.error("积分", "上传失败")
end
end
-- 2. 上传积分
expvp.upload_local_score(upload_score_callback)
5.23 expvp.query_total_score(callback)
功能:
查询当前账号在服务器上的总积分;
注意事项:
需要用户已登录 IOT 账号;
参数:
callback
参数含义:查询回调函数;格式为:
function callback(success, total_score)
-- 用户代码
end
该回调函数接收 success 和 total_score 两个参数,参数说明如下:
参数含义:是否查询成功
数据类型:boolean;
取值范围:true/false;
注意事项:true表示查询成功,false表示查询失败;
是否必选:必选;
参数名称:success
参数含义:服务器上的总积分
数据类型:number;
取值范围:非负整数;
注意事项:查询成功时返回服务器上的总积分,失败时为0;
是否必选:必选;
参数名称:total_score
注意事项:查询完成后异步调用此回调函数;
数据类型:function;
是否必选:是;
返回值:
无;
示例:
-- 1. 定义查询回调函数
local function query_total_score_callback(success, total_score)
if success then
log.info("积分", "服务器总分:", total_score)
end
end
-- 2. 查询总分
expvp.query_total_score(query_total_score_callback)
5.24 expvp.delete_score(callback)
功能:
删除当前账号在服务器上的积分记录;
注意事项:
需要用户已登录 IOT 账号;删除成功后会自动设置标记,下次上传不会叠加历史积分;
参数:
callback
参数含义:删除回调函数;格式为:
function callback(success, deleted_count)
-- 用户代码
end
该回调函数接收 success 和 deleted_count 两个参数,参数说明如下:
参数含义:是否删除成功
数据类型:boolean;
取值范围:true/false;
注意事项:true表示删除成功,false表示删除失败;
是否必选:必选;
参数名称:success
参数含义:删除的记录数
数据类型:number;
取值范围:非负整数;
注意事项:删除成功时返回删除的记录数,失败时为0;
是否必选:必选;
参数名称:deleted_count
注意事项:删除完成后异步调用此回调函数;
数据类型:function;
是否必选:否;
返回值:
无;
示例:
-- 1. 定义删除回调函数
local function delete_score_callback(success, deleted_count)
if success then
log.info("积分", "删除记录数:", deleted_count)
end
end
-- 2. 删除积分记录
expvp.delete_score(delete_score_callback)
5.25 expvp.query_leaderboard(callback, n)
功能:
查询排行榜(前 N 名,按积分降序);
注意事项:
API 只负责查询前 N 名数据,应用层拿到全部数据后自行分页;
参数:
callback
参数含义:查询回调函数;格式为:
function callback(success, data)
-- 用户代码
end
该回调函数接收 success 和 data 两个参数,参数说明如下:
参数含义:是否查询成功
数据类型:boolean;
取值范围:true/false;
注意事项:true表示查询成功,false表示查询失败;
是否必选:必选;
参数名称:success
参数含义:排行榜数据
数据类型:table;
取值范围:有效的数据表;
注意事项:查询成功时返回排行榜数据,包含以下字段:
- records: 记录数组 table(前N名),每条记录为 table,包含:
- i1:积分值(number)
- s1:玩家昵称(string)
- uni_key:账号标识(string)
- total:总记录数(number)
是否必选:必选;
参数名称:data
注意事项:查询完成后异步调用此回调函数;
数据类型:function;
是否必选:是;
n
参数含义:查询前N名;
数据类型:number;
取值范围:1-100;
是否必选:否;
默认值:30;
注意事项:默认前30名,最多查询前100名,应用层拿到数据后自行分页;
参数示例:30
返回值:
无;
示例:
-- 1. 定义查询回调函数
local function query_leaderboard_callback(success, data)
if success and data.records then
for i, rec in ipairs(data.records) do
log.info("排行榜", i, rec.s1, rec.i1)
end
end
end
-- 2. 查询前30名(默认)
expvp.query_leaderboard(query_leaderboard_callback)
-- 3. 查询前50名
expvp.query_leaderboard(query_leaderboard_callback, 50)
六、产品支持说明
支持 LuatOS 开发的所有产品都支持 expvp 扩展库。