无限大世界
你可以在PC编辑器中新建地图,选择Lua模板图中的无限大世界,查看本示例的完整工程
游戏简介
在玩家身边小范围内存在地板方块,随着玩家移动脚下的地板方块不断创建销毁。
技术要点
- Lua 添加Tick回调函数
- Lua 自定义哈希函数实现返回唯一值
- Lua 使用基础类型进行数据管理
- Lua 计算玩家当前地块信息
- Lua 记录加载和销毁的地块信息
- Lua 加载和销毁地块
注:本示例可在编辑器模板图中下载。
技术要点分析
Lua 添加Tick回调函数
lua
local function onPreTick(_)
updateTiles()
end
---设置Tick回调函数
--- 第一个参数帧前回调,第二个参数帧后回调
LuaAPI.set_tick_handler(onPreTick, nil)
Lua 自定义哈希函数实现返回唯一值
lua
-- 自定义哈希函数
local function getKey(x, z)
-- math.tointeger(x) 返回整数
-- +4096 防止返回负数
-- 65536=2^16 保证唯一性
-- 假设 x 和 z 的范围是 [-4096, 4096),那么结果将始终是非负的
return (math.tointeger(x) + 4096) * 65536 + math.tointeger(z) + 4096
end
注:代码位置:main.lua
Lua 使用基础类型进行数据管理
lua
local usedTiles = {} -- 记录已使用的地块{key:tileUnit},使用哈希函数计算唯一值作为key
local freeTiles = { tile1 = {}, tile2 = {} } -- 分开存储两种类型的地块
local tileUnit1 = LuaAPI.query_unit("地块1")
local tileUnit2 = LuaAPI.query_unit("地块2")
local tilePrefabId1 = tileUnit1.get_key()
local tilePrefabId2 = tileUnit2.get_key()
local groundSize = math.Vector3(4, 1, 4)
-- 定义加载和卸载半径
local LOAD_RADIUS = 1 -- 加载周围1格地块(3x3)
local UNLOAD_RADIUS = 3 -- 只有超出3格距离才卸载(7x7)
注:代码位置:main.lua
Lua 计算玩家当前地块信息
lua
-- 计算一下当前地块格子的坐标,将世界中的玩家坐标转换为地块索引坐标
-- position 玩家位置
-- groundSize 地块大小
-- x 和 z 是地块坐标
local divisorZ = 1.0 / math.toreal(groundSize.z)
local z = math.tointeger(math.floor((position.z + groundSize.z * 0.5) * divisorZ))
local divisorX = 1.0 / math.toreal(groundSize.x)
local x = math.tointeger(math.floor((position.x + groundSize.x * 0.5) * divisorX))
注:代码位置:main.lua
Lua 记录加载和销毁的地块信息
lua
-- local LOAD_RADIUS = 1 -- 加载周围1格地块(3x3)
-- local UNLOAD_RADIUS = 3 -- 只有超出3格距离才卸载(7x7)
-- 记录加载范围内的地块
-- 双层循环遍历加载范围内的地块
for i = x - LOAD_RADIUS, x + LOAD_RADIUS do
for j = z - LOAD_RADIUS, z + LOAD_RADIUS do
-- 地块索引坐标转世界坐标的位置
local posX = i * groundSize.x
local posZ = j * groundSize.z
-- 计算地块索引坐标的哈希值
local gridKey = getKey(posX, posZ)
-- 存入inLoadRangeTiles
inLoadRangeTiles[gridKey] = true
end
end
-- 记录卸载范围内的地块(注:后面在卸载时如果不在卸载范围内,则进行销毁)
-- 双层循环遍历销毁范围内的地块
for i = x - UNLOAD_RADIUS, x + UNLOAD_RADIUS do
for j = z - UNLOAD_RADIUS, z + UNLOAD_RADIUS do
-- 地块索引坐标转世界坐标的位置
local posX = i * groundSize.x
local posZ = j * groundSize.z
-- 计算地块索引坐标的哈希值
local gridKey = getKey(posX, posZ)
-- 存入inUnloadRangeTiles
inUnloadRangeTiles[gridKey] = true
end
end
注:代码位置:main.lua
Lua 加载和销毁地块
lua
-- 加载新的地块
for gridKey in pairs(inLoadRangeTiles) do
-- 判断是否已经加载过
if not usedTiles[gridKey] then
-- 通过哈希值获取地块索引坐标,相当于哈希函数的逆运算
local posX = math.floor(gridKey / 65536) - 4096
local posZ = gridKey % 65536 - 4096
-- 棋盘格判断:根据格子坐标和决定使用哪种地块
local gridX = math.floor(posX / groundSize.x)
local gridZ = math.floor(posZ / groundSize.z)
-- 判断是哪种类型的地块
local isType1 = (gridX + gridZ) % 2 == 0
local freeList, prefabId, model
-- 判断是哪种类型的地块,并赋值地块
if isType1 then
freeList = freeTiles.tile1
prefabId = tilePrefabId1
model = tileUnit1
else
freeList = freeTiles.tile2
prefabId = tilePrefabId2
model = tileUnit2
end
-- 从 freeList 表中移除并返回最后一个元素
local unit = table.remove(freeList)
-- 判断是否有空闲地块,如果有则使用,如果没有则创建
if unit then
unit.set_position(math.Vector3(posX, 0.0, posZ))
unit.set_model_visible(true)
unit.set_physics_active(true)
else
unit = GameAPI.create_obstacle(prefabId, math.Vector3(posX, 0.0, posZ),
math.Quaternion(0, 0, 0), model.get_scale())
end
-- 记录加载的地块
usedTiles[gridKey] = unit
end
end
-- 移除超出卸载范围的地块
for gridKey, unit in pairs(usedTiles) do
-- 判断是否超出卸载范围的地块
if not inUnloadRangeTiles[gridKey] then
-- 关闭可见性并关闭物理
unit.set_model_visible(false)
unit.set_physics_active(false)
-- 判断是哪种类型的地块并放入对应的回收池
local unitId = unit.get_key()
-- 判断是哪种类型的地块,并放入对应的回收池
if unitId == tilePrefabId1 then
table.insert(freeTiles.tile1, unit)
else
table.insert(freeTiles.tile2, unit)
end
-- 移除地块
usedTiles[gridKey] = nil
end
end
注:代码位置:main.lua
完整代码
lua
local UINodes = require("Data.UINodes")
-- 游戏开始事件
LuaAPI.global_register_trigger_event({ EVENT.GAME_INIT }, function()
local usedTiles = {}
local freeTiles = { tile1 = {}, tile2 = {} } -- 分开存储两种类型的地块
local tileUnit1 = LuaAPI.query_unit("地块1")
local tileUnit2 = LuaAPI.query_unit("地块2")
local tilePrefabId1 = tileUnit1.get_key()
local tilePrefabId2 = tileUnit2.get_key()
local groundSize = math.Vector3(4, 1, 4)
-- 定义加载和卸载半径
local LOAD_RADIUS = 1 -- 加载周围1格地块(3x3)
local UNLOAD_RADIUS = 3 -- 只有超出3格距离才卸载(7x7)
local function getKey(x, z)
return (math.tointeger(x) + 4096) * 65536 + math.tointeger(z) + 4096
end
-- 初始两个地块
usedTiles[getKey(0, 0)] = tileUnit1
usedTiles[getKey(4, 0)] = tileUnit2
-- 根据玩家位置更新地块
local function updateTiles()
local allRoles = GameAPI.get_all_valid_roles()
local inLoadRangeTiles = {} -- 加载范围内的地块
local inUnloadRangeTiles = {} -- 卸载范围内的地块
for _, role in ipairs(allRoles) do
local character = role.get_ctrl_unit()
local position = character.get_position()
role.set_label_text(UINodes["位置"],
string.format("当前位置: (%d, %d, %d)", math.tointeger(position.x), math.tointeger(position.y),
math.tointeger(position.z)))
-- 计算一下当前地块格子的坐标
local divisorZ = 1.0 / math.toreal(groundSize.z)
local z = math.tointeger(math.floor((position.z + groundSize.z * 0.5) * divisorZ))
local divisorX = 1.0 / math.toreal(groundSize.x)
local x = math.tointeger(math.floor((position.x + groundSize.x * 0.5) * divisorX))
-- 记录加载范围内的地块
for i = x - LOAD_RADIUS, x + LOAD_RADIUS do
for j = z - LOAD_RADIUS, z + LOAD_RADIUS do
local posX = i * groundSize.x
local posZ = j * groundSize.z
local gridKey = getKey(posX, posZ)
inLoadRangeTiles[gridKey] = true
end
end
-- 记录卸载范围内的地块
for i = x - UNLOAD_RADIUS, x + UNLOAD_RADIUS do
for j = z - UNLOAD_RADIUS, z + UNLOAD_RADIUS do
local posX = i * groundSize.x
local posZ = j * groundSize.z
local gridKey = getKey(posX, posZ)
inUnloadRangeTiles[gridKey] = true
end
end
end
-- 加载新的地块
for gridKey in pairs(inLoadRangeTiles) do
if not usedTiles[gridKey] then
local posX = math.floor(gridKey / 65536) - 4096
local posZ = gridKey % 65536 - 4096
-- 棋盘格判断:根据格子坐标和决定使用哪种地块
local gridX = math.floor(posX / groundSize.x)
local gridZ = math.floor(posZ / groundSize.z)
local isType1 = (gridX + gridZ) % 2 == 0
local freeList, prefabId, model
if isType1 then
freeList = freeTiles.tile1
prefabId = tilePrefabId1
model = tileUnit1
else
freeList = freeTiles.tile2
prefabId = tilePrefabId2
model = tileUnit2
end
local unit = table.remove(freeList)
if unit then
unit.set_position(math.Vector3(posX, 0.0, posZ))
unit.set_model_visible(true)
unit.set_physics_active(true)
else
unit = GameAPI.create_obstacle(prefabId, math.Vector3(posX, 0.0, posZ),
math.Quaternion(0, 0, 0), model.get_scale())
end
usedTiles[gridKey] = unit
end
end
-- 移除超出卸载范围的地块
for gridKey, unit in pairs(usedTiles) do
if not inUnloadRangeTiles[gridKey] then
unit.set_model_visible(false)
unit.set_physics_active(false)
-- 判断是哪种类型的地块并放入对应的回收池
local unitId = unit.get_key()
if unitId == tilePrefabId1 then
table.insert(freeTiles.tile1, unit)
else
table.insert(freeTiles.tile2, unit)
end
usedTiles[gridKey] = nil
end
end
end
local function onPreTick(_)
updateTiles()
end
LuaAPI.set_tick_handler(onPreTick, nil)
end)
注:代码位置:main.lua
编辑器配置
创建地块预设
- 将组件自定义属性后并存为预设,方便使用预设编号动态创建地块
新预设同步到Lua编程中使用
编辑器侧:蛋仔开发助手已连接状态下且修改已经保存。
VSCode侧:蛋仔开发助手已连接状态下,需勾选预设标号、物品预设后点击导出数据即可在Data目录下找到相应数据文件。