Skip to content

换装玩法

你可以在PC编辑器中新建地图,选择Lua模板图中的换装玩法,查看本示例的完整工程

游戏简介

玩家站在对应职业公告牌后方的区域内,会自动穿上对应职业的服饰。回到中心区域后,会自动卸下服饰。

技术要点

  • Lua 实现ClassUtils.class函数
  • Lua 理解面向对象编程,并定义DressUpArea类
  • Lua 使用Lua文件配置装扮区域信息
  • Lua 计算每个装饰区域的相关信息
  • Lua 实现装扮区域的辅助函数
  • Lua 实现创建装扮区域函数(createDressUpArea)
  • Lua 根据角度创建角色展示模型

注:本示例可在编辑器模板图中下载。

技术要点分析

Lua 实现ClassUtils.class函数

  • 此函数实现简单但功能强大的类系统
  • 该系统特点:1.单继承 2.自动调用父类的构造函数 3.允许重写函数 4.提供类名和父类引用
  • 为Lua提供面向对象编程结构,可以实现面向对象编程
lua
-- 定义ClassUtils.class函数,接受类名和可选父类作为参数
function ClassUtils.class(classname, super)
	-- 父类检查,确保父类要么是nil,要么是table
	local superType = type(super)
	assert(super == nil or type(super) == "table", superType)

	-- 创建新类,如果有父类,设置元表实现继承
	local cls
	if super then
		cls = {}
		setmetatable(cls, {__index = super})
		cls.super = super
	else
		cls = {}
	end
	-- 属性设置,设置类名和元表
	cls.__cname = classname
	cls.__index = cls
	-- 创建新实例,并设置其元表为类本身
	function cls.new(...)
		local instance = setmetatable({}, cls)

		local child = cls
		local classes = {cls}
		-- 收集类及其所有父类
		while child.super do
			table.insert(classes, child.super)
			child = child.super
		end
		-- 从最顶层开始,调用类中的构造函数
		for i=#classes,1,-1 do
			local ctor = rawget(classes[i], "ctor")
			if ctor then
				ctor(instance, ...)
			end
		end
		-- 返回实例
		return instance
	end
	-- 返回类
	return cls
end

注:代码位置:Utils/ClassUtils.lua

Lua 理解面向对象编程,并定义DressUpArea类

  • 理解类和对象的关系

    类是对象的抽象,对象是类的实例

    类定义了一类对象的属性和函数;对象是类的一个实例,可以通过类名创建对象

    属性和方法是类的所有对象共有的

  • 面向对象编程的核心思想:封装、继承、多态

    封装:将对象的属性和行为封装到一个类中,以便于管理和使用(封装DressUpArea类)

    继承:将父类中定义的属性和行为复制到子类中,以便于子类使用(本次示例不涉及)

    多态:子类可以重写父类的同名函数,从而实现不同的行为(本次示例不涉及)

接下来我们将定义一个DressUpArea类,用于表示装扮区域。

lua
-- 导入ClassUtils.class函数
local class = require("Utils.ClassUtils").class
-- 定义DressUpArea类
---@class DressUpArea
---@field new fun(string, table, fun, fun, fun, Vector3, number): DressUpArea
local DressUpArea = class("DressUpArea")
-- 定义DressUpArea类构造函数
function DressUpArea.new(key, info, enterCallback, leaveCallback, pos, yaw)
	-- 定义类的属性即该类会有哪些数据
	self.id = dressUpId
	self.info = info
	self.exitTrigger = nil
	self.enterCb = enterCallback
	self.exitCb = exitCallback

	self.areaObj = nil
	self.showObj = nil
	self.center = math.Vector3(0, 0, 0)
	-- 初始化调用创建装扮区域函数
	function DressUpArea:createArea(pos, yaw)
end
-- 定义DressUpArea类的函数即该类会有哪些行为(通俗讲该类可以做什么)
function DressUpArea:createArea(pos, yaw)
	-- 以下代码省略,后面会详细介绍
end

注:以上代码简化,详细代码位置:main.lua

Lua 使用Lua文件配置装扮区域信息

  • 配置装扮区域信息
  • 使用Lua文件配置装扮区域信息
lua
return {
	Doctor = {
		name = "医生",
		modelCreatureKey = Prefab.character["职业-医生1"],
	},
	Director = {
		name = "指挥员",
		modelCreatureKey = Prefab.character["职业-指挥员12"],
	},
	-- 以下省略
}

注:以上代码简化,详细代码位置:Data/DressUpData.lua

lua
-- 导入DressUpData.lua文件,即可使用DressUpData.lua文件中的数据
local DressUpData = require("Data.DressUpData")

注:代码位置:main.lua

使用lua文件配置装扮区域信息,可以将配置信息保存在一个lua文件中,方便管理和维护。

Lua 计算每个装饰区域的相关信息

lua
-- 计算圆形排列的参数
local numDressUps = #dressUpDatas -- 装饰区域数量
local angleDelta = math.pi * 2.0 / numDressUps -- 角度增量
local radius = 30 -- 装扮区域半径
local currAngle = 0.0 -- 当前角度

-- 遍历所有装饰区域信息
for _, data in ipairs(dressUpDatas) do
		local key = data[1] -- 装饰区域ID
		local info = data[2] -- 装饰区域信息

		-- 计算装扮区域的位置
		local dir = math.Vector3(math.cos(currAngle), 0, math.sin(currAngle)) -- 装扮区域方向
		local pos = math.Vector3(0, -4, 0) + dir * radius -- 装扮区域位置

		createDressUpArea(key, info, pos, math.pi - currAngle) -- 创建装扮区域(后面会详细介绍)

		currAngle = currAngle + angleDelta -- 更新角度
	end

注:代码位置:main.lua

Lua 实现装扮区域的辅助函数

lua
-- 创建装扮区域的辅助函数
local function createDressUpArea(key, info, pos, yaw)
	-- 定义进入区域的回调函数
	local function _enterCallback(role, character)
		-- 如果角色为空,则返回
		if character.get_role_id() == -1 then
			return
		end
		-- 设置角色模型
		character.set_model_by_creature_key(info.modelCreatureKey)
	end
	-- 创建装扮区域对象,并传入相关信息以及回调函数
	DressUpArea.new(key, info, _enterCallback, nil, pos, yaw)
end

注:代码位置:main.lua

Lua 实现创建装扮区域函数(createDressUpArea)

lua
-- 创建装扮区域
-- pos 装扮区域位置
-- yaw 装扮区域朝向
function DressUpArea:createArea(pos, yaw)
	local info = self.info
	local text = info.name
	-- 创建单位组
	local area = GameAPI.create_unit_group(Consts.JOB_CHOOSE_PREFAB, pos, math.Quaternion(0, yaw, 0))
	-- 将单位组赋值给类的属性
	self.areaObj = area
	-- 后面创建展示用角色会用到
	local boardPos = pos
	-- 遍历区域中的子对象
	for _, child in ipairs(area.get_children()) do
		local childName = child.get_name()
		-- 设置装扮介绍
		if string.find(childName, "名称", 1, true) == 1 then
			child.set_billboard_text(text)
			boardPos = child.get_position()
		end
		-- 设置装扮选择区域
		if string.find(childName, "选择区域", 1, true) == 1 then
			local areaId = LuaAPI.get_unit_id(child)
			local areaCenter = child.get_position()
			self.center = areaCenter
			-- 注册进入装扮选择区域的触发器
			LuaAPI.global_register_trigger_event(
				{ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.ENTER, areaId },
				function(_, _, data)
					local character = data.event_unit
					local role = character.get_role()
					-- 检查是否为有效角色
					if role == nil or character.get_camp_id() == -1 then
						return
					end
					self.enterCb(role, character)
				end
			)

			-- 注册退出装扮选择区域的触发器
			self.exitTrigger = LuaAPI.global_register_trigger_event(
				{ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.LEAVE, areaId },
				function(_, _, data)
					local character = data.event_unit
					local role = character.get_role()
					if self.exitCb then
						self.exitCb(role, character)
					end
				end
			)
		end
	end
	-- 计算展示用角色的位置和方向(后面会详细介绍)
end

注:代码位置:main.lua

Lua 根据角度创建角色展示模型

lua
if info.modelCreatureKey then
		-- 计算展示用角色的位置和方向
		local dir = math.Vector3(0, 0, 1)
		dir:set_pitch_yaw(0, yaw + math.pi / 2) -- 根据yaw计算展示用角色的朝向
		-- 创建展示用角色
		self.showObj = GameAPI.create_creature(
			info.modelCreatureKey,
			boardPos + dir * 1.0,
			math.Quaternion(0, yaw + math.pi / 2, 0),
			math.Vector3(1, 1, 1)
		)
end

注:代码位置:main.lua

完整代码

lua
-- 导入必要的模块
local Consts = require("Data.Consts")
local DressUpData = require("Data.DressUpData")
local class = require("Utils.ClassUtils").class

-- 定义DressUpArea类
---@class DressUpArea
---@field new fun(string, table, fun, fun, fun, Vector3, number): DressUpArea
local DressUpArea = class("DressUpArea")

-- DressUpArea类的构造函数
function DressUpArea:ctor(dressUpId, info, enterCallback, exitCallback, pos, yaw)
	self.id = dressUpId
	self.info = info
	self.exitTrigger = nil
	self.enterCb = enterCallback
	self.exitCb = exitCallback

	self.areaObj = nil
	self.showObj = nil
	self.center = math.Vector3(0, 0, 0)
	self:createArea(pos, yaw)
end

-- 创建装扮区域
function DressUpArea:createArea(pos, yaw)
	local info = self.info
	local text = info.name
	-- 创建单位组
	local area = GameAPI.create_unit_group(Consts.JOB_CHOOSE_PREFAB, pos, math.Quaternion(0, yaw, 0))
	self.areaObj = area
	local boardPos = pos
	-- 遍历区域中的子对象
	for _, child in ipairs(area.get_children()) do
		local childName = child.get_name()
		-- 设置装扮介绍
		if string.find(childName, "名称", 1, true) == 1 then
			child.set_billboard_text(text)
			boardPos = child.get_position()
		end
		-- 设置装扮选择区域
		if string.find(childName, "选择区域", 1, true) == 1 then
			local areaId = LuaAPI.get_unit_id(child)
			local areaCenter = child.get_position()
			self.center = areaCenter
			-- 注册进入装扮选择区域的触发器
			LuaAPI.global_register_trigger_event(
				{ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.ENTER, areaId },
				function(_, _, data)
					local character = data.event_unit
					local role = character.get_role()
					-- 检查是否为有效角色
					if role == nil or character.get_camp_id() == -1 then
						return
					end
					self.enterCb(role, character)
				end
			)

			-- 注册退出装扮选择区域的触发器
			self.exitTrigger = LuaAPI.global_register_trigger_event(
				{ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.LEAVE, areaId },
				function(_, _, data)
					local character = data.event_unit
					local role = character.get_role()
					if self.exitCb then
						self.exitCb(role, character)
					end
				end
			)
		end
	end

	if info.modelCreatureKey then
		-- 计算展示用角色的位置和方向
		local dir = math.Vector3(0, 0, 1)
		dir:set_pitch_yaw(0, yaw + math.pi / 2)
		-- 创建展示用角色
		self.showObj = GameAPI.create_creature(
			info.modelCreatureKey,
			boardPos + dir * 1.0,
			math.Quaternion(0, yaw + math.pi / 2, 0),
			math.Vector3(1, 1, 1)
		)
	end
end

-- 创建装扮区域的辅助函数
local function createDressUpArea(key, info, pos, yaw)
	-- 定义进入区域的回调函数
	local function _enterCallback(role, character)
		if character.get_role_id() == -1 then
			return
		end

		character.set_model_by_creature_key(info.modelCreatureKey)
	end
	DressUpArea.new(key, info, _enterCallback, nil, pos, yaw)
end

-- 设置所有装扮区域
local function setupDressUpAreas()
	local dressUpDatas = {}
	for key, info in pairs(DressUpData) do
		table.insert(dressUpDatas, { key, info })
	end
	-- 计算圆形排列的参数
	local numDressUps = #dressUpDatas
	local angleDelta = math.pi * 2.0 / numDressUps
	local radius = 30
	local currAngle = 0.0

	-- 创建每个装扮区域
	for _, data in ipairs(dressUpDatas) do
		local key = data[1]
		local info = data[2]

		-- 计算装扮区域的位置
		local dir = math.Vector3(math.cos(currAngle), 0, math.sin(currAngle))
		local pos = math.Vector3(0, -4, 0) + dir * radius

		createDressUpArea(key, info, pos, math.pi - currAngle)

		currAngle = currAngle + angleDelta
	end
	DressUpArea.new("Default", { name = "取消装扮" }, function(role, character)
		character.reset_model()
	end, nil, math.Vector3(0, -4, 0), math.pi)
end

-- 注册游戏初始化事件
LuaAPI.global_register_trigger_event({ EVENT.GAME_INIT }, function()
	setupDressUpAreas()
end)

注:代码位置:main.lua

编辑器配置

创建生物预设

  • 打开预设编辑器面板,将蛋仔生物另存为预设

存为预设

  • 在新预设的属性窗口中设置蛋仔生物的装饰,并应用到所有对象

修改蛋仔生物装饰

  • 使用蛋仔开发助手同步至VSCode侧,实现动态创建NPC蛋仔生物

创建装扮选择区域预设

  • 选中所有子组件,并合并组件组并创建装扮选择区域预设

合并组件组

装扮选择区域存为预设

  • 在预设编辑器可查看装扮选择区域预设

预设编辑器

  • 使用蛋仔开发助手同步至VSCode侧,实现动态创建装扮选择区域