跳转至

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)

定义: 自动为玩家寻找对手的系统。

流程:

  1. 玩家调用 start_match() 进入匹配队列
  2. 系统根据 game_name 寻找正在匹配的对手
  3. 匹配成功后自动创建房间并加入
  4. 超时未匹配到对手触发 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)
                    └── 玩家通信

隔离原则:

  1. 游戏隔离: 不同 game_name 的设备无法互相发现和通信
  2. 排行榜隔离: 不同 score_cls 的积分独立计算排名
  3. 房间隔离: 不同房间的玩家无法直接通信(需通过房间 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单位毫秒
            是否必选
            注意事项默认30000ms30);
            参数示例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_idnicknamedevice_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:设备IDstring
                       - 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 扩展库。