跑商玩法
你可以在PC编辑器中新建地图,选择Lua模板图中的跑商玩法,查看本示例的完整工程
游戏简介
玩家用已有的金币在商店中购买物品,再通过贸易中心售卖商品获得更多的金币。
技术要点
- Lua 定义TriggerAreaHandler类,用于处理商店的触发器
- Lua 定义UIHandler类,用于处理玩家界面UI显示
- Lua 定义ItemMangaer类,用于处理整个游戏的物品管理
- Lua 初始化商店并实现道具刷新和购买
- Lua 初始化贸易中心并实现更新公告和物品售卖
注:本示例可在编辑器模板图中下载。
技术要点分析
Lua 定义TriggerAreaHandler类,用于处理商店的触发器
lua
-- 构造函数:初始化触发区域处理器
-- @param triggerArea 触发区域
-- @param enterCallback 进入区域时的回调函数
-- @param leaveCallback 离开区域时的回调函数
function TriggerAreaHandler:ctor(triggerArea, enterCallback, leaveCallback)
-- 保存触发区域引用
self.triggerArea = triggerArea
-- 获取触发区域的单位ID
local triggerAreaUnitId = LuaAPI.get_unit_id(triggerArea)
-- 注册玩家进入触发区域的事件
LuaAPI.global_register_trigger_event(
{ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.ENTER, triggerAreaUnitId },
function(_, _, data)
-- 获取进入区域的角色
local character = data.event_unit
local role = character.get_role()
-- 调用进入回调函数,并将进入区域的角色信息传入
enterCallback(role)
end
)
-- 注册玩家离开触发区域的事件
LuaAPI.global_register_trigger_event(
{ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.LEAVE, triggerAreaUnitId },
function(_, _, data)
-- 获取离开区域的角色
local character = data.event_unit
local role = character.get_role()
-- 调用离开回调函数,并将离开区域的角色信息传入
leaveCallback(role)
end
)
end
注:代码位置:TriggerAreaHandler.lua
Lua 定义UIHandler类,处理玩家界面UI
- 定义UIHandler类,初始化玩家界面UI显示
lua
-- 构造函数
function UIHandler:ctor()
-- 初始化按钮处理器映射
self._role2BtnHandlers = {}
-- 遍历所有有效角色
for _, role in ipairs(GameAPI.get_all_valid_roles()) do
-- 隐藏商品操作按钮
role.set_node_visible(UINodes["商品操作按钮"], false)
-- 设置初始金币数量
role.set_kv_by_type(Enums.ValueType.Int, "money", 200)
-- 更新角色金币UI显示
self:updateRoleMoneyUI(role)
end
-- 注册商品操作按钮点击事件
LuaAPI.global_register_custom_event("点击商品操作按钮", function(_, _, data)
local role_id = data.role_id
local handler = self._role2BtnHandlers[role_id]
if handler then
handler()
end
end)
end
注:代码位置:UIHandler.lua
- 当购买或出售商品时,更新玩家金币显示
lua
-- 更新玩家金币显示
function UIHandler:updateRoleMoneyUI(role)
-- 获取角色当前金币数量
local money = role.get_kv_by_type(Enums.ValueType.Int, "money")
-- 更新金币数量显示
role.set_label_text(UINodes["金币数量"], tostring(money))
end
注:代码位置:UIHandler.lua
- 商品出售和购买操作按钮使用同一个UI控件,通过使用UIHandler类中的setItemOpHandler方法,将按钮文本和点击回调绑定到控件上
lua
-- 更新操作按钮文本和点击回调
function UIHandler:setItemOpHandler(role, text, handler)
local btn = UINodes["商品操作按钮"]
-- 根据文本是否为空来设置按钮可见性
role.set_node_visible(btn, text ~= "")
-- 设置按钮文本
role.set_button_text(btn, text)
-- 存储角色对应的按钮处理函数
self._role2BtnHandlers[role.get_roleid()] = handler
end
注:代码位置:UIHandler.lua
Lua 定义ItemMangaer类,用于处理整个游戏的物品管理
- 初始化商品管理器,读取ItemData数据,建立ID到数据的映射
lua
function ItemManager:ctor()
-- 初始化角色举起物品的映射表
self._char2lift = {}
-- 初始化物品ID到物品数据的映射表
self._id2ItemData = {}
-- 遍历所有物品数据,建立ID到数据的映射
for _, itemData in pairs(ItemData) do
self._id2ItemData[itemData.prefabID] = itemData
end
-- 获取所有有效角色
local allRoles = GameAPI.get_all_valid_roles()
-- 为每个角色注册事件
for _, role in ipairs(allRoles) do
local character = role.get_ctrl_unit()
self:registerEvents(character)
end
end
注:代码位置:ItemManager.lua
- 注册事件,当角色举起物品时,记录该物品信息;当角色放下物品时,销毁该物品
lua
function ItemManager:registerEvents(character)
-- 注册角色举起物品的事件
LuaAPI.unit_register_trigger_event(character, { EVENT.SPEC_LIFEENTITY_LIFT_BEGAN }, function(_, _, data)
local itemObj = data.lifted_unit
-- 禁用物品的物理效果
itemObj.set_physics_active(false)
-- 记录角色举起的物品信息
self._char2lift[character.get_role_id()] =
{ itemObj = itemObj.get_bound_equipment(), itemData = self._id2ItemData[itemObj.get_key()] }
end)
-- 注册角色放下物品的事件
LuaAPI.unit_register_trigger_event(character, { EVENT.SPEC_LIFEENTITY_LIFT_ENDED }, function(_, _, data)
local unit = data.lift_unit
local itemObj = data.lifted_unit
-- 重新启用物品的物理效果
itemObj.set_physics_active(true)
-- 计算放下物品的位置
local direction = unit.get_orientation():apply(math.Vector3(0, 0, 1))
itemObj.set_position(unit.get_position() + direction)
-- 清除角色举起物品的记录
self._char2lift[character.get_role_id()] = nil
end)
end
注:代码位置:ItemManager.lua
- 通过传入itemKey和目标位置旋转,创建商品
lua
-- 创建商品
function ItemManager:createItem(itemKey, targetPos, targetRot)
local itemData = ItemData[itemKey]
local prefabId = itemData.prefabID
-- 在指定位置创建物品
local itemObj = GameAPI.create_equipment(prefabId, targetPos)
local unit = itemObj.get_unit()
-- 设置物品朝向
unit.set_orientation(targetRot or math.Quaternion(0, 0, 0))
local item = { name = itemKey, itemData = itemData, itemObj = itemObj }
-- 设置物品可被举起
unit.set_lifted_enabled(true)
-- 禁用物品交互
unit.set_interact_enabled(false)
-- 启用自定义投掷力度
unit.set_custom_thrown_force_enabled(true)
-- 设置投掷力度为0
unit.set_custom_thrown_force(math.toreal(0))
return item
end
注:代码位置:ItemManager.lua
- 销毁商品
lua
-- 销毁商品
function ItemManager:destroyItem(item)
item.itemObj.destroy_equipment()
end
注:代码位置:ItemManager.lua
- 购买商品,检查金钱是否足够,扣除金钱,更新UI显示,创建商品
lua
-- 购买商品
function ItemManager:buyItem(role, item)
-- 获取角色当前金钱
local money = role.get_kv_by_type(Enums.ValueType.Int, "money")
local buyPrice = item.itemData.buyPrice
-- 检查金钱是否足够
if money < buyPrice then
role.show_tips("金币不足")
return false
end
-- 扣除金钱
money = money - buyPrice
role.set_kv_by_type(Enums.ValueType.Int, "money", money)
-- 更新UI显示
G.uiHandler:updateRoleMoneyUI(role)
-- 创建并给予商品
local character = role.get_ctrl_unit()
self:createItem(item.name, character.get_position())
return true
end
注:代码位置:ItemManager.lua
- 出售商品,扣除金钱,更新UI显示,销毁商品
lua
-- 出售商品
function ItemManager:sellItem(role, item, sellPrice)
-- 获取角色当前金钱
local money = role.get_kv_by_type(Enums.ValueType.Int, "money")
-- 增加金钱
money = money + sellPrice
role.set_kv_by_type(Enums.ValueType.Int, "money", money)
-- 更新UI显示
G.uiHandler:updateRoleMoneyUI(role)
-- 销毁商品
self:destroyItem(item)
end
注:代码位置:ItemManager.lua
- 获取指定角色当前举起的商品信息
lua
-- 获取指定角色当前举起的商品信息
function ItemManager:getRoleLiftedItem(role)
return self._char2lift[role.get_roleid()]
end
注:代码位置:ItemManager.lua
Lua 初始化商店并实现道具刷新和购买
- 初始化商店,创建触发器处理器,并注册进入和离开出售区域的处理器
lua
for i = 1, 2 do
-- 查询商品桌子单位
local desk = LuaAPI.query_unit("商品桌子" .. i)
-- 随机添加商品到桌子上
self:randomAddItem(desk)
-- 获取商品触发器
local triggerArea = desk.get_child_by_name("商品触发器")
-- 创建触发器处理器
TriggerAreaHandler.new(triggerArea, function(role)
-- 进入了出售区域, 显示玩家购买按钮
G.uiHandler:setItemOpHandler(role, "购买", function()
local deskUnitId = LuaAPI.get_unit_id(desk)
local item = self.deskItems[deskUnitId]
-- 尝试购买商品
-- 购买商品
if item and G.itemManager:buyItem(role, item) then
-- 购买成功后,进入刷新状态
self:enterRefreshState(desk)
-- 销毁已购买的商品
G.itemManager:destroyItem(item)
-- 从桌子上移除商品
self.deskItems[deskUnitId] = nil
end
end)
end, function(role)
-- 角色离开出售区域时,移除购买按钮
G.uiHandler:setItemOpHandler(role, "", nil)
end)
end
注:代码位置:BuyShop.lua
- 实现道具刷新,并添加到桌子上
lua
function BuyShop:enterRefreshState(deskObj)
-- 获取商品公告牌
local billboard = deskObj.get_child_by_name("商品公告牌")
-- 获取桌子的单位ID
local unitId = LuaAPI.get_unit_id(deskObj)
-- 定义更新倒计时的函数
local function updateCountDown()
local second = self.shopCountDowns[unitId].countDown
second = second - 1
self.shopCountDowns[unitId].countDown = second
-- 更新公告牌显示的倒计时
billboard.set_billboard_content("刷新倒计时#n #n" .. second)
-- 倒计时结束时,随机添加新商品并取消定时器
if second < 1 then
self:randomAddItem(deskObj)
LuaAPI.global_unregister_trigger_event(self.shopCountDowns[unitId].timerId)
end
end
-- 注册定时器,每秒更新一次倒计时
local timerId = LuaAPI.global_register_trigger_event({ EVENT.REPEAT_TIMEOUT, 1.0 }, function()
updateCountDown()
end)
-- 初始化倒计时数据
self.shopCountDowns[unitId] = { timerId = timerId, countDown = 4 }
-- 立即更新一次倒计时
updateCountDown()
end
注:代码位置:BuyShop.lua
- 随机添加道具
lua
-- 根据概率随机选择一个道具上架出售
function BuyShop:randomAddItem(deskObj)
local items = {}
local totalWeight = 0
-- 遍历所有商品数据,准备随机选择
for itemKey, itemData in pairs(ItemData) do
-- 将商品数据添加到items表中
table.insert(items, itemKey)
totalWeight = totalWeight + itemData.probability
end
-- 生成随机数
local randomValue = LuaAPI.rand() * totalWeight
-- 选择项目
local currentWeight = 0
for _, itemKey in ipairs(items) do
currentWeight = currentWeight + ItemData[itemKey].probability
if randomValue <= currentWeight then
-- 将选中的商品绑定到指定的桌子上
self:bindShopItem(itemKey, deskObj)
return
end
end
end
注:代码位置:BuyShop.lua
- 绑定商品到桌子上
lua
-- 在指定位置创建商品道具
function BuyShop:bindShopItem(itemKey, deskObj)
local itemData = ItemData[itemKey]
-- 计算商品的目标位置(在桌子上方2个单位)
local targetPos = deskObj.get_position() + math.Vector3(0, 2, 0)
-- 获取商品的旋转(如果没有指定,则使用默认值)
local targetRot = itemData.rot or math.Quaternion(0, 0, 0)
-- 创建商品
local item = G.itemManager:createItem(itemKey, targetPos, targetRot)
-- 获取桌子的单位ID
local deskUnitId = LuaAPI.get_unit_id(deskObj)
-- 将商品与桌子关联
self.deskItems[deskUnitId] = item
-- 设置商品不可被抓举和物理交互
local unit = item.itemObj.get_unit()
unit.set_lifted_enabled(false)
unit.set_physics_active(false)
-- 更新商品公告牌的内容
local billboard = deskObj.get_child_by_name("商品公告牌")
billboard.set_billboard_content(itemKey .. "#n #n$" .. itemData.buyPrice)
return item
end
注:代码位置:BuyShop.lua
Lua 初始化贸易中心并实现更新公告和物品售卖
- 初始化贸易中心,并注册进入和离开出售区域的处理器
lua
-- 添加商店的交互区
local triggerArea = LuaAPI.query_unit("出售区域触发器")
TriggerAreaHandler.new(triggerArea, function(role)
-- 进入了出售区域, 显示玩家购买按钮
G.uiHandler:setItemOpHandler(role, "出售", function()
-- 按钮的事件
self:onSellItem(role)
end)
end, function(role)
-- 角色离开出售区域时,移除购买按钮
G.uiHandler:setItemOpHandler(role, "", nil)
end)
注:代码位置:TradeCenter.lua
- 实现公告更新
lua
-- 注册一个定时器,实现定时刷新时间和出售倍率信息
LuaAPI.global_register_trigger_event({ EVENT.REPEAT_TIMEOUT, 1 }, function()
self:refreshBillBoard()
end)
注:代码位置:TradeCenter.lua
lua
-- 更新出售商店公告, 时间, 折扣信息
function TradeShop:refreshBillBoard()
-- 更新当前时间。这里使用分钟作为单位,每次调用方法时增加1分钟,并在达到24小时(1440分钟)时循环。
self._curTime = (self._curTime + 1) % (24 * 60)
-- 计算当前小时。使用整除运算 // 将分钟转换为小时。
local hour = self._curTime // 60
-- _discounts 是一个数组,用于存储出售倍率。
self._curSellDiscount = self._discounts[hour]
-- 找到出售公告牌
local billboard = LuaAPI.query_unit("出售公告牌")
-- 设置公告牌的文本
billboard.set_billboard_text(
string.format("%02d:%02d", self._curTime // 60, self._curTime % 60)
.. "#n #n出售价格倍率:"
.. string.format("%.1f", self._curSellDiscount)
)
end
注:代码位置:TradeShop.lua
代码主逻辑
lua
-- 导入必要的模块
local SellShop = require("SellShop")
local BuyShop = require("BuyShop")
local ItemManager = require("ItemManager")
local UIHandler = require("UIHandler")
--- 唯一全局变量
G = {}
-- 游戏开始事件
LuaAPI.global_register_trigger_event({ EVENT.GAME_INIT }, function()
-- 初始化物品管理器
G.itemManager = ItemManager.new()
-- 初始化购买商店
G.buyShop = BuyShop.new()
-- 初始化出售商店
G.sellShop = SellShop.new()
-- 初始化UI处理器
G.uiHandler = UIHandler.new()
end)
注:代码位置:main.lua
编辑器配置
UI界面
- 搭建金币显示以及商品操作按钮
创建房子预设
- 房子所有子组件合并,并存为预设
- 出售公告牌、出售区域交互、出售区域触发器成为房子-出售商店子物体,方便组件管理
- 使用蛋仔开发助手同步至VSCode侧,可在Data目录下找到相应数据文件
创建物品预设
- 打开预设编辑器,在管理窗口下选择背包一栏的组件,将物品预设存为新预设
- 新预设可在背包一栏下玩家自定义中查看
- 选中当前新预设后,更多物品表现效果可以通过修改外观实现,点击外观预设弹出创生面板选择
- 使用蛋仔开发助手同步至VSCode侧,可在Data目录下找到相应数据文件