任务计划程序¶
Time:10 分钟
任务计划程序负责在游戏运行时协调每帧中所有需要进行的工作,即使在游戏暂停时也不例外。其所协调的工作中包含侦测玩家输入、应用角色动画、 更新物理模拟及恢复处于 wait()
状态的脚本运行等。请一定要注意不要让任务计划程序超载,尤其是进行以下操作时:
使用自定义人物模型或输入方案时
自行为部件添加动画效果时(不使用
Animator
的情况下)大量使用精准物理时
频繁复制对象时
Frame(帧)¶
帧是游戏逻辑中的传输单元,每一帧中都会完成一定量的工作。在游戏中每秒内可发生多个帧,通常来讲,每秒帧数(FPS)越高,游戏性能也就越好。每帧内完成工作的效率越高,每秒内能够发生的帧数也就越多,而玩家体验也会因此提升。
RunService¶
向游戏添加每帧任务最直接的方法为使用 RunService
的下列成员:
RunService/BindToRenderStep|RunService:BindToRenderStep()
RunService/RenderStepped
RunService/Stepped
RunService/Heartbeat
在选择需要使用的成员时,一定要多加斟酌。
计划程序的优先级¶
任务计划程序将任务通过下列顺序进行分类与完成。有的任务在一帧中可能不会完成任何工作,而其它任务可能会在一帧中多次完成工作:
用户输入 — 输入(
UserInputService
)事件和绑定函数(ContextActionService/BindAction|ContextActionService:BindAction()
)将被优先处理。例:按下键盘键、鼠标移动、屏幕触控、手柄震动等。渲染 1.
RunService/BindToRenderStep|RunService:BindToRenderStep()
— 所有以这种方式进行绑定的函数将会以优先级顺序被调用。在内置 PlayerModule 中,镜头与角色控制脚本将按照enum/RenderPriority
决定的优先级顺序对回调进行绑定。 2.RunService/RenderStepped
事件被触发后,将会和所有事件一样调用连接函数,从最新连接的回调起开始运行。 3. 屏幕绘制 — 此时,游戏状态将会进行并行渲染。此后的更改将在下一帧进行渲染。复制对象获取任务 — 对传入属性变更与事件触发进行应用。
恢复等待状态 — 此时,任何当前处于
wait()
状态的运行中脚本将会在等待时间结束后进行恢复。等待恢复时间的下限为 0.03 秒,就是三十分之一秒。传递至spawn()
与delay()
的函数也会在此处运行。如果有线程需要在 0.1 秒后恢复,则会在之后的帧中进行恢复。Lua 垃圾收集器
模拟任务(如果游戏暂停则略过此步) 1. 内部旧版步骤
Explosion|Explosion
可造成地形破坏、击退及关节破坏。BodyMover|BodyMover
例:BodyPosition
、RocketPropulsion
、BodyGyro
等。Humanoid
发生状态更新,例:移动或死亡。Internal Animation Step — Animator 对
Motor6D/Transform
进行更新。将不会移动部件或更新已应用的关节偏移。 2.RunService/Stepped
事件被触发。 3. 内部物理步骤Motor6D — 对使用
Motor6D
连接部件的相对位置进行更新。世界步骤 — 更新部件接触并解决接触与约束。由于物理效果每 240 Hz 进行更新,所以每帧中可能会多次发生此步骤。所有世界步骤完成后,将会触发
BasePart/Touched
及相关物理事件。插值网络集合 — 对无所有者的体位置进行插值。例:
BasePart/SetNetworkOwner|BasePart:SetNetworkOwner()
。
RunService/Heartbeat
事件被触发。复制对象发送任务 — 对传出属性变更与事件触发进行发送。不会每帧执行。
指南规则¶
如果想要创建高性能效率的游戏,请参照以下准则:
除非必要,不要将函数绑定至渲染步骤(
BindToRenderStep/RenderStepped
)。此种行为将会延迟渲染线程,从而减少游戏的 FPS。只有输入后渲染前需要进行的任务才应该放置于此,例如镜头移动等。如果想要对顺序进行更为严格的控制,请使用RunService/BindToRenderStep|BindToRenderStep()
而非RunService/RenderStepped|RenderStepped
。无论任何时刻,尽量减少使用
wait()
的脚本数量。请避免使用while wait() do end
或while true do wait() end
,因为此两种构造并不能确保每帧或每个游戏步骤都会运行。可以使用RunService/Stepped|Stepped
或RunService/Heartbeat|Heartbeat
等事件进行替代,这些事件会对核心任务计划程序循环进行严格遵循。同理,请避免使用spawn()
或delay()
,因为其所使用的内部机制与wait()
相同(使用spawn()
时与协同程序库中的coroutine.wrap()
与coroutine.resume()
结合,效果更好)。RunService/Stepped|Stepped
在物理效果前发生,而RunService/Heartbeat|Heartbeat
在物理效果后发生。因此,对物理状态产生影响的游戏逻辑应该在RunService/Stepped|Stepped
中完成,例如设置部件的BasePart/Velocity|速度
等。与此相反,对物理状态进行依赖或反应的游戏逻辑应该在RunService/Heartbeat|Heartbeat
中进行处理,例如读取部件BasePart/Position|位置
以侦测其是否进入定义区域等。如果想要更改
Motor6D/Transform
,则需在RunService/Stepped|Stepped
对此进行处理。如果放置至其它步骤,则下一帧的Animator|Animator
将会对更改进行覆盖。即使Animator
不存在时,RunService/Stepped|Stepped
也是Motor6D/Transform
应用至部件位置前最后触发的 Lua 事件。
***Roblox官方链接:任务计划程序