K/V存储
为一般普通的数据存储,其如同一张表,而表里面可以存任何类型的数据,并且可以存储多个不同关键字(Key)的表。
存储中的每个值(value)都由一个独特的键(key)索引,可以往里面添加任意值。一个key只能对应一个value,value类型可以是数值,可以是字符串,也可以是json字符串等。例如玩家相关数据可如下所示存取:
键 (KEY) |
值 (VALUE) |
---|---|
level | 50 |
attr | { "flowers": 100, "level": 6, "vip": 3,"played_count": 13, "label": "超神"} |
coin | 78000 |
通过相同的数据集名称和键(key),就可以针对数据进行存/取操作。
排行榜数据
为有序数据集合,其数据存储的类型为数值类型,系统会按照数值自动进行排序,故我们把它简称为排行榜数据,其可以存储多个维度的数据进行自动排序。
排行榜基本格式如下:
数据保存的有效期
注意:在联机或单机情况下,该功能只能实现当前地图,当前房间数据的临时存储,房间关闭后数据就会丢失。
验证数据存储的正确性,必须在云服环境下(确保在云服房间内)才可正确的获取到所存储的数据,在单机或者联机下将无法获取到云服下的全部数据。
如何开启K/V存储和排行榜
开发者可以在开发者工具->触发器->变量库->全局变量中,通过选择排行榜或者表来进行存储两种类型的数据。如图所示:
请求类型 | 函数 | 每分钟请求上限 | 备注 |
---|---|---|---|
获取 | getDataListByKey |
30 + numPlayers × 10 | 每分钟该函数允许被请求的次数,超过上限将会被限制请求 |
设置 |
removeDataListByKey
|
30 + numPlayers × 10 |
所有列出的函数共享此限制 每分钟这些函数被请求的次数之和不能超过上限,否则请求将会被限制 |
有序数据集(排行榜) | 5 + numPlayers × 2 |
所有列出的函数共享此限制 每分钟这些函数被请求的次数之和不能超过上限,否则请求将会被限制 |
组成 | 最大字符数 | 备注 |
---|---|---|
l键(key) | 50 | 键(key)建议控制在50字符以内 |
名称 | 50 | 名称建议控制在50字符以内 |
数据 | 2,000,000 | 数据建议控制在2,000,000字符以内 |
string.len()
检查它们的长度。数据也保存为数据存储中的字符串,不管其初始类型如何,我们都可以使用 Lua 数据转换为序列化 JSON 表的 JSONEncode()
函数检查数据的大小。Q:执行set操作,出现请求失败
A:1、避免出现服务器CD限制,相同key的set操作需要间隔>=6s,避免同一秒多次set相同key的情况,导致数据没有保存成功。
2、避免无变化的数据,仍不停去执行set操作。给需要保存的数据设置needSave之类的布尔值标签,数据变化了设置为true,定时保存中根据needSave为true时候发送set请求保存,否则没必要发送到服务器。
3、避免绑定玩家行走、碰撞等高频行为而触发实时读写操作,否则一个玩家有可能一秒内触发数百个数据读写请求。
4、避免触发每分钟请求数QPM限制。
5、妥善处理set或get返回失败的情况,可以创建请求失败列,合理安排时机重试几次,一旦成功就从失败列表移除。
Q:一个游戏可以设置多个排行榜
A:1、一个游戏可以包括多个维度的排行榜,如:得分排行榜,击杀Boss次数排行榜,速度排行榜,时长排行榜,技能点数排行榜等。
2、如果游戏里有多个排行榜,每个榜的刷新时间要错开。
Q:排行榜最多可以设置多少位排名?
A:1、单个排行榜最多可存储1万名。
2、建议排行榜展示名次最好不要超过TOP 30,最多展示前100名。展示的名次越多,参与排行的人数越多,越影响性能,导致响应变慢;设计的时候需要做合理的取舍。
Q:如何合理的进行set,get操作?
A:1、避免零值(或初始值)参与排行榜,会出现所有玩家都进行set操作了一次。
2、避免低于排行榜最后一名的数据也set操作。假设你的排行榜展示前50名,如果第50名已经有100分了,那么对于分数低于100分的玩家就无需添加到排行榜里面去。
3、参与排行的数值,比如经验,等级,杀怪数量等,这几个数值可以序列化成json字符串保存在一个kv中,避免多次拉取数据的情况。
4、假设某游戏有经验,等级,杀怪数3个排行榜,那么可以一个玩家一个json保存在kv表里面,key为玩家uin,value为json:
{"exp": 888999,"lvl": 7,"kmonster": 39}
5、区分游戏商店购买、任务、成就等模块的配置和需要持久化的状态数据。
Q:配置文件建议不设置到数据储存中
A: 通用配置可以放脚本或另外一个全局表里,没有必要存储在kv表里。
"FindMaHongJun_5":{"questName":"FindMaHongJun_5","questProg":0,"questProgAll":1,"questSta":"no"}
这种可以简化,假设TaskID:10086表示该任务,那么
"10086":[0,1,0]
数组第1个元素表示questProg,第2个元素表示questProgAll,第三个元素表示questSta。
--定义一个收集玩家的数组
local userAll ={}
local isOver = false
--用于记录当前进入的玩家迷你号
local current_enter_player_id = 0
--获取云服中存储的玩家用户信息回调
--ret 返回结果
--k 当前的迷你号
--v 当前的用户信息表中返回的内容,实例中的格式为: rank:2 score:120
local get_UserData_callback = function(ret,k,v)
if ret == true then
--拿到数据后,进行字符串分割
local myValue = v
print('当前获取到的用户信息' ..myValue )
local my_rank, my_score = string.match(myValue, 'rank:([0-9]+)score:([0-9]+)')
if(my_rank == 0 and my_score == 0 )then
print('用户信息表中活动到的数据得分为0,初始值不处理')
return
end
--当前玩家的迷你号
local objid=tonumber(k)
local actionattr=3 --变量类型:数值
--score为变量库中命名的得分,开发者可以根据自己在变量库中设定的内容,进行修改此处
local msg="score"
--设置私有数值“score”的值为my_score
local score_result = VarLib2:setPlayerVarByName(objid,actionattr,msg,my_score)
if(score_result == ErrorCode.OK)then
print('设置私有数值“score”的值为my_score 成功~~~~')
else
print('设置私有数值“score”的值为my_score 失败~~~~')
end
local rank_actionattr=4 --变量类型:字符串
--rank为变量库中声明的排行榜,开发者可以根据自己在变量库中设定的内容,进行修改此处变量名
local rank_msg="rank"
--设置私有数值“rank”为my_rank
local rank_result = VarLib2:setPlayerVarByName(objid,rank_actionattr,rank_msg,my_rank)
if (rank_result == ErrorCode.OK)then
print('设置私有数值“rank”为my_rank 成功~~~~')
else
print('设置私有数值“rank”为my_rank 失败~~~~')
end
else
print('返回用户信息数据失败')
end
end
--请求用户信息表中的数据
local getUserTableInfo = function()
--user_yang_001 为变量库中 创建的 用户信息表的脚本索引名称,开发者根据自己的设定进行修改
local libvarname = 'user_yang_001'
--将当前进入游戏的玩家迷你号作为请求条件
local str_player_id = tostring(current_enter_player_id)
--获取玩家的用户信息
local ret = CloudSever:getDataListByKey(libvarname,str_player_id,get_UserData_callback)
if ret == ErrorCode.OK then
print('game 中请求玩家用户数据成功')
else
print('game 请求求玩家用户数据失败')
end
end
--获取自己的排名情况返回函数
--ret 返回结果
--k 是迷你号
--v 是得分
--ix 是排名
local get_my_data_callback = function(ret,k,v,ix)
if ret == true then
--获取数据成功
local actionattr=3 --变量类型:数值
--score为变量库中命名的得分,类型为数值
local msg="score"--变量名
--获取到服务器端的“score”的值,赋值给本地私有变量score
local numb_player_id = tonumber(k)
local my_score = v
--设置当前玩家的私有变量:score得分数据,开发者可以根据自己的设定名称进行更改
local myscore_result = VarLib2:setPlayerVarByName(numb_player_id,actionattr,msg,my_score)
if myscore_result == ErrorCode.OK then
--设置成功
print('game 中 设置得分成功...')
else
--设置失败
print('game 中 设置得分失败...')
end
local actionattr_rank=4 --变量类型:字符串
--rank 为变量库中命名的排名,类型为字符
local msg_rank="rank" --变量名,可修改
--获取私有数值“rank”的值,为myrank
local myrank = ix
if ix == nil or ix == 0 then
myrank = "暂未上榜"
end
local myrank_sult = VarLib2:setPlayerVarByName(numb_player_id,actionattr_rank,msg_rank,myrank)
if myrank_sult == ErrorCode.OK then
--设置成功
print('设置排名成功...')
else
--设置失败
print('设置排名失败...')
end
else
--没有获取到信息,则从用户信息表中获取
getUserTableInfo()
end
end
--设置目前最新的本地数据到云端排行榜
--new_value: 当前得分
--pl_id: 当前玩家迷你号
local function setNewDataToCloud(new_value,pl_id)
local libvarname = 'rank_yang_001'
local player_id = tostring(pl_id)
local ret = CloudSever:setOrderDataBykey(libvarname,player_id,new_value)
if ret == ErrorCode.OK then
print('Game中 setNewDataToCloud设置排行榜值成功 玩家ID = ' .. player_id .. '当前得分: ' .. new_value)
else
print('Game中 setNewDataToCloud设置排行榜值失败')
end
end
--给用户信息表上传数据
--user_id 玩家迷你号
--user_rank 当前排行
--user_score 当前得分
local function setUserInfoDataCloud(user_id,user_rank, user_score)
--user_yang_001 为变量库中定义的用户信息表的 脚本引用名称,开发者可以根据自己的设定,修改此名称
local libvarname = 'user_yang_001'
--将数据组合成 rank:20 score:120 格式进行保存
local m_text = 'rank:'.. user_rank .. 'score:'.. user_score
local uid = tostring(user_id)
--调用接口,进行保存信息到云端的用户信息表中
local ret = CloudSever:setDataListBykey(libvarname,uid,m_text)
if ret == ErrorCode.OK then
print('setUserInfoDataCloud设置用户信息值成功 保存信息:' .. m_text)
else
print('设置用户信息值失败')
end
end
--离开和结束公共的设置玩家信息方法
--pl 为迷你号
local function setUserInfoAll(pl)
--如果传来的参数为空,则直接终止
if pl == nil then
return
end
local num_palyer_id = tonumber(pl)
local actionattr=3--变量类型:数值
--score为变量库中定义的得分,开发者可以根据自己的设定内容,进行修改此处变量名
local msg="score"
--获取私有数值“score”的值,赋值给value
print('Game中 setUserInfoAll中 玩家迷你号==' .. num_palyer_id )
local result,value = VarLib2:getPlayerVarByName(num_palyer_id,actionattr,msg)
if(result == ErrorCode.ok)then
print('Game中 setUserInfoAll中获取数据本地数据前,value==' .. value .. ' num_palyer_id==' .. num_palyer_id )
end
-- 获取私有排行榜
--rank为变量库定义的排行榜,开发者可以根据自己的设定内容,进行修改此处变量名
local msg2="rank"
local actionattr2=4 --变量类型:字符串
local result2,re_rank = VarLib2:getPlayerVarByName(num_palyer_id,actionattr2,msg2)
if (value ~= nil and re_rank ~= nil and value ~= 0 )then
--当前得分不是0,即不是初始值
--如果自己的本地数据比最后一名的得分都高,则将分数上传到排行榜服务器.
--如果分数小于最后一名的得分,则没有必要上传到服务器的排行榜,只需要在离开,或者游戏结束的时候将信息上传到用户信息中即可
setNewDataToCloud(value,pl)
local str_rank = re_rank
if(re_rank == "暂未上榜")then
str_rank = 0
end
--设置到用户信息表
if isOver == true then
--如果是离开游戏,或者游戏结束,则向服务器上传用户信息进行保存
setUserInfoDataCloud(pl,str_rank, value)
else
print('Game中 定时器中,不需要设置用户信息,设置了排行榜就可以了')
end
else
--这里就是初始化的时候,无需处理
print('Game中 获取本地私有变量数据都为空的时候,说明本地没有数据' )
end
end
--玩家进入游戏,获取排名,获取玩家信息,并设置本地数据rank,score
local function the_EnterGame_callback(e)
local pl = e.eventobjid
table.insert(userAll,pl)
--将当前进入玩家迷你号,临时存在变量中,后面回调函数中使用
current_enter_player_id = pl
--rank_yang_001 为设置的排行榜中脚本的引用名称,开发者可以根据自己的设定,进行修改此命名
local libvarname = 'rank_yang_001'
-- 请求当前进入玩家的排行榜数据
local my_ret = CloudSever:getOrderDataByKey(libvarname,pl,get_my_data_callback)
if my_ret == ErrorCode.OK then
print('请求自己的排行榜数据成功my_ret == ErrorCode.OK')
else
print("请求自己的排行榜数据失败")
--没有自己的排行榜,则需要去用户信息表中获取信息,进行展示
getUserTableInfo()
end
end
--{{{ 游戏开始后,时钟每(30ms)执行一次 ,向服务器排行榜进行上传玩家的数据
--[==[
--]==]
--}}}
local function dingShiSetDataForCloud(e)
local current = e.second
--定时30秒上传一次数据到云端
if(current ~= nil and current >=30 and (current- 30)%30==0) then
for kv, v in ipairs(userAll)do
setUserInfoAll(v)
end
end
end
--如果比赛结束,将玩家的信息保存到云端(排行榜和用户信息表)
local function thegame_end_callback()
isOver = true
for kv, v in pairs(userAll)do
setUserInfoAll(v)
end
end
local function the_TeaveGame_callback(e)
local pl = e.eventobjid
isOver = true
setUserInfoAll(pl)
--从本地数据中移除离开玩家的uin
table.remove(userAll,pl)
end
--向服务器提交数据
ScriptSupportEvent:registerEvent("Game.RunTime",dingShiSetDataForCloud)
--监听游戏结束的事件
ScriptSupportEvent:registerEvent([=[Game.End]=], thegame_end_callback)
--监听玩家离开事件
ScriptSupportEvent:registerEvent([=[Game.AnyPlayer.LeaveGame]=], the_TeaveGame_callback)
--监听玩家进入
ScriptSupportEvent:registerEvent([=[Game.AnyPlayer.EnterGame]=], the_EnterGame_callback)
--用于存放当前点击的玩家迷你号
local current_click_player_id = 0
--用于存放获取到的云端数据
local cloud_data = nil
--昵称文本控件ID
--开发者可以修改为自己的排行榜界面中第一、二、三...名昵称称控件ID
local txt_name_id = {
[1] = '7124235295204530136_13',
[2] = '7124235295204530136_16',
[3] = '7124235295204530136_17',
}
--得分文本控件ID
--开发者可以修改为自己的排行榜界面中第一、二、三...名得分控件ID
local txt_score_id = {
[1] = '7124235295204530136_14',
[2] = '7124235295204530136_18',
[3] = '7124235295204530136_19',
}
--统一设置第一名~第三名的数据展示
--开发者可以再此处设置自己的排行榜界面数据
local function setNewDataForUI()
if(cloud_data ~= nil)then
local v_value = cloud_data
local uiid="7124235295204530136"
--将当前点击的玩家迷你号,转换为数值类型
local nu_current_player_id = tonumber(current_click_player_id)
for ix, v in ipairs(v_value) do
local v_score = v.v
local current_name = v.nick --这里为获取玩家昵称,开发者可以直接通过v.nick获取到存储在排行榜中对应玩家的昵称
Customui:setText(nu_current_player_id,uiid,txt_score_id[ix], v_score)
Customui:setText(nu_current_player_id,uiid,txt_name_id[ix], current_name)
end
end
end
--请求排行榜返回的数据
local get_now_data_callback = function (ret,value) -- 返回结果不一定有排名
if ret ~= false and value ~= nil then
--设置排行榜中的界面数据
--将获取到的数据进行临时存储,以便后面使用
cloud_data = value
--测试
setNewDataForUI()
--获取到了数据,开始将数据显示到界面层
--setUIData()
--有了数据后,显示排行榜界面
local result = Player:openUIView(current_click_player_id,'7124235295204530136')
end
end
--设置玩家自己的数据,展示在界面
local function setMy_Data_InLocal()
--当前需要设置的界面ID,7124235295204530136为排行榜所显示的界面ID,开发者根据自己的界面ID进行修改此处
local uiid="7124235295204530136"
--7124235295204530136_21 为排行榜中排行的控件ID,开发者根据自己的控件ID修改此处
local element_rank_id='7124235295204530136_21'
--7124235295204530136_24 为排行榜中玩家的名称的控件ID,开发者根据自己的控件ID修改此处
local element_nick_id='7124235295204530136_24'
local numb_player_id = tonumber(current_click_player_id)
--获取变量值
local actionattr=3--变量类型:数值
----score为变量库中设置的得分名称,开发者可以根据自己的设置进行修改此处
local msg="score"
--获取私有数值“score”的值,赋值给value
local myscore_result,value = VarLib2:getPlayerVarByName(numb_player_id,actionattr,msg)
if (myscore_result == ErrorCode.OK) then
print('请求玩家的得分数据正确,得分 ==' .. value)
else
print('请求玩家的得分数据出错')
end
local rank_actionattr=4 --变量类型:字符串
--rank 为变量库中设置排行名称,开发者可以根据自己的设置进行修改此处
local rank_msg="rank"--变量名
--获取私有数值“rank”的值,赋值给value
local my_rank_result,my_rank_value = VarLib2:getPlayerVarByName(numb_player_id,rank_actionattr,rank_msg)
if(my_rank_result == ErrorCode.OK) then
print('请求玩家的排名数据正确 my_rank_value==' .. my_rank_value)
else
print('请求玩家的排名数据出错')
end
--开始设置私有数据到界面
--7124235295204530136_27为界面层 得分的控件ID,开发者可以根据自己的界面,设置控件ID
local element_score_id='7124235295204530136_27'
local set_score_result = Customui:setText(numb_player_id, uiid, element_score_id, value)
if set_score_result == ErrorCode.OK then
print('UI脚本中设置自己的得分成功score_value=' .. value)
else
print('UI脚本中设置自己的 得分失败 score_value=' .. value)
end
--设置名字
local name_result, my_name = Player:getNickname(numb_player_id)
local set_name_result = Customui:setText(numb_player_id,uiid,element_nick_id, my_name)
if(set_name_result == ErrorCode.OK)then
print('脚本中,设置自己的名称成功my_name ==' .. my_name)
else
print('脚本中,设置自己的名称成功')
end
--设置排名
if(my_rank_value == 0 or my_rank_value == nil) then
my_rank_value = "暂未上榜"
end
local rank_set_result = Customui:setText(numb_player_id,uiid,element_rank_id, my_rank_value)
if rank_set_result == ErrorCode.OK then
print('UI脚本中 自己的排行榜设置正常 rank_value .. ' .. my_rank_value)
else
print('UI脚本中 自己的排行榜设置失败 rank_value .. ' .. my_rank_value)
end
end
--奖杯点击事件
--开发者可以设置自己的排行榜按钮事件
local function jiangbei_click(e)
local pl = e.eventobjid
--是不是自己设置的排行榜按钮,如果不是就直接不处理
--7124254390629128152_1 为界面层上排行榜按钮,用于点击,开发者根据自己的排行榜按钮进行修改此处
if( e.btnelenemt ~= '7124254390629128152_1')then
return
end
--当前点击的玩家迷你号,用于回调的时候使用
current_click_player_id = pl
-- rank_yang_001 为变量库中设置的排行榜表的,脚本接口引用名称,开发者根据自己的设置,修改此处
local libvarname = 'rank_yang_001'
--玩家进入游戏,请求从大到小排序的前3名的数据;如果需要从小到大的排序,则将-3修改为3.
--开发者可以根据自己的设置的排行榜一共是多少名来设置该数字,直接取到前面多少名。
local ret = CloudSever:getOrderDataIndexArea(libvarname,-3,get_now_data_callback)
if ret == ErrorCode.OK then
print('请求排行榜数据成功ret == ErrorCode.OK')
else
print("请求排行榜数据失败")
end
--设置进入的玩家自己的信息
setMy_Data_InLocal()
end
--监听点击事件
ScriptSupportEvent:registerEvent([=[UI.Button.Click]=],jiangbei_click)