In game development it is a common requirement to be able to schedule the execution of your code. Normally when you call a function the code will run from start to end and return in a single frame. There are cases where something has to happen over a sequence of frames or after a scheduled delay. Example cases:
- Pause the code execution for a specified amount of time.
- Change to the next level 3 seconds after the user has reached the finish line.
- Calculate something complex over a number of frames that would normally make the game hang.
In this tutorial we will learn how to use Schedules and the special OnTick method to execute our code at an appropriate time.
In Unity a similar concept exists called Coroutines, allowing you to schedule the execution of your code.
The Coroutine class in Unity is similar to the Crayta Schedule method. They can both pause and resume their operation, where they left off, on the following or on a coming frame. A Unity Coroutine runs in the same thread, in a similar manner in Crayta the Schedule method will execute in sequence with the main thread.
How to use Schedules?
Here is a simple example on how to use a Schedule in Crayta:
function MyScript:OnInteract() self:Schedule( function() Wait(1.0) Print("Interact was pressed a second ago...") Wait(1.0) Print("Interact was pressed two seconds ago...") Wait(1.0) Print("Interact was pressed three seconds ago...") end) end
This method will print a couple of messages in the console but instead of printing them at the same time, it will print them with a second difference one from the other.
Schedule is a method available in the ScriptComponent class, meaning that it is available to be used in any Crayta script.
In the example above we used the Wait() method which is available to be used inside the callback function that is executed by the Schedule. As the name implies this method forces the code execution to pause and resume at a later point.
There are two ways to use this method:
- Wait( duration ) where duration is the time in seconds to pause in minimum before resuming the execution.
- Wait() pause the execution for a single frame and then resume in the next frame.
Both methods return the exact time in seconds taken from the pause instant till the method resumed. That is useful to implement non-linear smooth animations.
The code below will animate an entity moving periodically up and down when placed in an :OnTick(dt) function:
local angle = 0 while true do local dt = Wait() angle = angle + dt local position = Vector.New(0, 0, 100 * math.sin(angle)) self:GetEntity():SetPosition(position) end
There is also another method available to be used inside Schedule, IsInSchedule(). As the name suggests this will return true or false, depending if the code executing is running in a schedule or not. This is quite useful if you are calling methods in a schedule that you also call, with a different behavior, in other parts of your script.
If required you can easily cancel any schedule by calling the Cancel() method in the parent script. When you call the Schedule() method a handler is returned which can be stored in a variable or script property for later use.
That handle can be used at any point by passing it in the Cancel( handler ) method to stop immediately the execution of that schedule.
function MyScript:Init() self.mySchedule = self:Schedule( function() -- animating the current entity end) end function MyScript:OnInteract() -- this will stop the animation as soon as the player interacts with it self:Cancel(self.mySchedule) end
How to use OnTick?
OnTick is a method available in all scripts, that is updated automatically by Crayta on each frame. The deltaTime is in seconds, which is the time elapsed since the last frame was rendered, and is made available as an argument to be used by the executing code (e.g. to make smooth animations).
Here is an example using OnTick to infinitely rotate an entity around its Z axis, which is useful for items such as powerups:
function PowerupProp:ClientInit(dt) Self.rotation = Rotation.New(0,0,0) end function PowerupProp:ClientOnTick(dt) self.rotation.yaw = self.rotation.yaw + dt * self.properties.rotationSpeed self:GetEntity():SetRotation(self.rotation) end
You will notice that instead of using OnTick(), we used a variation of it called ClientOnTick(). Much like the Init function the OnTick comes with the same three variants depending on the context it is used in:
- OnTick (executed in the server)
- ClientOnTick (executed in all clients)
- LocalOnTick (executed in the local client)
Usually for objects that require cosmetic animations, when the movement of the entity doesn’t affect the gameplay, it is better to do it on each client separately, using ClientOnTick().
For animating something specific to a certain player, that is not required to be in sync or even visible to other players, LocalOnTick() can be used, but only on scripts attached to a User or Player template. That makes it useful for doing stuff in a Player’s HUD, but can’t be used to rotate a collectable.
When something should be synchronized across all connected players, OnTick() should be used.
Schedules vs OnTick
Both methods do something similar, execute code across several frames. So when to use each?
Schedules are more versatile. You have control over how often the code will execute, you can get the deltaTime elapsed and even stop the execution at any point. Also Schedules can be started at any point in your script code, so all contexts are supported in Schedules, too (Server, Client, Local).
Schedules ultimately can be configured to have the same behaviour as the OnTick method (using a while loop and a Wait).
OnTick is automatically called by Crayta, providing the deltaTime elapsed and it should be used when something should be executed per frame. For code that pauses or works occasionally Schedules can be a better alternative from a performance point of view.
Schedules are better used when you require exact control of when your code is executed. Also when you have to do something complex that would normally cause the the game to hang for a while. Putting that code in a Schedule and spreading calculations across a number of frame, can keep the frame rate constant.