Hello, and welcome to a new feature in KiwiJS v1.2.0! Today we’ll be doing the Time Warp (again?), taking a long look at Clock
s and all the new things they can do to control your game.
The Clock
code isn’t restricted to just clocks, of course. It includes upgrades for animation, timers, and tweening, which in turn can control just about any part of your game.
At the end of this article, we’ll discuss best practice when it comes to time. You can save yourself a lot of pain if you develop with a full understanding of time and the tools available to control it!
Time Hierarchy
We’ve given time a real overhaul in KiwiJS v1.2.0. Just how major an upgrade is this? Let’s take a look.
Old Time
Once upon a time, there was one clock, and it was either playing or not playing. Time flowed something like this:
- Master Clock
- Game Clock
- Animations
- Timers
- Tween Manager
There are several issues with this system. For example, say you want to pause the game. Pause the Game Clock, and all your animations and tweens will stop. But what if you have a beautiful animated UI, with tweened transitions and animated buttons and special effects? None of that will play while the Game Clock is paused. You could always put a check on everything that refers to a universal pause variable, but now we’re back to something a lot more complex than this.game.time.clock.pause()
.
New Time
In KiwiJS v.1.2.0, we’ve turned the clocks up to 11. Here’s an example of a new hierarchy:
- Master Clock
- World Clock
- Animations
- Timers
- Tween Manager
- Rate
- UI Clock
- Animations
- Timers
- Tween Manager
- Rate
- …
Immediately, you can see that everything is better. Pause the game world by pausing the world clock, and the UI remains intact.
But you can do even more cool stuff.
Using Clocks
Clock Management
To create a new Clock
, use the game’s clock manager game.time
.
|
// Run from a State.create method var worldClock = this.game.time.addClock( "world" ); var uiClock = this.game.time.addClock( "ui" ); |
And as simple as that, you’ve got two new clocks to play with.
Clock Properties
We’ve added some useful properties and methods to the Clock
. These make it much easier to create smooth movement – and some other tricks.
rate
The rate
property was first introduced in v1.1.0 on ClockManager
. It proved so useful that we’ve expanded it onto the clocks themselves.
rate
is simply actual frame time divided by desired frame time. If the two are identical, rate
= 1. If actual frame time is taking too long, rate
> 1.When the frame rate drops, rate
increases proportionally. Multiplying by rate
makes your properties change at the same rate no matter what frame rate you encounter.
We strongly advise that any animation over time incorporate * rate
to keep it smooth. This will also allow it to take advantage of other new time features.
|
// Example of rate in use // This assumes an Entity, which always has an onboard clock this.transform.rotation += 0.01 * this.clock.rate; |
maxFrameDuration
Using rate
lets you skip dead time and get on to where you should be by now. But sometimes that skip is just too massive. For example, if your browser hiccups and freezes for six seconds, you might not want to skip that far ahead in time. That’s long enough for characters to move through walls, jump halfway across the screen, and otherwise exhibit unwanted behavior.
We’ve implemented maxFrameDuration
to fix these issues. By default, maxFrameDuration
= -1 and has no effect. If set to a positive number of milliseconds, however, it will clamp the effective frame duration to that level. This prevents major skips.
We advise that you set maxFrameDuration
during a state create method, not earlier. If you try to set it just after game creation, you may find that clocks have not yet been created.
|
// During state create // Set the game to treat skips as 0.5 seconds at most this.game.time.clock.maxFrameDuration = 500; |
elapsed()
This method returns the number of clock units (default: seconds) since the Clock
was started. This does not include time spent paused. In the event that time runs backwards, the total will diminish.
timeScale
Pay attention; this is awesome.
The timeScale
parameter controls how fast time elapses on that clock. By default it is 1, which means time advances into the future at 1 second per second.
You can change timeScale
. Set it to 2 to induce fast-forward. Set it to 0.5 to go into slow motion. Set it to 0 to freeze time. Set it to -1 to rewind time.
This is another reason why you should use rate
in all your animations. If you are using rate
, it will be affected by timeScale
.
If you are running different parts of your game on different clocks, you can change the timeScale
to create targeted temporal distortions. For example, you might drop a time bomb on an enemy and freeze them in place while the player’s speed is unaffected. Or you might send the whole game into slow-mo while your user interface remains at full speed.
The timeScale
isn’t magic, and it isn’t a timeline. You can’t undo events or reverse physics. At least, not with the core library. timeScale
is the foundation upon which you can build the most sublime paradox, should you desire it.
setTimeout()
KiwiJS has a robust timing system, but it was always a little bit tricky to use. Here’s the old way of making a single-use timer event that prints a message after 1 second:
|
var clock = this.game.time.clock; var timer = clock.createTimer( "timeoutTimer", 1 ); timer.createTimerEvent( TimerEvent.TIMER_STOP, function() { // The only bit we care about console.log( "Time's Up" ); // Clean up the elapsed timer clock.removeTimer( timer ); }); } ); timer.start(); |
That’s a bit of an eyeful, isn’t it?
To fix this, we’ve taken inspiration from standard JavaScript functions. Clocks now have a setTimeout()
method, which works very much like the normal browser version. It even uses milliseconds. Now you can simply call:
|
var func = function() { console.log( "Time's Up" ); }; var timer = this.game.time.clock.setTimeout( func, 1000 ); |
Much nicer.
The main difference with the KiwiJS setTimeout()
is that it takes an extra optional parameter:
|
setTimeout( callback, timeout, context, ...args ) |
The context
parameter defines the object which is this
inside the callback. Experienced JavaScript developers will appreciate that this
is often not this
, so we make it easy.
You may also specify any number of arguments after context. These will be passed to the callback.
setInterval()
Just like setTimeout, setInterval()
mimics the JavaScript command. It repeats a given callback function once per given number of milliseconds.
|
var func = function() { console.log( "...time loop. Help, I'm stuck in a..." ); }; var timer = this.game.time.clock.setTimeout( func, 1000 ); |
This will simply repeat the callback once every second forever and ever.
As both setTimeout()
and setInterval()
return a Timer
object, you can store it and use it to clear the timer later. Unlike basic JavaScript, there is no “clearInterval” command. You should simply use the standard Timer management systems:
|
this.game.time.clock.removeTimer( timer ); |
Using Other Time-Based Features
Clocks are cool, but they just give out time. Other features actually use it. Let’s take a look at some of the new things you can do in v1.2.0 with Animation
, Timer
, and Tween
.
Animation
An Animation
is a single sequence of spritesheet cells. It is stored in an AnimationManager
. Upon playback, the game will display cells one by one, switching at a particular time interval.
There’s that keyword: time. Yes, an animation is controlled by a Clock
. In v1.2.0, you can alter the flow of time, and the animation will play back at the altered speed. You can even reverse time, and animations will play backwards.
Note that if your animation does not loop and plays back to its own beginning, it will dispatch onComplete
and stop.
In v1.2.0, animations will automatically use the Clock
of their entity. If you set the Clock
directly on an animation, it will use that clock instead.
(In more technical terms, an animation will use a clock if it is set. If its clock is instead set to null
, it will attempt to use this._parent.entity.clock
; that is, the Entity
of an AnimationManager
. Animations are created with clock null
, so this behavior is default.)
Timer
The Timer
object executes TimerEvent
objects after a time interval. We’ve added helpers to make Timer
use simpler (see Clock.setTimeout
and Clock.setInterval
, above), and those should make everything much easier. However, there are some advanced tips that you might find useful.
Timers work on clocks. This means that you can pause and resume clocks, and all timers on that clock will respect that.
You can also alter the timeScale
of a clock, and its timers will also respect that. There are some non-obvious consequences of this. When time runs backwards, a Timer
will start “un-counting”. It will not fire events when played backwards.
Further, if a Timer
rewinds to before its start time, it will clear its events and stop itself. This reflects the fact that, when time goes forwards again, game logic will probably add these events and start the timer again. Note that this can cause “orphan timers” to appear. An “orphan timer” is a timer that was scheduled to remove itself, such as in a setTimeout
, but was rewound and cleared itself, so it will never remove itself. That orphan will sit on the clock doing nothing forever. If you are using negative time, you must audit your timers, or you may get unexpected accumulation and slowdown.
Tween and TweenManager
A Tween
is a one-off transition from one number to another. It’s usually used for animation, but it can be applied to any number on any object.
Tweens use time. In previous versions of KiwiJS, they used game.time.clock
exclusively. We’ve upgraded them in v1.2.0 to use any clock.
Tween are created on a TweenManager
, so that’s where we set the clock. For example, if you were creating separate tweens for world and UI space, you might do this:
|
// Within a State.create method... var clocks = {}; clocks.world = this.game.time.addClock( "world" ); clocks.ui = this.game.time.addClock( "ui" ); var tweens = {}; tweens.world = new Kiwi.Animations.Tweens.TweenManager( this.game, clocks.world ); tweens.ui = new Kiwi.Animations.Tweens.TweenManager( this.game, clocks.ui ); |
You may now call these new TweenManagers to obtain tweens that use custom clocks. You can pause, resume, and timeScale these clocks as you wish, and the tweens will follow along.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
// Create a tween on mySprite var tween = tweens.world.create( mySprite ); // Configure the tween var destination = { rotation: Math.PI, x: 100, y: 200 }; var duration = 1000; var tweenType = Kiwi.Animations.Tweens.Easing.Linear.None; tween.to( destination, duration, tweenType ); tween.start(); // You may also start the Tween automatically by adding "true" // to the end of its parameters. tween.to( destination, duration, tweenType, true ); // No need to call "start" |
Because a Tween is a one-off, it should not act outside the bounds of its duration. Accordingly, if you rewind time to before the Tween began, it will consider itself finished, and signal itself for removal. (It will not fire its onCompleteCallback
signal, however. This will only fire if it finishes at the end.)
Best Practice
We’ve spent a lot of time working with time, and we have some recommendations that will help you create to your full potential. These guidelines will help you create an integrated, functional timespace that is easy to control and to understand. You don’t need to be making a time-warp game to use these practices; they’re a good idea even for basic animations.
Use Time for Everything
You may think this goes without saying, but it’s an important guideline. There are many ways to animate your game world, and all of them seem to give you time-based animations, but some are more reliable than others.
For example, if you have a line that reads this.rotation += 0.01
, you might think that this will rotate an object at a steady rate. It will fire once per frame, and that’s nice and even, right?
I’m afraid you’ve fallen into my trap. Frames are supposed to be even, but in practice they often aren’t. Browser performance can change based on system activity, background pages, etc. This rotation might slow and chop at unexpected times.
Use Rate
If you iterate a number on a per-frame basis, such as in this.rotation += 0.01
, you should always multiply by clock.rate
. This number will compensate for any slowdown on the previous frame.
|
// Best practice per-frame iteration this.rotation += 0.01 * this.clock.rate; |
Ensure that you use the rate
of a clock. The default game.time.rate
cannot be controlled as well as clock rates.
Note that rate cannot tell you how long the current frame will take to render, because it hasn’t happened yet. We must estimate from the previous frame. However, it has the sum effect of keeping everything very close to where it should be.
Use Tweens
Tweens are handy aids. Whenever you want to perform a single movement, consider using a tween. For example, if you want to lower the drawbridge on a castle, use a tween. There are many flavours available, which you can see in our tween tutorial.
Use Timers
When you want to schedule events, just use Clock.setTimeout
or Clock.setInterval
. They’re easy and reliable, and they work with game clocks.
Use Custom Clocks
It’s hard to retroactively add clocks into a scene, so if you think you’ll ever need more than one, do it at the start. We recommend creating “world” and “ui” clocks at a minimum.
When you create a clock, you should also create an associated TweenManager
that uses that clock.
When you create entities, make sure you set their clock
property to the correct clock. This will ensure that their animations use the correct clock as well. It will also give them onboard access to the custom clock, which is useful for accessing rate
.
Pause Efficiently
Now that everything in your scene is controlled by time, you can ensure that they pause with a single command.
|
// Pause a clock clock.pause(); // Alternate pause method clock.timeScale = 0; // Resume a clock clock.resume(); // Or if using alternate method clock.timeScale = 1; |
As of KiwiJS v1.2.1, these are functionally identical, but each have unique considerations. The timeScale
method allows you to tween pause on and off, creating a brief slo-mo effect. However, if you are also using time manipulation elsewhere in your scene, you may find it difficult to track both paused and manipulated time. We recommend you use clock.pause()
unless otherwise necessary.
Use maxFrameDuration on All Clocks
The maxFrameDuration
property can prevent excessive skipping if the frame rate is very low. We find that this will often happen on the first frame of a scene, right after loading, or upon returning to a web page game after visiting another tab. Game objects can jump far outside their predicted boundaries, disrupting gameplay.
Use maxFrameDuration = 500
to limit this skip to half a second. This is usually enough to prevent objects from jumping too far, but still permits smooth movement even in the event of very slow or irregular frame rates. Don’t forget that it is a per-clock property; you should set it on all your clocks at once during State.create
.
It Is Time
You have learned much about time management today. We hope that it is useful. Understanding the nature of time gives you access to tools that make your life as a developer much easier. Please give them a go, and let us know how you get on! And remember – where we’re going, we don’t need roads. (Oh yeah, we’re subtle.)
Next time: All the colors of the rainbow, or at least anything that fits into an RGBA color space.
Recent Comments