【Roblox/Lua】Roblox抽奖游戏设计概述
文章目录
- Roll功能
- RollPanelUI
- RollAnim
- RollManager
- PlayerBackpack
- BagUI
- 物品检视功能(检视,锁定,装备,删除)
- ItemUI
- InfoUI
- 自动抽奖
- 快速抽奖
- 快速移动
- 部分实现细节
- 新增
- 删除
- 背包面板
- 玩家初始化
- 代码总览
- 数据存储部分
- 服务器逻辑部分
- 客户端逻辑部分
- 算法部分
Roll功能
RollPanelUI
这是玩家的Roll相关UI类,负责管理按钮的事件和冷却,以及调用UI动画
- 按下RollBtn,调用ServerRPC事件C2S_Roll
- 播放RollAnim动画拖延时间
RollAnim
这是一个模块脚本,封装了其父级UI(RollAnimUI)的动画方法,对外暴露Play函数并可指定播放时长,自身有成员变量rollItem作为最后显示的Roll到的物品
- 通过Play函数触发,传入playTime
- 开启一个协程,随机抽取假物品,0.2秒一轮,直到达到playTime
- 最后将rollItem作为最后一次动画的物品进行播放
- rollItem依赖于外部的传入,因为客户端向服务端请求一个随机物品是有时间延迟的,因此客户端可以先进行抽奖动画的播放(playTime),服务端的抽取结果稍后存到rollAnim的rollItem字段供动画取用(一般小于playTime,如果不小于playTime,rollAnim就进行循环播放等待直到获取到结果)
RollManager
这个类是唯一的服务器脚本类,负责与封装的玩家存储服务DataStorageService进行交互,同时执行一些服务器逻辑
-
接收C2S_Roll的调用
-
服务端使用随机算法Roll一个物品出来(按权重随机算法,权重范围1~100w)
-
从服务器获取玩家当前数据(DataStorageService),将该物品增加到玩家数据,再存储回服务器(第一次拉取后,服务器会进行缓存,后续从缓存中取数据)
-
将当前物品改造为客户端需要的数据结构,然后调用ClientRPC将物品发送给指定客户端
PlayerBackpack
这是一个客户端缓存的背包对象,目的是加快对数据的操作,所有对背包数据的操作都由该模块脚本完成,该类操作完自身数据后对外进行客户端委托的广播和ServerRPC的调用
- 接收S2C_AddItem的调用
- 为客户端缓存背包副本增加物品
- 触发OnAddItem委托
BagUI
这是玩家的背包UI脚本,其通过OnAddItem和OnRemoveItem委托来管理自己的物品格子,内部存储了数组进行UI的管理,格子的生成依赖于其子级的第一个格子UI,即ItemTemplate对象
- 接收OnAddItem委托
- 将输入的包含item和islocked的数据进行拆解,并生成itemUI,加入到背包Grid中
物品检视功能(检视,锁定,装备,删除)
ItemUI
- ItemUI初始化时,为其赋予Index,方便其调用委托
- ItemUI本身是一个按钮,初始化时即绑定事件,触发OnItemSelected委托,传入自身缓存的Index
- 该类还封装了SetLock来快速改变格子外观,UpdateIndex负责在删除物品时由BagUI进行调用,更新缓存的Index
InfoUI
- 接收OnItemSelected委托
- 根据传入的index,在playerBackpack缓存的背包数据中抽取数据,并显示在详细面板上
- 更新锁定按钮和装备按钮的文字
- 细节面板锁定
- 直接触发playerBackpack封装的LockItem方法,该方法会先修改客户端对应物品数据,然后触发OnLockItem委托,最后再调用C2S_LockItem这个ServerRPC
- 细节面板删除
- 直接触发playerBackpack封装的RemoveItem方法,该方法会先删除客户端背包数据,然后触发OnRemoveItem委托,最后再调用C2S_RemoveItem这个ServerRPC
- 细节面板装备
- 直接触发playerBackpack封装的EquipItem或者UnEquipItem的方法,这两个会修改玩家数据中的EquipedIndex字段,该字段为nil代表不装备,为数字代表装备的是背包中对应索引的物品
- playerBackPack在修改完客户端EquipedIndex字段后,会触发OnEquipItem委托,然后调用C2S_EquipIten这个ServerRPC
- 玩家头上的UI在玩家进入时就已创建,因此服务端只需要根据传入的Index,到玩家数据中取对应的物品数据,即可修改玩家头上的UI(服务器修改保证这个操作会广播给所有玩家)
自动抽奖
- 自动抽奖会在Roll功能冷却完成后自动进行,因此适合通过一个委托进行,委托触发时机为按钮冷却完成
- 检测玩家是否开启AutoRoll功能,据此决定是否要进行下一轮自动抽奖
快速抽奖
在RollAnim和RollCooldownAnim开始播放时,进行一次判断,确定玩家是否启用了快速抽奖,接着将玩家等待的时间相应减少
快速移动
- 在玩家加入和玩家初次购买时进行检测,提高玩家的移动速度
- 为玩家所有能改变颜色的物体赋予红色
- 为玩家头部添加一个具有Weld约束的角模型
- 为玩家提供原本的3倍移动速度
部分实现细节
新增
- 细节面板:若从 0 ~ 1 则需要更新背包的细节面板,让细节面板自动的选择到第一个物品
删除
- 细节面板:删除后细节面板直接指向第一个物品,若删除后不再有物品则直接隐藏细节面板
- 装备物品:如果删除的物品在装备的物品之前,则需要更新装备的索引,如果删除的物品是装备的物品,则同时取消装备
- 批量删除:代码封装度不够高,因此部分特殊判断的逻辑需要在删除和批量删除的客户端,服务端都进行一次判断,如删除物品是否锁定,是否更新装备索引
背包面板
- 如果背包面板打开,则Roll动画播放完后应该立刻关闭,因为玩家在编辑自己的物品后,和刚抽完时的状态不再相同,抽奖面板默认作用于最后一个物品
玩家初始化
- 在服务器脚本中,负责监听玩家何时加入游戏,玩家加入时,通过数据库数据对玩家进行初始化
- 同时在玩家重生时,再次对玩家进行一次初始化
代码总览
数据存储部分
- 使用一个dataStoreService模块脚本封装对数据库的访问,通过缓存玩家数据来减少访问数据库的次数。外部请求时,会深拷贝一份再返回
- 存储物品的数据索引而非完整的物品本身,在复原时通过索引获取配置表中对应的物品,减少存储空间
- 将玩家的物品,设置,购买信息等放在一张表中进行集中存储
服务器逻辑部分
- 服务器逻辑基本是被动方式实现的,即依赖于客户端通过ServerRPC进行远程调用,服务端进行响应
- 服务端唯一的主动代码执行是为玩家加入游戏时对玩家进行数据库数据的复原
- 服务器负责包括Roll物品,验证玩家购买,以及对数据库进行增删改的操作(由客户端通过ServerRPC调用)
客户端逻辑部分
- 客户端大部分逻辑集中在模块脚本和UI当中,最重要的模块脚本是PlayerBackpack,该脚本存储了客户端的数据副本,但作用只是为了提高系统的反应速度,并不会将数据提交回服务器。
- PlayerBackpack中封装的方法会先操作客户端数据,再触发客户端委托,最后再提交ServerRPC,这一流程保证了客户端的响应速度。UI基本都调用PlayerBackpack的函数。S2C的ClientRPC也都是由PlayerBackpack订阅并响应。
- 客户端的冷却动画和Roll滚动动画的方法是封装在模块脚本当中的,通过Tween完成可控的动画效果。
- 玩家的装备词条功能会被执行两次,第一次是客户端立即执行,第二次是稍后的服务器将该操作广播给所有客户端执行(有延迟)。
算法部分
- 服务端的按权重进行抽取的概率方法,存储只存储每个条目的抽奖概率的分母部分
- 计算时会用表中概率最大的数去除以存储的其他条目的分母部分,这样就相当于颠倒了整个表的概率,同时还不会有太高的浮点精度要求
- 之后进行一次随机数的抽取,范围是[1, 所有分母总和],接着从翻转后的分母开始累加,如果和第一次超过随机到的数,则此时的条目就是随机到的条目