At the time I started to make my action RPG game, the GameMaker: Marketplace had more or less just opened and it was getting some pretty interesting stuff thrown up. In particluar there were a few lighting engines on there that use shaders to create dynamic lights and shadows from instances or tiles, and these interested me greatly as good 2D lighting in GameMaker has long been a "holy grail". So, I dug into my account and downloaded two of the best looking ones to test with my project...
To tell the truth, I was pretty excited by this, as I remember old GM users like GearGOD or Adventus creating super complex lighting engines in GM6 which looked absolutely gorgeous but were complex as hell:
These engines were an amazing feat given the limitations of GameMaker at that time, and I remember pouring for hours over the code-base of them to try and figure out just how the hell they did this visual magic! However, i was new to GameMaker (and programming in general) and confess to not following most of what went on in the code. These engines were good but they weren't very user-friendly...
When GM7 and GM8 came along, there seemed to be a lack of interest in these things, mainly due to the poor performance in real world situations of any lighting engines, and no engine created for those versions seemed to equal the previous ones. However, with the introduction of shaders to GameMaker: Studio, it appeared that the lighting bug had bitten again and I wanted to see just how these new systems compare to those I tested long ago.
The first of these assets that I tested was TrueLight 2D, by xygthop3. The reason I chose this one to start with was that in my game I figured that I'd only really be needing shadows for the walls and doors, so since it is advertised as working with tiles, I figured that this could be a fast and easy way to add some nice ambient lighting effects to my game without increasing the instance count.
Sadly, I was wrong... Yes, the engine is fantastic at what it does - and I can't recommend it enough for easy lighting, especially in platform games - but due to the way I was creating my game and the different tile depths required to give that "pseudo" 2.5 D look, it just wasn't working out as I'd hoped. I also found that after adding multiple lights to my game (quite a few actually!), the FPS was quite drastically reduced, and the number of vertex batches being sent to the GPU was rather high, meaning that for a mobile game I'd potentially have problems.
I really didn't want to reduce the number of lights or the quality so I moved on to test the next asset I'd bought...
The Glare Engine by Tizzio was the next asset I tested, and if I am honest I should say that I was blown away by it. Honestly, the first impression when testing the demo was "Wow!", as it really is one of the most complete lighting engines I have seen for GameMaker, and although it has it's complexities, actually setting it up and getting the basics running seemed like a pretty easy thing to do. So, I set to work adding the engine to my core game.
The docs that accompany the engine are really comprehensive and within an hour or so, I had it working. I was spawning a light object for the player, then spawning further lights (dimmer and coloured) within each of the room spaces in the dungeon, and it was looking okay. I was still concerned about the vertex batches though, as, again, they were higher than I'd have liked, but testing on my (mid-range) Android phone showed that I was still getting a healthy FPS of around 200. This encouraged me to dig around in the engine and see what else it could do, resulting in some nice effects like this one when the player passes in front of a gate:
However, as you can probably see in that GIF, I was also having problems... particularly with having the light surface syncing with the movement of the view. No matter what I did I could not get it to update in time, and there was always an annoying lag. I also felt that the engine itself was actually overkill for my game, as it has a huge number of scripts and components, and I really did only want a simple light/shadow engine. I don't need bloom, nor do I need day/night cycles, nor do I need any of the other bells and whistles that make this such an amazing asset!
Which got me thinking about just what I did need, and whether or not I should maybe just forget about other people's assets and make something for myself.
Going back to basics, I started thinking about what I really wanted from lighting in my game. Did I want it to reveal things as the player progresses or did I just want it as an effect to enhance the user immersion? Or both? Since this was going to be a rogue-like action game, I decided that having everything dark and letting the player explore and discover things would be good, and maybe add a touch of suspense as you open a door and suddenly discover 100 enemies behind it. I also thought about the structure of my game, and how it's grid-based, which led me to conclude that I should make a "block" lighting system.
I set about doing this using tiles again. Since the room is on an 8x8 grid, I started by simply tiling the whole room from top to bottom with 8x8 black tiles. The tile ID for each was stored in another ds grid, and then I wrote a script that would modify the alpha of the tiles around any given point. this was actually quite efficient, as I didn't have to loop through the whole grid to change the alpha, but rather use the radius of the light source to only check the corresponding grid area around it.
This was looking quite nice, but I still had to deal with collisions, so that the light doesn't go through the walls or doors. the obvious solution was to use a collision line, then quantise the results into the grid, but, as we all know, the collision functions are a bit slow and it would waste any optimisations I have made by using tiles. Cue a quick search of Google which threw up a nice formula called Bresenham's line algorithm.
Basically, this takes a straight line and then rasters it down to a grid, and after checking some of the JS and C++ code examples that I found, it was pretty easy to add it into GameMaker as a script and make my own collision line function from it for grids (you can find it here, if you're interested: GMLScripts.com). Now I had the effect that I wanted!
Only I didn't... This was a fog of war, not a lighting engine! It would be great for a TDS or if the game was turn based, or even as a Marketplace asset, but it wasn't what I wanted for this game. Time to go back to the beginning and start over. sigh
Taking inspiration from the older ligthing engines, I set about writing my own in a similar style to them. I wasn't going to use shaders as I don't know enough about writing them, but I do know surfaces, and all the best engines previously used them to create their magic. With the changes to how GameMaker: Studio renders things, and the fact that I didn't need anything too complex, I figured that I could whip up something workable that would both do what I wanted and also be light on processing.
To start with I created a surface that was the size of the view, then added in some caster objects. I created a nice grey-scale sprite for these using pixel gradients rather than smooth gradients, and then had them draw to the surface to give a nice "glow" over an area - this was pretty standard stuff and can be found in the surfaces tutorials that come with GameMaker. The challenge now was to have objects that block the light source and cast shadows...
Obviously the walls were going to be the shadow casters to start with, but how? Looking at other people's examples I saw that primitives were often used to create shadow projections, so I set about programming this. To start with I created a custom vertex format and a vertex buffer. This is new functionality in GameMaker: Studio, and although I'd tested it out, I hadn't had a chance to actually use them for anything and this seemed like an ideal moment to give it a go. Using custom buffers and formats means that it should be quicker since I can select what is to be submitted to the GPU for rendering. The actual vertex format I created only takes 2D position and colour, as I have no need of textures, nor normals, etc...
I then had to apply this to my wall objects to create the shadow projection, but first UI added in a couple of checks to help keep performance to a maximum: a check to see if the wall was actually in the view, and then a second bbox overlap check to see if the instance was in the light - no point drawing shadows when they won't be visible! After that I had to calculate the direction the shadows would go in and the projection for them. this was done by getting the direction from the light source to each of the points of the wall object and then projecting those points along a line to create polygon shadow. The following image illustrates this:
As you can see from the illustration - which I have exaggerated a bit - the actual shadow can be an odd shape due to the projection method and the different angles involved (and, in fact, the engine will also render any type of polygon with a shadow, not just blocks, including convex or concave, which gives even weirder shadow shapes), and if I don't project the points from the corners far enough then users will see the edges. The other issue with this way of doing things is the sheer processing required to do this every step. However I came up with a solution to that too!
What I did was have every light source create a surface, then draw itself to that surface, before finally looping through the shadow caster objects and drawing them on top. This meant that the final projection points didn't matter as long as they were outside the bounds of the surface, and with everything held on a single surface, I could then use a controller variable and only update the contents when necessary. Essentially, I was converting all lights from dynamic (updated in real time) to static (updated once then simply drawn). This would give a massive frame boost and, since it's actually controlled by a single variable, I could also turn these static lights to dynamic lights if I wanted to (and back again).
Think about this. I have a room with a light and a number of doors. As long as the doors are there, then the shadows that those lights cast won't change (since the walls don't move). So they are kept static. However when the player opens a door, the update variable is set and the surface is re-drawn, only now without the door shadow caster, and then the update variable is set to false again. Now, the FPS is still kept high even with a lot of lights in the room, and the "hit" when a light is updated is minimal because it only happens every now and then!
These individual light surfaces are then drawn to the final "full" light surface, which is then overlayed on the screen. Note that in most engines they use an additive blend mode, but for mine I decided to use a "multiply" blend mode, as I feel it gives a far more natural look to the lights.
// Equivalent (more or less) of the "multiply" blend mode in photoshop drawsetblendmodeext(bmdestcolor, bmzero);
And that was my lighting engine created! I was getting a much better frame rate than with any of the shader options, it looked nicer than the FOW/tile based solution, and it was just flexible enough to be used for other things and not just my own game (in fact, as with other things I've made, it's <a href="https://marketplace.yoyogames.com/assets/721/aura-surface-based-lighting" target="blank">available on the Markietplace too!). The only down-side to it was that I was still getting a pretty huge number of vertex batches, but on Mobile it was running smoothly and still had frame rate overhead to spare, so I wasn't too worried about that yet.
I'd had a great deal of fun writing my own lighting engine, and although it's fairly basic, it looks fantastic in my opinion and - more to the point - it fit my game perfectly. I enjoyed it so much in fact that I decided to start working on some of the special effects that the game will need rather than expand on the core gameplay! But before that I had to deal with a slight identity crisis, which I'll talk about in depth next week...