Nuclear Throne Style Camera System

While working on a new game prototype, I found some inspiration for a really cool feature from Nuclear Throne. If you are not familiar, Nuclear Throne is a top down rogue-like developed by Vlambeer where the player needs to eliminate all of the enemies in a particular stage in order to proceed.

Nuclear throne has a really unique camera system. Instead of keeping the camera centered on the player, like in other games, the camera system will follow the player’s mouse. This allows the player to see more of the level in the direction that they are aiming, helping you see around corners and making sure you don’t run into an ambush.

Another thing I like in Nuclear Throne, to save on the number of sprites that the game uses, the gun isn’t animated as part of the player, it is just a static sprite that will move around based on where the player has their mouse. The gun will also move in front of or behind the player based on where the player is currently aiming.

Building these features in GameMaker Studio 2 isn’t really all of that hard, it only requires 3 game objects to be created, a player object, a gun object and a camera controller object. I will add some wall objects just to show the camera system working, because it will be hard to see the camera’s movement on a pure black background.

We can start this project by opening up GameMaker Studio 2 and creating our 4 game objects, I named mine obj_player, obj_gun, obj_wall and obj_camera_controller. The player, wall and gun need sprites to be able to see them in the room. The player and wall sprites can be 32 * 32 and the gun can be 32 * 8. The sprite for the player should be center aligned, the gun’s sprite should be middle left aligned and the wall doesn’t really matter. Once your objects have been created and your editor looks like mine, we are ready to start writing some code.

We can begin by writing all of the code for the player. The amount of code that we need to put inside of the player object to make this work is very minimal. We only need to add 2 lines of code to the create event and a single line of code inside the step event.

Inside of the Create event we are going to be spawning a gun on the player’s x, y location, and we are going to pass through the player’s instance id over to the gun.

///@description Player Create Event
gun = instance_create_layer(x, y, "Instances", obj_gun);
gun.spawned = id;

The player’s step is only going to be updating the player’s depth based on their current y position inside of the room. This should seem very familiar if you have been using Gamemaker for a while. The depth variable is used to tell Gamemaker the order that objects should be drawn in the room. This bit of code will make objects further down the screen drawn on top of objects higher in the room

///@description Player Step Event
depth = -y;

The gun’s code is going to be in the step event of the gun object. The gun will need to update it’s location to be the same as the player’s location, which we know thanks to the player passing us it’s ID. After that we will need to determine what direction the gun should face. To do that we can use the point_direction function to find the direction between the gun’s current position and the current position of the mouse.

///@description Gun Step Event
x = spawned.x;
y = spawned.y;
var mouse_direction = point_direction(x, y, mouse_x, mouse_y);

Now that we know the direction of the mouse, it is time to actually make the gun face the mouse. The easiest way to achieve this is to update the image angle with the direction.

image_angle = mouse_direction;

When we run the game, this is what we get.

This isn’t exactly finished, the gun is always behind the player no matter where the mouse is, and we want the gun to be in front of the player when he is aiming down. The solution is that now we need to move the gun ever so slightly towards the mouse to get the desired effect.

Moving the gun isn’t very hard, first we need to create an offset variable in the gun’s create event and assign it the number of pixels we would like it to be offset by. I picked 5 pixels for this value.

///@description Gun Create
offset = 5;

Back in the step event we move the gun’s x and y positions using the lengthdir functions and giving it the direction of the mouse and our offset value. Finally we will need to update the depth of the gun so that it will be drawn over the player.

x += lengthdir_x(offset, mouse_direction);
y += lengthdir_y(offset, mouse_direction);
depth = -y;

Now we have a nice effect where the gun disappears behind the player when you aim up and it is in front of the player when you aim down.

With that feature out of the way we can now move onto having the camera follow the mouse. Put a couple walls and your camera controller object into your room (so that you can see the camera move) and set up some views. Don’t forget to make sure that the room is big enough that the camera can move through the room and you have the view set to follow the camera controller object (To make this a bit easier you can also assign a sprite to your camera controller object so that you can see the work that you are doing, but this is completely optional).

We are going to be moving our camera object directly in between the mouse and the player and updating the camera position to that location. We can find out the exact middle point between the player and mouse by taking the player’s position, the mouse’s position and averaging them. Once we have calculated the middle point between the player and mouse we can assign that value to the X and Y position of our controller in it’s step event.

///@description Camera Controller Step Event
x = (obj_player.x + mouse_x) div 2;
y = (obj_player.y + mouse_y) div 2;

And that’s all you need to do. When you run the game you should have a camera that follows along with the mouse. If you don’t, then make sure you remember to enable views, have the view follow your controller object, and make sure that the borders in the view keep your controller directly in the middle.

When you add movement to the player object the camera will move with the player as well because our controller is always going to move to the half way point between the player and the mouse.

This implementation is a little bit crude and the movement isn’t as smooth as Nuclear Throne’s, but its a very similar effect. With a bit of tweaking this could be made to work exactly like Nuclear Throne’s camera system.

Until Next Time
– Steven

Mini Map with Fog of War in Gamemaker Studio

I was showing off the newest game, that we at Elektri are working on, and one of my Instagram followers asked me how I did the mini map with fog of war / map discovery mechanic inside of that game. In the 20 seconds of googling the issue I see results on Reddit and the Gamemaker forums explaining the high level concept of what I am doing, but not really getting into the nitty-gritty, so I thought I would go really in-depth about what is going on in my fog of war system.

I recommended that you follow the tutorial that Heartbeast did on building a random walk algorithm before you follow this tutorial. First, because this mini map is built on top of the random level generation script, and second it will give you a rudimentary crash course on what a DS_GRID is. If you just want to follow along with making the mini map, you can download a completed version of the random level generation tutorial here.

The mini map is made up 2 key pieces of data, tiles that the player is able to walk on, and the player’s current location. Because we only need to draw these 2 things to the user interface, we only need to make 2 sprites. These sprites do not need to be very big, the ones that I use are 3 pixels by 3 pixels. The sprite representing the player’s location is white, so RGB 255, 255 and 255 and the one representing the floor is RGB 0, 0, 0 with an alpha of 128 to make it translucent.

All of the work for the mini map is going to be handled by 1 game object. In most games you can put it in your game manager but for the purpose of this tutorial we can make a dedicated mini map object. This new mini map object is only going to have 2 events, the create event and the draw_gui event. The create event is going to be used to initialize some data and most of the heavy lifting is done in the draw_gui event.

If you followed heartbeast’s tutorial, you would already know that the level is being stored inside of a DS_GRID and perhaps all we need to do is draw the existing DS_GRID to the GUI layer and we would have a functional mini map. Hey readers, that is a good idea that you just came up with! Lets go ahead and do that.

We can resize the GUI by running a line of code in the create event. The project has some values that I do not particularly like. The camera is set to 320 * 180 with the view port at 1280 * 720. I left the camera alone and changed the view port to 640 * 360 and changed the GUI to 640 * 360.


display_set_gui_size(640, 360);

Now we need to loop through the DS_GRID, stored in o_level, and draw the floor tiles to the screen. We can store the width and height of the grid, as local variables, with the ds_grid_width() and ds_grid_height() methods and use this data in our nested for loop. Inside the for loop we check current the grid position and draw it to the GUI if it is a floor tile.

Finally we check the player’s x and y location in the room, divide those values by the width and height of the cell to get the cell that our player is standing on, and draw that location to the GUI (also don’t forget to put an instance of our mini map object into the game room).


// Get width and height
var width = ds_grid_width(o_level.grid_);
var height = ds_grid_height(o_level.grid_);

// Spacing for map tiles
var space = sprite_get_width(s_map_path);

// Draw the map
for (var yy = 0; yy < height; yy++)
{
for (var xx = 0; xx < width; xx++)
{
if (o_level.grid_[# xx, yy] == FLOOR)
draw_sprite(s_map_path, 0, xx * space, yy * space);
}
}


// Draw the player
draw_sprite(s_map_player, 0, (o_player.x div CELL_WIDTH) * space, (o_player.y div CELL_HEIGHT) * space);

What we have working now is indeed a functional mini map with player location but you might be asking yourself, hey wait a minute, this tutorial is supposed to have fog of war right? Well, yes, but it was your idea to just draw the existing DS_GRID to the GUI and I just followed along with your idea.

To be able to make a working fog of war we will need access to data that the existing DS_GRID doesn’t have, and to do that we need to make a second DS_GRID to store that data. Our new DS_GRID is going to have the exact same width and height as the DS_GRID stored in o_level and instead of storing if a tile is a floor or wall we are going to store if a floor tile is void or has been explored. If the tile is void we will not draw it to the screen and if it is explored we can then draw it to the screen.

To make programming this a little bit easier we can open up the create_macros script and add an explored macro. You can add our new explored macro just underneath the existing floor, wall and void macros, and give our new macro a value of -8.


#macro EXPLORED -8

With the new macro created, we can create our new DS_GRID in the create event of the mini map object. We can give it the same dimensions as the o_level DS_GRID using the ds_grid_width and ds_grid_height functions again. After the DS_GRID is created the entire data set needs to be filled with void.


// Set the grid width and height
var width = ds_grid_width(o_level.grid_);
var height = ds_grid_height(o_level.grid_);

// Create the minimap grid
explored = ds_grid_create(width, height);


// Fill the grid with void
ds_grid_set_region(explored, 0, 0, width, height, VOID);

In the draw_gui event, we need to switch the DS_GRID that we are drawing to the GUI from the level grid to our own grid, and add an if statement to only draw a tile if it is flagged as explored, instead of if it is a floor.


if (explored[# xx, yy] == EXPLORED)

Finally, we need some sort of method to change the value inside of the DS_GRID from void to explored. A really easy way is to simply change the value of the tile that the player is standing on. If we are standing on a tile, then we know it’s a floor and we can mark that tile as being explored. We also already know what tile the player is at because we are drawing it to the screen, so we can flag that tile as explored inside of our new DS_GRID.


// Flag player's location as explored
explored[# o_player.x div CELL_WIDTH, o_player.y div CELL_WIDTH] = EXPLORED;

Now we have a working fog of war, but not enough of the map is discovered as we walk through the room, the player needs to be standing on a tile for it to be flagged as discovered. We need a way to discover more of the map, ideally whatever is on the screen should be flagged as explored. What I did is counted how many tiles are on the screen at a time, this game’s camera size is 320 * 180, and the tiles are 32 * 32. So there are 10 tiles that can be on the X axis at any given time and 5 and a half tiles on the Y axis.

What I do will not discover 100% of the tiles on-screen but it will be close enough. We are already flagging the tile that the player is on, and we can flag 4 tiles to the left and right and 2 tiles up and down giving us 9 tiles of visibility on the X axis and 5 on the Y axis. This means that the tiles on the very edge of the camera will not be discovered on the mini map until you move closer to them.

To do this, inside of the draw_gui event we are going to have another nested for loop where we check 4 tiles left and right of the player and 2 tiles up and down from the player, and if those are floor tiles in the level DS_GRID then we will mark them as explored in our DS_GRID.


// Flag the rest of the tiles on-screen as explored.
for (var yy = 2; yy >=-2; yy--)
{
for (var xx = 4; xx >=-4; xx--)
{
// Get cell to check
var xexplore = (o_player.x div CELL_WIDTH)+xx;
var yexplore = (o_player.y div CELL_HEIGHT)+yy;

// Don't go outside DS_GRID
xexplore = clamp(xexplore, 1, width-2);
yexplore = clamp(yexplore, 1, height-2);


// Check the cell and flag as explored
if (o_level.grid_[# xexplore, yexplore] == FLOOR)
explored[# xexplore, yexplore] = EXPLORED;
}
}

And there you have it, a working mini map with a fog of war that will only draw what is visible on the user’s screen. Hopefully somebody, somewhere, finds this post useful at some point in time.

Until Next Time
– Steven