Due to the rapid pace of change some links or details may no longer be correct.
New Phaser Forum, Keyboard Updates, new Animation Events, Erasing Render Textures and more!
Welcome to the final Dev Log of 2018. I'm not going to get all retrospective on you. Not this issue, at least. Even so, it's been quite the year! I wanted to cover what I've been working on for the past couple of weeks and what's left to be done before I can release Phaser 3.16. First of all though, let's talk about the forum.
New Phaser Forum
Since Phaser first began, we have maintained a board over at the HTML5 Game Devs Forum. This has been great for many years but we felt it was time to move to our own home. I had looked at Discourse before. As a forum app, it seemed light-years ahead of what Invision, the software at HTML5GD, could do. Sadly, it was just too expensive for us to consider. So all those power features seemed out of reach.
Which is why, when Discourse offered Phaser a free managed installation, as part of their initiative to help open source projects, we jumped at the chance! :)
I had the option to move over all of the posts from the HTML5GD forum to the new one. Which would have given us a rich vein of archived material to draw on. However, it was impossible to determine which users had registered on HTML5GD just for Phaser, rather than one of the other projects hosted there, like Babylon.js. I also felt that, as a user, I wouldn’t be happy to find my account details moved from one site to another without my permission. With those two facts in mind, I took the decision to start the new forum afresh.
So, please join the new Phaser forum. It’s still a bit of a blank slate, albeit a much more powerful one than before! It has proper integration with GitHub, Patreon and other services. A really neat notification system, genuinely useful email summaries and a lot more. I will keep the HTML5GD Phaser boards open, but they will eventually be moved to be read-only.
The Copper Bones Key
In the last Dev Log, I talked about how the work on the Scale Manager required me to recode parts of the input system to deal with the new scale modes. As a continuation of this work I also revisited the way the Keyboard Events are handled in Phaser 3.
The specificity of the Keyboard events has been changed to allow you more control over event handling. Previously, the Keyboard Plugin would emit the global
keydown\_CODE event first (where CODE was a keycode string, like
keydown\_A), then it would emit the global
keydown event. In previous versions,
Key objects, created via
this.input.keyboard.addKey(), didn't emit events.
Key class now extends EventEmitter and emits two new events directly:
up. This means you can listen for an event from a Key you've created, i.e.:
The order has also now changed. If it exists, the Key object will dispatch its
down event first. Then the Keyboard Plugin will dispatch
keydown\_CODE and finally the least specific of them all,
keydown will be dispatched.
You also now have the ability to cancel this at any stage either on a local or global level. All events handlers are sent an event object which you can call
event.stopImmediatePropagation() on. This will immediately stop any further listeners from being invoked in the current Scene. Therefore, if you call
stopImmediatePropagation() in the
Key.on handler, then the Keyboard Plugin will not emit either the
keydown global events. You can also call
stopImmediatePropagation() during the
keydown\_CODE handler, to stop it reaching the global
keydown handler. As
keydown is last, calling it there has no effect.
There is also the
stopPropagation() function. This works in the same way as
stopImmediatePropagation but instead of being local, it works across all of the Scenes in your game. For example, if you had 3 active Scenes (A, B and C, with A at the top of the Scene list), all listening for the same key, calling
stopPropagation() in Scene A would stop the event from reaching any handlers in Scenes B or C. Remember that events flow down the Scene list from top to bottom. So, the top-most rendering Scene in the Scene list has priority over any Scene below it.
All the above also works for
New in 3.16 is the ability to receive a global
keyup event from any key on the keyboard. Previously, it would only emit the event if it came from one of the keys listed in the KeyCodes file. Now, those global events will fire for any key, regardless of location.
Key capturing is the way in which you stop a keyboard DOM event from activating anything else in the browser by calling
preventDefault on it. For example, in tall web pages, pressing the SPACE BAR causes the page to scroll down. Obviously, if this is also the fire or jump button in your game, you don't want this to happen. So the key needs to be 'captured' to prevent it. Equally, you may wish to also capture the arrow keys, for similar reasons. Key capturing is done on a global level. If you set-up the capture of a key in one Scene, it will be captured globally across the whole game.
In 3.16 you now do this using the new
KeyboardPlugin.addCapture method. This takes keycodes as its argument. You can either pass in a single key code (i.e. 32 for the Space Bar), an array of key codes, or a comma-delimited string - in which case the string is parsed and each code it can work out is captured.
To remove a capture you can use the
KeyboardPlugin.removeCapture method, which takes the same style arguments as adding captures. To clear all captures call
KeyboardPlugin.clearCaptures. Again, remember that these actions are global.
You can also temporarily enable and disable capturing using
KeyboardPlugin.disableGlobalCapture. This means if you set-up a bunch of key captures, but then need to disable them all for a while (perhaps you swap focus to a DOM text field), you can call
disableGlobalCapture to do this, and when finished in the DOM you can enable captures again with
enableGlobalCapture, without having to clear and re-create them all.
Default captures can be defined in the Game Config in the
input.keyboard.captures object. The captures are actually stored in the
KeyboardManager class. The
KeyboardPlugin is just a proxy to methods in the Keyboard Manager, but is how you should interface with it.
There are a bunch of other smaller changes to the Key classes to make them easier to use. Please check out the full Change Log for details if you're curious, or wait for the 3.16 release.
I added this feature a while ago, but haven't discussed it in a Dev Log yet. It's a new blend mode called ERASE which, as its name implies, allows you to erase something from the screen. This is mostly useful when combined with Render Textures or Canvas Textures. It allows you to literally 'eat away' at a texture without using any kind of expensive mask, and it works the same in WebGL as it does in Canvas.
Click around (or 'paint') in the following example to see the effect in action:
Render Textures, specifically, have a new 'erase' method that allows you to do this quickly and easily:
It can accept a texture based Game Object, like the Image in the code above, or any of the Shape objects (like a circle or polygon), or indeed anything else a Render Texture can draw to itself. It just uses an erase blend mode internally, which causes that object to eat-away at whatever was on the Render Texture previously.
This is particularly useful for certain types of special effects. For example, you could have a Render Texture filled with a solid color and then wipe it away using a brush and tween sequence to create really nifty reveal transitions. Or it could form a part of a game element, such as blowing away parts of a texture based object.
Following a similar thread I've also made a number of updates to the Canvas Texture class. First, is the addition of the 'drawFrame' method. This allows you to draw a texture frame, from any loaded atlas or sprite sheet, directly to the Canvas Texture. To go with this, I also added 'getPixels', which lets you grab a region of the CanvasTexture and return all of the pixels within it. Combined with the new 'setPixel' method it means you can now quickly modify the contents of the texture using direct methods. Here's an animated gif showing a player literally blowing pixels away out of a ufo:
This is now trivial to achieve and can be put to great use in a matter of ways. The above gif is from a demo that will be part of the Phaser Backers Examples package in February. In December I released the first batch of Backers Examples, with another pack due on January 4th. Eventually, these examples will be released publically, too. For now, though, they're a perk for those who go above and beyond and genuinely help Phaser to survive.
In 3.16 I also made a number of subtle, but powerful, changes to the way animations are handled.
When creating an animation it will no longer raise a console warning if the animation key is already in use. Instead, it will return the animation belonging to that key. A brand new animation will only be created if the key isn't already in use. When this happens, the
add event is emitted by the Animation Manager. If no event is emitted, the animation already existed. You can also use the new AnimationManager.exists method to check to see if an animation is already present using the given key.
More significantly, the Animation class now extends the Event Emitter and dispatches events itself. This allows you to listen for events from a specific Animation, rather than via a Game Object. This is handy, for example, if you had an explosion animation that you wanted to trigger a sound effect when it started. You can set this up on the animation directly, rather than a Sprite playing it, which makes more logical sense.
Animations will emit the following events: 'start', when it is played by any Game Object, either forwards or in reverse. 'restart' when any Game Object restarts it and 'complete' when it finishes playing on any Game Object.
Not to be outdone, Game Objects now emit some new events, too. They will emit events that carry the name of the animation within them. For example, if you had an animation called 'explode' then a Game Object will now emit 'animationstart-explode' when it starts playing, or 'animationcomplete-explode' when it finishes. This allows you to listen for animation events on a Game Object level, rather than global one, right down to a specific animation instance.
Finally the last new feature is called 'chain'. This allows you to line-up another animation to start playing as soon as the current one stops, no matter how it stops (either by reaching its natural end, or directly by having stop called on it). You can chain a new animation at any point, including before the current one starts playing, during it, or when it ends (via its
animationcomplete callback). Chained animations are specific to a Game Object, meaning different Game Objects can have different chained animations without impacting the global animation they're playing.
All in all, I feel like animations are a lot more flexible now, at least in the way in which you can deal with their events. You can choose to handle things on a global, local or instance level, whichever your coding style prefers.
3.16 in the New Year
I'm a bit frustrated that I didn't get to release 3.16 this month. It's extremely close to being finished. This week I completed the new Size component, which is very much inspired by the Aspect Ratio Fitter from Unity. It allows you to define a width and height, lock the aspect ratio between them and then scale it based on a variety of modes - such as 'fit', 'envelope', 'width takes priority' and more. In short, it's really useful for handling the scaling of objects, which is why it's a class in its own right. It's also now the core of the new Scale Manager and has allowed me to remove a whole bunch of duplicate code and properties.
Finishing the Scale Manager is my absolute priority when I return in January and then 3.16 will be released. I'll do my best to make sure that future updates are faster, although that should be a lot easier because future updates aren't likely to be as far reaching and involved as the Scale Manager was! When you try inserting an octopus into the tank, it's hard not to get its tentacles everywhere, and that's very much what it feels like the Scale Manager was. Still, I'm very happy with where I am, and don't believe there is all that much work left to be done before I can release.
Beyond that, there's plenty more to get on with. The GitHub issues list has backed-up a bit, so could do with a clear down. The Examples repo is being worked on too, and is having a make-over. You can track the changes in its new branch, but suffice to say, when we merge it there will no longer be any examples that don't run. They'll all have proper names and be in more sensible folders where required. This is a piece of work I've got someone helping with as it just really needed doing, so I diverted some funds to pay for this as I knew I didn't have time myself. When complete we can release the examples onto the Phaser web site and labs can return to being what its name implies: the Phaser labs, specifically for experiments and tests.
That just leaves me to wish everyone a very Merry Christmas and Happy New Year. I won't be around that much over the next 10 days as I'll be playing with the kids and enjoying some time to revitilize my grey cells. Personally, I'll be digging in to Just Cause 4 (I absolutely love that series), and Belen convinced me to try Assassins Creed: Odyssey, too :) A little gaming, lots of family time and back in the office before you know it, raring to finish off 3.16. Take care everyone!