跳转到内容

编程元件性能优化实战

常用验证性能的方法

游戏创作过程中,我们经常会遇到需要优化性能的场合(例如试玩中发现游戏卡顿)。

如果您需要监测游戏帧率,那么可以前往【资源社区】添加【帧率监测】模组到您的的地图中。

除此之外,下面介绍一种,使用CPU时间来辅助判断性能问题的方法。

--获取起始时间
local startTime=TimerManager:GetClock()
Log:PrintServerLog("time start:"..startTime)
local endTime
local count=0
--创建100个方块
for i = 1, 10, 1 do
for j = 1, 10,1 do
local pos, rot, scale = Engine.Vector(i*100,j*100,50), Engine.Rotator(0,0,0), Engine.Vector(1,1,1)
local callBack = function(elementId)
count=count+1
--当创建完成时
if count==100 then
--获取结束时间
endTime=TimerManager:GetClock()
Log:PrintServerLog("time end:"..endTime)
--用结束时间减去起始时间,并通过Log输出
local time=endTime-startTime
Log:PrintServerLog("time:"..time)
end
end
Element:SpawnElement(Element.SPAWN_SOURCE.Config, 1101002001034000, callBack, pos, rot, scale,true)
end
end
--参考上面的思路,就可以方便计算出某个方法或一系列逻辑的运算时间。

检查短时间内频繁调用的函数

在游戏中,短时间(例如每帧)如果计算量较大,那么可能造成性能负担。

例如,在Start函数中,往往需要处理大量逻辑。针对这种情况,可以将部分逻辑延迟1帧(或若干帧)执行(使用TimerManager:AddFrame)

例如,经常在update函数(每帧调用)中处理大量的逻辑。针对这种情况,可以根据实际需求,每隔一段时间调用一次函数,以优化性能。

优化前:

--思路:在update函数中,每一帧都获取角色Id,并进行射线检测
-- 游戏更新
function GameClient:OnUpdate(deltaTime)
--获取玩家角色Id
local localPlayerId=Character:GetLocalPlayerId()
--射线检测玩家角色面前的元件
local elementId=PlayInteractive:GetHitResultWithRaycast(PlayInteractive.HIT_TYPE.Element,\
Character:GetPosition(localPlayerId),\
Character:GetForward(localPlayerId)*100+Character:GetPosition(localPlayerId),\
true,\
1)
--输出元件Id
Log:PrintLog("elementId",elementId)
end

优化后:

--思路:增加计时器,每0.2秒才执行一次射线检测
在update函数外,定义变量(玩家角色Id,经过的时间)
local localPlayerId
local timePassed=0
-- 客户端游戏开始时,只获取一次玩家角色Id即可
function GameClient:OnStart()
localPlayerId=Character:GetLocalPlayerId()
end
-- 游戏更新
function GameClient:OnUpdate(deltaTime)
--累积经过的时间
timePassed=timePassed+deltaTime
--如果积累的时间大于0.2秒
if timePassed>=0.2 then
--射线检测玩家角色面前的元件
local elementId=PlayInteractive:GetHitResultWithRaycast(PlayInteractive.HIT_TYPE.Element,\
Character:GetPosition(localPlayerId),\
Character:GetForward(localPlayerId)*100+Character:GetPosition(localPlayerId),\
true,\
1)
--输出元件Id
Log:PrintLog("elementId",elementId)
--将累积时间重置为0
timePassed=0
end
end

减少反复创建销毁,采用对象池思路

短时间内大量创建或销毁游戏对象,会对性能有一定影响。例如在无尽跑酷游戏中,不断在玩家角色前方创建障碍物;或者在刷怪游戏中,创建大量的生物怪物。如果您面临类似的问题,那么可以采用对象池的思路:

把需要使用的一系列对象预先放置在场景中,使用时,只需要从对象池中”取出”要用到的元件,当使用完成时再”放回”对象池,就可以避免反复创建销毁的开销。

--下面是一个示例。
--假设我们制作一个小游戏。地图上也会随时间不断创建"钻石"元件,当玩家接触"钻石"元件,就会销毁钻石。
--按照创建/销毁的思路,我们需要使用Element:SpawnElement来不断创建元件,使用Element:Destroy来销毁元件
--如果采用对象池思路去实现
--创建对象池变量
local inactivePool = {} --未激活的对象池
local activePool = {} --激活的对象池
local currentDiamond
-- 游戏启动时
function GameServer:OnStart()
--首先创建对象池(可以将需要用到的元件摆放在地图中,也可以游戏开始时创建;无论采用哪种方法,都需要将这些对象添加进对象池)
for index = 1, 20 do
--示例创建了20个钻石形状的元件
local pos, rot, scale = Engine.Vector(0,0,-1000), Engine.Rotator(0,0,0), Engine.Vector(0.5,0.5,0.5)
local callBack = function(elementId)
--将这些元素添加到未激活的对象池中
table.insert(inactivePool,elementId)
end
Element:SpawnElement(Element.SPAWN_SOURCE.Config, 1101008001004504, callBack, pos, rot, scale,true)
end
--延迟1秒后开始
TimerManager:AddTimer(1,function()
--每隔一定时间设置一个钻石元素
TimerManager:AddLoopTimer(2,function()
--当未激活的对象池中存在元素时
if #inactivePool>0 then
--从未激活对象池中移除一个元素
currentDiamond=table.remove(inactivePool,1)
--将该元素添加到激活对象池中
table.insert(activePool,currentDiamond)
--随机设置该元素的位置
Element:SetPosition(currentDiamond,Engine.Vector(UMath:GetRandomInt(-500,500) ,UMath:GetRandomInt(-500,500),0),Element.COORDINATE.World)
end
end)
end)
--玩家碰撞钻石的事件
System:RegisterEvent(\
Events.ON_PLAYER_TOUCH_ELEMENT,\
function (playerId, elementId) --playerId = 玩家id, elementId = 元件id
--如果碰撞的元件在激活的对象池中
if Array:IsContainValue(activePool, elementId) then
--找到这个元素对应的index,然后从激活的对象池中移除
local index=Array:GetIndexByValue(activePool,elementId)
table.remove(activePool, index)
--再添加到未激活的对象池中
table.insert(inactivePool, elementId)
--最后将对象设置初始位置
Element:SetPosition(elementId,Engine.Vector(0,0,-1000),Element.COORDINATE.World)
end
end\
)

游戏中不再需要使用的资源尽量销毁。

例如特效资源,如果播放完成后没有销毁(且不再使用),不断累积也会对性能有不小影响。针对这种情况,建议及时销毁掉游戏中不再使用的资源。

备注:针对一次性使用的特效,建议播放时接口参数都选择【自动销毁】;如果特效可能重复播放,建议记录特效id并管理。

--下面是一个示例(如何确定特效是否销毁了)
function GameClient:OnStart()
local particleId
--创建一个粒子特效(注意,这里没有自动销毁)
TimerManager:AddTimer(1,function()
particleId= Particle:PlayAtPosition(18,Engine.Vector(0,0,0),1,false,0)
end)
--5秒后,检查场景中是否还有上面的粒子特效
TimerManager:AddTimer(5,function()
local bExist = Particle:IsParticleEffectExist(particleId)
if bExist then
--如果粒子还存在,则销毁
Particle:StopParticle(particleId)
Log:PrintLog("粒子已销毁")
end
end)
end