Due to the rapid pace of change some links or details may no longer be correct.
TweenManager Planning and TileMap Renderer
Hello and welcome to this weeks update. First things first, the ever active Phaser CE has had another new release. They're now on Phaser CE v2.7.10, and the features and fixes keep coming thick and fast. I've started to see a number of games 'in the wild' built against Phaser CE too, which is really encouraging. One of the changes in this release is a big reduction in the npm package size, which in turn means Phaser CE is now being properly picked up by the two major JS CDNs, which is great news. More details in the README like usual.
How to kill a MacBook Pro
This week didn't exactly start off very well. Some of you may not realise, but the little company I run (Photon Storm) has its own offices in the town where I live. So my main work PC and a stupidly large retro gaming collection are kept out of our home. It gives me somewhere to concentrate, be inspired and leave things in whatever state I like at the end of the day. However, I also like working from home, and at this time of the year will often sit in the garden in the evening, hacking away at Phaser 3 on my MacBook Pro.
At least I did until my son accidentally tipped an entire pint glass of juice over it. The Mac was plugged into the mains at the time, and the juice didn't just splash off the case, it literally flowed up the rear heat vents and right inside. After the initial shock was over, and the drink had finished dripping out of the screen, I realised this wasn't going to be an easy fix. I let it dry out properly, did all the usual things, but noticed even the power connector was slightly black. My Mac was dead and sadly my ability to work from home was gone with it.
The price of a new Macbook Pro in the UK right now is insane. The prices are high anyway because, well, Apple. But they recently jacked them all up even higher because of Brexit. I couldn't justify replacing it, especially as I'm trying to run the company as lean as possible right now, turning down client work so we can focus 100% on getting Phaser 3 finished. In the end, I settled for replacing it with a Windows laptop at half the price of a Mac. It has almost twice the speed, and a proper NVIDIA GeForce 1050Ti GPU driving a nice 17" screen. I really didn't want to have to buy it, but I can't be in a position where I'm unable to work on Phaser from home. On the plus side, the new laptop also has an Intel HD GPU inside it, which is pretty terrible, making it a perfect testbed for Phaser 3 rendering tests :)
Phaser 3 Updates
This week I've been carrying on building the Tween Manager, and Felipe has been doing R&D on the new Tilemap renderer. I know what you're thinking, wasn't I working on the Tween Manager last week as well? And the answer is yes. It has certainly taken longer than I anticipated. Yet at the same time, it now has far more flexibility and features than I ever anticipated too. It has also gone through a few revisions as I tested it and wasn't happy with the performance.
Below you can see a photo I took of some planning sheets on my office floor. These are A3 pieces of paper! Don't worry that you can't read all of my notes, what is important is to get a sense of the complexity involved in planning out a friendly, yet powerful API for everyone. I find that when I'm stuck with something I like to grab the sketch pad, a pile of Sharpies, and just lay down on the floor and draw, write and doodle until it forms some kind of plan in my mind. Sometimes I can achieve the same thing just by working through the code in my IDE, but when there are many moving parts, like with a Tween system, actually physically writing them out helps me a lot.
I'm now on approach #3 on how the data structures should be organised internally. When you think about a tween you typically think of a single object, like a Sprite, having one or more properties tweened - such as its position or scale. In reality, it's quite a bit more involved than that. For example, I wanted the new system to be able to tween whole groups of Sprites, not just a single instance. So now you can pass in an array of targets and have each of them updated.
I also wanted for properties to have their own attributes. For example, when you define a Tween using the current v2 system, or with a package like TweenMax, you set the type of ease and duration for the Tween as a whole. But what if you wanted to tween an object along its x-axis for 4 seconds using a Quadratic ease, but also on its y-axis using a Bounce that lasted for 2 seconds? Right now you'd have to use 2 tweens and chain them together. In TweenMax you'd have to use a Timeline object instead. Either way, it's messy. So I was adamant you'd be able to do this in your code:
Click the image above to see it in action. It's a pretty nice effect, made possible by being able to specify specific attributes for the actual property being tweened, i.e. the x property above has a duration of 4000ms. You can also define 'global' values. So if you added a property called duration at the top level and gave it a value of 3000, every property being tweened would have a default duration of 3000ms, unless it overrides it itself.
Values for properties can be static like in the example above the target will tween to an x position of 700, because that is what's defined. However, values can also be relative:
Here we're tweening an array of images. The final x coordinate will be +600, so whatever their current x coordinate is, plus another 600 pixels. Their final y coordinate is 2x their current one. So if image1 is at y200 it will end at y400. Relative modifiers include addition, subtraction, multiplication and division, relative to each individual target.
The ease itself has had quite an upgrade. It can now be a function directly:
Above is a custom ease function (again, click it to run it). There is also an 'easeParams' property, which allows you to pass in custom values to an ease function. For example, you could now control the force of the overshoot in the 'Back' ease or the amplitude and force of the Elastic ease.
All time-based properties can be functions now as well, such as in the code below:
Above you can see that the delay property is a function. It is called when the Tween starts playing, and it passes in the current index of the target, the total number of targets, and a reference to the target instance. The function just has to return a value. In the code above it returns a delay of 100ms * index. The end result is a staggered start time for each target, creating a pretty nice effect in one single tween.
You can now finally play, stop, pause and resume tweens at any point. I also added the ability to Seek them, to any point in their duration, based on either progress or a time value. I didn't have time to create demos for these this week, but I'll link to them next issue.
As you can see Tweens have come on a long way from v2. They're stable, powerful and use a nice expressive configuration object, instead of an insane number of method arguments. I still want to add in the ability for you to create basic Timelines, and I need to wire-up the tween events, but otherwise, I'm very happy with how flexible they are now. I believe there is a fine line between a tween engine designed for general purpose use (like TweenMax) and one built specifically for games, hopefully this new system brings across the power features of one into the other.
Tilemap Rendering R&D
Felipe has been working on investigating different ways of rendering tilemaps in WebGL this week. The aim was to spend a week exploring different methods, and then spend the next couple of weeks implementing the eventual method we choose.
He came up with four different approaches, and as it turned out two of them proved to be equally powerful in different situations, each suitable for different styles of game. So rather than force one upon you, we're going to include them both in v3 and let you decide which is best for your game. I'll let him explain further:
1. Tilemap Texture
The main idea for this was to encode the tilemap texture index into another texture. This way you can simply draw a single quad and when executing the fragment shader you can unpack the tile coordinates and then use it to output the correct pixel. The problem with this approach is that you can easily just bake the real tilemap into a texture.
2. Render Tilemap to Texture
This approach was an attempt to reduce the number of vertices pushed to the GPU every frame. What we did here was simply to render the whole map to a texture and later just render a single quad together with that texture. The problem with this approach are GPU texture size limits (especially on mobile) and rendering unnecessary pixels. It's also not very flexible, as once the texture is baked you can't then easily change it.
3. Immediate Tilemap Rendering
This approach is a bit like rendering with canvas. We push the vertices up to the GPU in the same way that the BlitterBatch and SpriteBatch work. Basically, we fill the entire vertex data array with the vertices of the tilemap every frame. This, of course, is slow, especially because it requires a sync point between the CPU and GPU so we can upload the vertex and element buffers resources. However, it's very flexible. You can modify the map data in real-time, which is essential if you're making the sort of game where the map needs to change, or you want to create an 'endless' world. This mode won't be able to fill really large maps quickly, but for most sorts of game it will be more than enough, so we're including it as a 'Dynamic Tilemap Renderer' mode.
4. Pre Recorded Tilemap Rendering
This approach is similar to Immediate mode but instead of filling and updating the vertex buffer resources every frame we only do it at initialization. It's just like rendering static meshes. This allows us to avoid doing CPU-GPU sync and just submit glDraw commands with N to N + M vertices. Since tiles are in sequential order culling can also be done easily. The problem with this approach is the lack of dynamism on the map. As you'd expect though, it's crazy fast. We used what we would consider an 'extreme' test map of 150 x 10,000 tiles (1.5 million tiles in total) and it rendered in a single draw call at a solid 60fps on dedicated GPUs. On my rubbish Intel HD GPU it managed it at 45fps. Not many games will need 1.5 million tiles however, so if you bring this approach back down into the realm of a normal title, it should cope even on integrated graphics.
The end result of this research is that we're going ahead with the Immediate and the Pre-Recorded approaches. In the Phaser 3 API they'll be identified as 'Dynamic' and 'Static' methods respectively, which is in-line with how we split up BitmapText and other advanced Game Objects. If your map won't ever change, you can use pre-recorded. If it will, Immediate mode is for you.
Over the next couple of weeks you'll see this appear in the API and some new examples coming with them.
Phaser 3 Mailing List and Developers Guide
If you're interested in helping evolve the shape of Phaser 3, then please join the Phaser 3 Google Group. The group is for anyone who wishes to discuss what the Phaser 3 API will contain.
The Phaser 3 Developers Guide is available. Essential reading for anyone who'd like to help build Phaser 3.