编程元件性能优化实战
常用验证性能的方法
游戏创作过程中,我们经常会遇到需要优化性能的场合(例如试玩中发现游戏卡顿)。
如果您需要监测游戏帧率,那么可以前往【资源社区】添加【帧率监测】模组到您的的地图中。
除此之外,下面介绍一种,使用CPU时间来辅助判断性能问题的方法。
--获取起始时间local startTime=TimerManager:GetClock()Log:PrintServerLog("time start:"..startTime)local endTimelocal count=0--创建100个方块for i = 1, 10, 1 dofor j = 1, 10,1 dolocal 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-startTimeLog:PrintServerLog("time:"..time)endendElement:SpawnElement(Element.SPAWN_SOURCE.Config, 1101002001034000, callBack, pos, rot, scale,true)endend--参考上面的思路,就可以方便计算出某个方法或一系列逻辑的运算时间。检查短时间内频繁调用的函数
在游戏中,短时间(例如每帧)如果计算量较大,那么可能造成性能负担。
例如,在Start函数中,往往需要处理大量逻辑。针对这种情况,可以将部分逻辑延迟1帧(或若干帧)执行(使用TimerManager:AddFrame)
例如,经常在update函数(每帧调用)中处理大量的逻辑。针对这种情况,可以根据实际需求,每隔一段时间调用一次函数,以优化性能。
优化前:
--思路:在update函数中,每一帧都获取角色Id,并进行射线检测-- 游戏更新function GameClient:OnUpdate(deltaTime)--获取玩家角色Idlocal localPlayerId=Character:GetLocalPlayerId()--射线检测玩家角色面前的元件local elementId=PlayInteractive:GetHitResultWithRaycast(PlayInteractive.HIT_TYPE.Element,\Character:GetPosition(localPlayerId),\Character:GetForward(localPlayerId)*100+Character:GetPosition(localPlayerId),\true,\1)--输出元件IdLog:PrintLog("elementId",elementId)end优化后:
--思路:增加计时器,每0.2秒才执行一次射线检测在update函数外,定义变量(玩家角色Id,经过的时间)local localPlayerIdlocal 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)--输出元件IdLog:PrintLog("elementId",elementId)--将累积时间重置为0timePassed=0end
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)endElement: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)endend)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)endend\)游戏中不再需要使用的资源尽量销毁。
例如特效资源,如果播放完成后没有销毁(且不再使用),不断累积也会对性能有不小影响。针对这种情况,建议及时销毁掉游戏中不再使用的资源。
备注:针对一次性使用的特效,建议播放时接口参数都选择【自动销毁】;如果特效可能重复播放,建议记录特效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("粒子已销毁")endend)
end