动态搭建
你可以在PC编辑器中新建地图,选择Lua模板图中的动态搭建,查看本示例的完整工程
游戏简介
玩家可以将砖块随意与其他组件进行焊接,并且有焊接预览功能。
技术要点
- Lua 将焊接函数注册到帧更新函数中
- Lua 创建焊接预览预制体,并根据玩家的位置和旋转进行变换
- Lua 监听角色的举起事件和放下事件
- Lua 给UI控件注册事件
- Lua 通过射线检测进行检测焊接点的有效性
- Lua 使用关节尝试焊接物体
- Lua 创建StickController类,用于焊接管理
注:本示例可在编辑器模板图中下载。
技术要点分析
将焊接函数注册到帧更新函数中
lua
-- 导入StickController类
local StickController = require("StickController")
-- 注册全局触发器
LuaAPI.global_register_trigger_event({ EVENT.GAME_INIT }, function()
-- 创建StickController对象
local stickController = StickController.new()
-- 注册帧更新函数
local function onPreTick()
stickController:update()
end
-- 设置帧更新处理函数
LuaAPI.set_tick_handler(onPreTick, nil)
end)
注:代码位置:main.lua
创建焊接预览预制体,并根据玩家的位置和旋转进行变换
- 创建焊接预览预制体
lua
-- 对每个玩家做操作
for _, role in ipairs(GameAPI.get_all_valid_roles()) do
-- 获取角色控制的单位
local roleId = role.get_roleid()
-- 创建用来预览焊接位置的指示器
self.previewUnits[roleId] = GameAPI.create_obstacle(
UnitPrefab["焊接预览"],
math.Vector3(0, 0, 0),
math.Quaternion(0, 0, 0),
math.Vector3(scale, scale, scale),
role
)
self.charLiftInfos[roleId] = {}
-- 初始化时需要将预览的指示器需要对所有人都默认隐藏
for _, role in ipairs(GameAPI.get_all_valid_roles()) do
for _, unit in pairs(self.previewUnits) do
role.set_unit_visible(unit, false)
end
end
注:代码位置:StickController.lua
- 根据玩家的位置和旋转进行变换
lua
-- 此函数已经注册到帧更新函数,每帧会调用一次
function StickController:update()
-- 需要一直检测能否焊接,如果能焊接则显示焊接预览指示器和焊接按钮
for roleid, liftInfo in pairs(self.charLiftInfos) do
-- 获取角色控制的单位
local role = GameAPI.get_role(roleid)
-- 获取角色举起物体的信息
local previewUnit = self.previewUnits[role.get_roleid()]
-- 检查是否能焊接
local canStick = self:checkRoleCanStick(role, liftInfo)
-- 设置焊接按钮和焊接预览的可见性
role.set_node_visible(UINodes["焊接按钮"], canStick)
role.set_unit_visible(previewUnit, canStick)
-- 如果能焊接,将焊接预览的位置和旋转设置到焊接预览指示器上
if canStick then
local rot = role.get_ctrl_unit().get_orientation()
local pos = liftInfo.stickPos
previewUnit.set_position(pos)
previewUnit.set_orientation(rot)
end
end
end
注:代码位置:StickController.lua
监听角色的举起事件和放下事件
lua
-- 监听角色举起物体的事件
LuaAPI.unit_register_trigger_event(character, { EVENT.SPEC_LIFEENTITY_LIFT_BEGAN }, function(_, _, data)
-- data 是举起物体
local lifted = data.lifted_unit
-- 此处做的操作是获取物体所有的关节,并将动态焊接的关节全部移除
self:tryRemoveStickJoints(lifted)
-- 举起的物体变半透明,同时关闭物理
lifted.enable_expr_device_by_name("修改透明度")
lifted.set_physics_active(false)
-- 将玩家举起物体信息按玩家roleid存储起来
self.charLiftInfos[roleId] = { unit = lifted }
-- 举起后打开相机朝向的更新
role.set_camera_rotation_sync_enabled(true)
end)
-- 监听角色放下物体的事件
LuaAPI.unit_register_trigger_event(character, { EVENT.SPEC_LIFEENTITY_LIFT_ENDED }, function(_, _, data)
-- data 是放下的物体
local lifted = data.lifted_unit
-- 举起的物体恢复透明度,同时恢复物理
lifted.disable_expr_device_by_name("修改透明度")
lifted.set_physics_active(true)
-- 将玩家举起物体信息移除
self.charLiftInfos[roleId] = {}
-- 重置放下物体的位置
local direction = character.get_orientation():apply(math.Vector3(0, 2, 0))
lifted.set_position(character.get_position() + direction)
lifted.set_orientation(character.get_orientation())
-- 放下后关闭相机朝向的更新
role.set_camera_rotation_sync_enabled(false)
end)
注:代码位置:StickController.lua
Lua 给UI控件注册事件
lua
-- 点击焊接按钮
LuaAPI.global_register_custom_event("点击焊接", function(_, _, data)
local role = data.role
-- 获取角色举起物体的信息
local liftInfo = self.charLiftInfos[role.get_roleid()]
-- 检查角色是否可以焊接
if self:checkRoleCanStick(role, liftInfo) then
-- 尝试焊接
self:tryStickUnit(role, liftInfo)
end
end)
注:代码位置:StickController.lua
Lua 通过射线检测进行检测焊接点的有效性
lua
function StickController:checkRoleCanStick(role, liftInfo)
-- 传入玩家信息和举起物体信息
if not liftInfo.unit then
return false
end
-- 获取相机朝向
local camRot = role.get_camera_rotation()
if camRot.w == 0 then
-- 需要剔除无效的相机朝向(刚开启相机方向监听的时候,相机朝向可能还未更新)
return false
end
local character = role.get_ctrl_unit()
-- 角色头顶处
local startPos = character.get_position() + math.Vector3(0, 2.9, 0)
-- 相机朝向
local direction = camRot:apply(math.Vector3(0, 0, 1))
local length = 15.0
-- 给举起物体添加两个属性且设为nil
liftInfo.stickTo = nil -- 焊接目标(指的是另一个可以焊接的组件)
liftInfo.stickPos = nil -- 焊接点(坐标位置)
-- 从角色头顶朝相机方向打射线(最远距离为 length),碰到的第一个物体和位置,作为焊接目标和焊接点
GameAPI.raycast_unit(
startPos,
startPos + direction * length,
{ Enums.UnitType.OBSTACLE },
function(unit, point, normal)
-- 只有射线检测成功才会更新焊接目标和焊接点
liftInfo.stickTo = unit
liftInfo.stickPos = point
end
)
-- 只有找到焊接点才算成功,否则返回false
return liftInfo.stickTo ~= nil
end
注:代码位置:StickController.lua
Lua 使用关节尝试焊接物体
lua
function StickController:tryStickUnit(role, liftInfo)
-- 传入玩家信息和举起物体信息
-- 先判断焊接目标和玩家举起的物体是否是同一个物体,如果不是才执行焊接
if liftInfo.stickTo and liftInfo.stickTo ~= liftInfo.unit then
-- 先把举起物体放下
role.get_ctrl_unit().lift_unit(liftInfo.unit)
-- 再恢复举起物体的物理
liftInfo.unit.set_physics_active(true)
-- 根据焊接点和焊接点旋转,将刚放下的物体调整位置
liftInfo.unit.set_position(liftInfo.stickPos)
liftInfo.unit.set_orientation(liftInfo.stick_rot or role.get_ctrl_unit().get_orientation())
-- 给焊接物体添加一个关节,用于尝试焊接
local joint = GameAPI.create_joint_assistant(Enums.JointAssistantKey.FIXED, liftInfo.unit, liftInfo.stickTo)
-- 给关节标记为动态焊接,方便删除
joint.set_kv_by_type(Enums.ValueType.Bool, "isDynamicStick", true)
end
end
注:代码位置:StickController.lua
Lua 创建StickController类,用于焊接管理
lua
-- 导入ClassUtils.class函数
local class = require("Utils.ClassUtils").class
-- 创建StickController类
---@class StickController
---@field new fun(): StickController
local StickController = class("StickController")
-- 创建StickController类构造函数以及其他函数
function StickController:ctor()
self.previewUnits = {} -- 预览焊接位置的指示器
self.charLiftInfos = {} -- 角色举起物体的信息
-- 代码省略 --
end
function StickController:checkRoleCanStick(role, liftInfo)
-- 代码省略 --
end
function StickController:tryStickUnit(role, liftInfo)
-- 代码省略 --
end
function StickController:tryRemoveStickJoints(unit)
-- 代码省略 --
end
function StickController:update()
-- 代码省略 --
end
return StickController
注:以上代码简化,详细代码位置:StickController.lua
完整代码
lua
local StickController = require("StickController")
LuaAPI.global_register_trigger_event({ EVENT.GAME_INIT }, function()
local stickController = StickController.new()
local function onPreTick()
stickController:update()
end
LuaAPI.set_tick_handler(onPreTick, nil)
end)
注:代码位置:main.lua
lua
local UnitPrefab = require("Data.Prefab").unit
local UINodes = require("Data.UINodes")
local class = require("Utils.ClassUtils").class
---@class StickController
---@field new fun(): StickController
local StickController = class("StickController")
function StickController:ctor()
self.previewUnits = {} -- 预览焊接位置的指示器
self.charLiftInfos = {} -- 角色举起物体的信息
local scale = 0.5
for _, role in ipairs(GameAPI.get_all_valid_roles()) do
local roleId = role.get_roleid()
-- 创建用来预览焊接位置的指示器
self.previewUnits[roleId] = GameAPI.create_obstacle(
UnitPrefab["焊接预览"],
math.Vector3(0, 0, 0),
math.Quaternion(0, 0, 0),
math.Vector3(scale, scale, scale),
role
)
self.charLiftInfos[roleId] = {}
local character = role.get_ctrl_unit()
-- 监听角色举起物体的事件
LuaAPI.unit_register_trigger_event(character, { EVENT.SPEC_LIFEENTITY_LIFT_BEGAN }, function(_, _, data)
local lifted = data.lifted_unit
self:tryRemoveStickJoints(lifted)
-- 举起的物体变半透明,同时关闭物理
lifted.enable_expr_device_by_name("修改透明度")
lifted.set_physics_active(false)
self.charLiftInfos[roleId] = { unit = lifted }
-- 举起后打开相机朝向的更新
role.set_camera_rotation_sync_enabled(true)
end)
-- 监听角色放下物体的事件
LuaAPI.unit_register_trigger_event(character, { EVENT.SPEC_LIFEENTITY_LIFT_ENDED }, function(_, _, data)
local lifted = data.lifted_unit
-- 举起的物体恢复透明度,同时恢复物理
lifted.disable_expr_device_by_name("修改透明度")
lifted.set_physics_active(true)
self.charLiftInfos[roleId] = {}
-- 重置放下物体的位置
local direction = character.get_orientation():apply(math.Vector3(0, 2, 0))
lifted.set_position(character.get_position() + direction)
lifted.set_orientation(character.get_orientation())
-- 放下后关闭相机朝向的更新
role.set_camera_rotation_sync_enabled(false)
end)
end
-- 预览的指示器需要对所有人都默认隐藏
for _, role in ipairs(GameAPI.get_all_valid_roles()) do
for _, unit in pairs(self.previewUnits) do
role.set_unit_visible(unit, false)
end
end
-- 点击焊接按钮
LuaAPI.global_register_custom_event("点击焊接", function(_, _, data)
local role = data.role
local liftInfo = self.charLiftInfos[role.get_roleid()]
if self:checkRoleCanStick(role, liftInfo) then
self:tryStickUnit(role, liftInfo)
end
end)
end
function StickController:checkRoleCanStick(role, liftInfo)
if not liftInfo.unit then
return false
end
-- 获取相机朝向
local camRot = role.get_camera_rotation()
if camRot.w == 0 then
-- 需要剔除无效的相机朝向(刚开启相机方向监听的时候,相机朝向可能还未更新)
return false
end
local character = role.get_ctrl_unit()
-- 角色头顶处
local startPos = character.get_position() + math.Vector3(0, 2.9, 0)
-- 相机朝向
local direction = camRot:apply(math.Vector3(0, 0, 1))
local length = 15.0
liftInfo.stickTo = nil
liftInfo.stickPos = nil
-- 从角色头顶朝相机方向打射线(最远距离为 length),碰到的第一个物体和位置,作为焊接目标和焊接点
GameAPI.raycast_unit(
startPos,
startPos + direction * length,
{ Enums.UnitType.OBSTACLE },
function(unit, point, normal)
-- 只有射线检测成功才会更新焊接目标和焊接点
liftInfo.stickTo = unit
liftInfo.stickPos = point
end
)
-- 只有找到焊接点才算成功
return liftInfo.stickTo ~= nil
end
function StickController:tryStickUnit(role, liftInfo)
if liftInfo.stickTo and liftInfo.stickTo ~= liftInfo.unit then
-- 先把举起物体放下
role.get_ctrl_unit().lift_unit(liftInfo.unit)
-- 再恢复举起物体的物理
liftInfo.unit.set_physics_active(true)
liftInfo.unit.set_position(liftInfo.stickPos)
liftInfo.unit.set_orientation(liftInfo.stick_rot or role.get_ctrl_unit().get_orientation())
local joint = GameAPI.create_joint_assistant(Enums.JointAssistantKey.FIXED, liftInfo.unit, liftInfo.stickTo)
-- 给关节标记为动态焊接,方便删除
joint.set_kv_by_type(Enums.ValueType.Bool, "isDynamicStick", true)
end
end
function StickController:tryRemoveStickJoints(unit)
-- 只删除标记为动态焊接的关节
for _, v in ipairs(GameAPI.get_joint_assistants(unit)) do
if v.has_kv("isDynamicStick") then
GameAPI.destroy_unit(v)
end
end
end
function StickController:update()
-- 需要一直检测能否焊接,如果能焊接则显示焊接预览指示器和焊接按钮
for roleid, liftInfo in pairs(self.charLiftInfos) do
local role = GameAPI.get_role(roleid)
local previewUnit = self.previewUnits[role.get_roleid()]
local canStick = self:checkRoleCanStick(role, liftInfo)
role.set_node_visible(UINodes["焊接按钮"], canStick)
role.set_unit_visible(previewUnit, canStick)
if canStick then
local rot = role.get_ctrl_unit().get_orientation()
local pos = liftInfo.stickPos
previewUnit.set_position(pos)
previewUnit.set_orientation(rot)
end
end
end
return StickController
注:代码位置:StickController.lua
编辑器配置
UI界面
- UI界面添加焊接按钮,并设置焊接按钮的点击事件
- VSCode侧注册自定义事件,实现焊接功能
lua
-- 点击焊接按钮
LuaAPI.global_register_custom_event("点击焊接", function(_, _, data)
local role = data.role
local liftInfo = self.charLiftInfos[role.get_roleid()]
if self:checkRoleCanStick(role, liftInfo) then
self:tryStickUnit(role, liftInfo)
end
end)
注:以上代码简化,详细代码位置:StickController.lua
创建焊接预览预设
- 使用多种组件组件,合并成焊接预览并存为预设
将焊接预览组件组添加为新预设后,使用蛋仔开发助手同步至VSCode侧通过lua编程实现动态创建焊接预览
游戏初始化时使用预设数据Prefab.lua配置文件获取焊接预览预设编号,为每个玩家创建焊接预览
lua
-- Prefab.lua中数据由编辑器自动生成
local UnitPrefab = require("Data.Prefab").unit
for _, role in ipairs(GameAPI.get_all_valid_roles()) do
local roleId = role.get_roleid()
-- 创建用来预览焊接位置的指示器
self.previewUnits[roleId] = GameAPI.create_obstacle(
UnitPrefab["焊接预览"],
math.Vector3(0, 0, 0),
math.Quaternion(0, 0, 0),
math.Vector3(scale, scale, scale),
role
)
---代码省略---
end
注:以上代码简化,详细代码位置:StickController.lua