Lighting with Gamemaker Studio 2

After I started working on my new platformer, I made some tweaks to it to try to separate it from the game that served as the original inspiration. The easiest change, that I thought of, was to make my game endless. Another idea was to have some sort of mechanic that would make my game more difficult the further you went along.

Keeping in mind that this game is about the player falling further and further down a hole in the ground, I thought that it would make sense that the visibility of the player would get progressively more restrictive the further down the player fell.

After some quick googling I found a simple little tutorial done by Zach Bell Games that looked perfect (Also if you don’t follow his work, you really should). The Zach Bell Games tutorial requires that you know a thing or two about surfaces in Gamemaker Studio 2, and even if you don’t it’s pretty easy to follow along. The only issue with the Zach Bell Games tutorial is that this is designed for a platformer with a fixed room size, which you can tell from this little section of code.


// Create a surface the size of the current room
// You could also create a surface the size of the current view, but I won't get into that
surf = surface_create(room_width, room_height);

They are making a surface the size of the entire room to create the darkness effect. This method doesn’t work with my endless platformer, because if a surface gets too big it will cause my game to crash. The tutorial, at Zach Bell Games, didn’t get into making a lighting system that only covers the view (as seen in that code block), so I needed to do it myself.

To start with this process I made a create event that is very similar to the one in the Zach Bell Games tutorial. The only major difference is that instead of making a surface that is the size of the room, it will only be the size of the view / camera. You are able to find the size of your view by going into the room editor and looking at the section “Viewport and Cameras”. My viewport is only 352 by 512 so I created a surface that size inside of my create event.


// Create Event
// Create the surface for lighting
surface = surface_create(352, 512);

// Set the surface target to the new surface instead of the application surface
surface_set_target(surface);

// Clear the surface
draw_clear_alpha(c_black, 0);

// Reset target surface back to the application surface
surface_reset_target();

Now that this surface is exactly the size of the view I can fill the surface with a translucent black layer black inside of the step event to give the effect of darkness. You should also keep in mind that surfaces are volatile and can be deleted randomly, so always check to make sure the surface still exists.


// Step Event

// Check that surface exists
if (surface_exists(surface))
{
// Switch surface to draw to
surface_set_target(surface);

// Select the colour to draw
draw_set_color(c_black);

// Set the alpha value to draw at
draw_set_alpha(0.8);

// Fill View with black
draw_rectangle(0,0, 352, 512, 0);

// Reset the alpha
draw_set_alpha(1);

// Reset the target
surface_reset_target();
}
else
{
// Run the create again if the surface isn't detected
surface = surface_create(352, 512);
surface_set_target(surface);
draw_clear_alpha(c_black, 0);
surface_reset_target();
}

Just filling the surface with black, in the step event ,doesn’t draw anything to the screen. To be able to draw the surface to the screen you need to run some code in the “draw end” event; however, this is where things get a little bit tricky. We now need the surface to follow the view because it isn’t going to fill the whole room. The tutorial by Zach Bell Games drew the surface at the position 0,0 and that isn’t going to work for this system because the surface would stay at the top left of the play area.

The first thing that you would probably think of is to draw the surface at the player’s current position. This might make sense because more than likely your view is set to follow the player. So if I go to the “draw end” event and run the code draw_surface(surface, obj_player.x, obj_player.y); this is what our view will look like.

The draw_surface command starts to draw the surface at the top left hand corner, when we provide it the x and y position of the player Gamemaker will start drawing the surface at the player object’s pivot point. Clearly, that will not do, I need to draw the surface starting at the top left of the view. Thankfully Gamemaker studio provides a method to be able to get top left X and Y position of whatever view you want. By making 2 local variables for the x and y position you can run the commands camera_get_view_x(camera) and camera_get_view_y(camera) to get the X and Y coordinates of the top left corner of the view (This command is different if you are using Gamemaker Studio 1.4). We can update our draw end event and see what we get.


// Draw End Event
if (surface_exists(surface))
{
// Get the X position of the view
var _x = camera_get_view_x(camera);

// Get the Y position of the view
var _y = camera_get_view_y(camera);


// Check what view we are on and draw the surface at _x and _y
if (view_current == 0)
draw_surface(surface, _x, _y);
}
else
{
// Run the create again if the surface isn't detected
surface = surface_create(352, 512);
surface_set_target(surface);
draw_clear_alpha(c_black, 0);
surface_reset_target();
}
}

Now I are getting somewhere. The black surface is now being drawn at the top left hand side of the camera, and it will always stay in front of the view no matter where the player moves because we are getting its position each time the draw_end event is run.

Now I just need to cut out the little circle of light around the player. I can go back to the step event and add in a couple more functions to take care of this. I need to set the blend mode to subtract, set the colour to white, and then I am ready to draw our circle (but don’t forget to set the blend mode back to normal after you draw the circle around the player). So now inside of the step event I check if the player object exists, and run the draw_circle function and feed it the x and y position of the player and see what happens.


// Draw Circle in Step Event

// Set blend mode to subtract
gpu_set_blendmode(bm_subtract);

// Set colour to draw
draw_set_color(c_white);

// Check if Player object exists
if (instance_exists(obj_player))
{
with (obj_player)
{
// Draw the light circle
draw_circle(x + random_range(-1, 1), y + random_range(-1, 1), 50 + random_range(-1, 1), false);
}
}
gpu_set_blendmode(bm_normal);
draw_set_alpha(1);
surface_reset_target();

What is going on here? Well, when we draw the circle we are providing it the X and Y position of the player in the room, and because the surface is following the view our little light circle will run away from the player. What I need to do is draw the circle at the X and Y position of the player inside of the view. To fix this I wrote a simple little script to be able to figure out the X and Y position of any object inside of the view.


// x_in_view(x)

// X position of Object
var _x = argument0;

// Check X position of object in view
_x = _x - camera_get_view_x(global.camera1);

// Return position of object in view
return _x;

That code block was just for the X position, I created a separate script for the Y only I changed the function to camera_get_view_y. With these two scripts I can figure out the X and Y position of the player object inside of the view and I should be able to cut out a circle of light around just the player. This is what the finished step event looks like.


// Finished Step Event
if (surface_exists(surface))
{
// Switch surface to draw to
surface_set_target(surface);

// Select the colour to draw
draw_set_color(c_black);

// Set the alpha value to draw at
draw_set_alpha(0.8);

// Fill View with black
draw_rectangle(0,0, 352, 512, 0);

// Set blend mode to subtract
gpu_set_blendmode(bm_subtract);

// Select the colour to draw
draw_set_color(c_white);

// Check if player object exists
if (instance_exists(obj_player))
{
with (obj_player)
{
// Get X position of player in view
var _x = x_in_view(x);

// Get Y position of player in view
var _y = y_in_view(y);

// Draw the light circle
draw_circle(_x + random_range(-1, 1), _y + random_range(-1, 1), 50 + random_range(-1, 1), false);
}
}

// reset blend mode
gpu_set_blendmode(bm_normal);

// Reset alpha
draw_set_alpha(1);

// Reset target
surface_reset_target();
}
else
{
// Run the create again if the surface isn't detected
surface = surface_create(352, 512);
surface_set_target(surface);
draw_clear_alpha(c_black, 0);
surface_reset_target();
}

Now that is looking a lot better. The scripts x_in_view and y_in_view are also able to return the view position of any other object I want. So because my game has enemies that also generate light I just copied the code from the player and provided the x and y positions of my enemies.

There it is, a lighting system that only covers the view and will work in an endless runner.

Until Next Time
– Steven

+1 to Pixel Art

Instead of keeping my latest little tech demo as just a tech demo, I thought that it would be a good idea to put in the work to make it a complete product.

I have been using new game as an opportunity to work on my pixel art skills. One of the major shortcomings that I previously had was with environments and backgrounds. With my work in Cursed Squire the backgrounds were made by just tiling a single geometric shape, it was crude but effective and it looked like the early NES style I was trying to evoke with that game.

While those background were good for what I was doing I still wanted to improve my ability to make environment artwork with this new project. Conveniently I happened to discover this tutorial by Sadface. Using the techniques in that guide, I took a couple of cracks at making a rock formation background. My early ones were very crude, and honestly didn’t look very good, but I kept at it and made little improvements with each iteration.



The other major area that I need to improve my pixel art skills with was with character design and animation. Previously I have used a technique called skeletal animation where body parts are drawn individually and then animated using another piece of software like Spriter. This technique is good for getting something done quickly with minimal effort, but wouldn’t really help me improve my drawing skills if I did it again, so I wanted to try to do this the old fashion way.

I took a couple attempts at creating a player character that would fit in with this style of game and environment, something loosely inspired by the Spelunky character and Indiana Jones.

That style of pixel art, which I first saw used in Superbrothers: Sword and Sworcery, clashed with my backgrounds. I needed the character to have a little bit more girth, which was going to make the animation a little bit more challenging for me, but after a couple more attempts I came up with this design.

With the concept finished it was time to then move onto animating a simple 8 frame run cycle. This was probably the most challenging thing I have done, in my game development career, to date. After several failed attempts I followed an 8 frame run cycle animation guide by Heartbeast and managed to come up with a run cycle that I think is probably the best that I have ever done.

Taking the lessons learned from working on this player, I have been able to apply similar lessons to the enemies that I have been working on for this game. One that I am quite proud of is this Skeleton.

I am quite pleased about the progress of this new game and hopefully have more to show off soon.

Until Next Time
– Steven