Cameras And Views


Cameras And Views

Introduction

Cameras give a mechanism for drawing different parts of your room to different places on the screen, or for drawing just a single part of your room to cover the whole screen. For example, in most platform games, the camera follows the main character, since if you could see the whole level on the screen then your character would be too small to see and there would be no surprises for the player. Cameras can also be used in multi-player co-op games, as they permit you to create a split-screen setup in which in one part of the screen you see one player and in another part you see the other player. A further example is in games in which part of the room should scroll (e.g. with the main character) while another part is fixed (for example some status panel). This can all be easily achieved in GameMaker Studio 2 using cameras and view ports, and in this tutorial we'll take you through all the basics of setting up and manipulating custom cameras in your own projects.

When you create your game, what the player "sees" at any given moment is governed by three different - but connected - things:

  • The Camera: This is essentially a "container" that will be used to hold the different view values and set how the room is displayed on screen.

  • The View: This is what the camera sees, based on the position, projection and rotation of the camera.

  • The View Port: The area of the physical screen where the camera view will be displayed.

The most obvious analogy to understand what is going on is to use that of an actual video camera. You have a scene that you want to film (the GameMaker Studio 2 room where your game happens), you have a camera to film it with (the GameMaker Studio 2 camera obviously), you have a lens that captures what you see (the GameMaker Studio 2 view) and you have a screen that displays what you see (the GameMaker Studio 2 view port).

undefined


So, when you run a game in GameMaker Studio 2, you will have at least one camera, and it will draw what it sees (the view) to a view port, which is then shown in the game window to the player. Note that this is true even if you don't have views activated in the room settings. If you do not create your own camera view and view port, GameMaker Studio 2 will still create a "default" camera and view/viewport based on the dimensions of the first room of the game. So if your first room is, for example, 800x400, then GameMaker Studio 2 will create a view port that is 800x400 and a camera with a view that covers the whole room. If you then change rooms to one that is a different size - 1024x768, for example - then the game window and view port will still be 800x400 but the camera view will be 1024x768 (to see the whole room) and then scaled/stretched to fit the smaller view port. This means that if you do not actively use cameras and views, you should make all your game rooms the same size

NOTE: There are ways you can change the view port and window size using GML so you are not forced to follow the rule above, but it's important to realise how the GameMaker Studio 2 camera and view port system works by default in this respect.

Now that we have an idea of what it means when we talk about cameras, views and view ports, it's time to actually use them. In the demo project that comes with this tutorial we have a very basic platform game that, when you run it, will create a game window that occupies the whole screen and shows the entire game room. If you haven't tested it yet, press Play in the GameMaker Studio 2 IDE and see it for yourself before continuing (you can move the player object using the Arrow Keys and make it jump using the Spacebar.

undefined


The Room Editor

When you have run the default Demo project for this tutorial, you can see what happens when we leave it up to GameMaker Studio 2 to set up the default camera... The whole room is shown, but it's been scaled to fit the view port, which in turn may have been scaled to fit the screen. This is far from satisfactory, and so we are going to set up a camera view in the Room Editor and limit what the player sees to only what is visible to the camera.

Open the Room Editor now on the resource rGrass (simply double click  on the resource, or use the right click menu  and select "open"). With the room editor open, you need to go to the Room Properties window, shown below, and expand the Viewports and Cameras section:

Room Properties Expanded

The first thing we are going to do is tick the Enable Viewports and the Clear Viewport Background options. The first option simply tells GameMaker Studio 2 that you are going to take over drawing the camera view and setting up the view port, while the second option clears the application surface with the window colour before drawing the camera view to the view ports (if you know that what you are drawing will cover the application surface then you can switch this off, but if in doubt make sure it is enabled).

We now need to define our camera view and the area of the screen on which to draw it (the view port).

NOTE: If you do not enable any cameras or view ports, then the game will still run but you will simply get a blank window.

For this demo, we only want to have one view active and have it follow the player, so you want to open Viewport 0 and click the Visible option:

Make View Visible

You can actually run the demo project now if you want and you'll already see a difference. The game window should be 1024x768 and the camera view focused on the top left corner of the room (these are the default values for the camera view). However, you can move the player outside of the view and the size isn't really what we want...

The first thing we are going to fix is the size of the game window and area within the room, so set the Camera Properties to have a Width and Height of 960px and 540px respectively, and the X pos and Y pos should both be set to 0 (this sets the size of the camera view and the position within the room when the room is entered). It's worth noting that these settings are also reflected by the View Overlay in the actual editor window for the room, and the overlay can be toggled on or off using the control button (shown below):

Room View Overlay

We also need to adapt the View Port size to suit the new camera view size, otherwise we will get stretching like this (run the project and see for yourself):

View Camera Stretched To Port Size

To prevent this, we need to set the Viewport Properties to be the same as the Camera Properties, ie: X pos and Y pos set to 0, and a width and height of 960px and 540px. You can test again and you should see that the game looks as it should, but you might want to take a moment to play with different values for the view port and see what happens. For example, if you set the X pos or Y pos values, then you'll get a black area where the view port has been offset and is drawing "outside" the game window, or if you set the width and height to be 480px and 270px then you'll get the game scaled to half the size.

NOTE: Setting the view port width or height in the first room of the game will affect the size of the game window, but changing the view port offset values will not affect the window position.

Camera Following

We have a camera view and a view port set up in the room editor, but it still doesn't follow the player when they move. Setting up view following is also achieved from the Viewports section of the Room Properties, underneath where we set the camera and port properties:

Object Following Room Properties

The first thing to do is select an object to follow. This is done by clicking  on the button which currently says "No Object". This will open up the Asset Explorer where you can select one of the available game Objects, in this case the object "oPlayer". This is telling GameMaker Studio 2 that the view should follow an instance of this object if it is found in the room.

NOTE: If there is no instance of the object in the room then the view will simply not move, and if there is more than one instance of the object in the room GameMaker Studio 2 will choose one to follow, but you have no way of telling which one it will choose.

You can run the project again now and move the player around. You'll see that when the player gets close to the edge of the screen the view will move... but this isn't really very good as the player cannot see what is coming and we want the view to move and keep the player in the center. For that, we need to change the horizontal and vertical collision border that the camera view uses to "focus" on the object:

Object Following Border

This border is the distance from the edges of the screen that the instance needs to be before the camera will update its position to follow the instance. In this case, it's set to 32px, which means that the view will only move if the instance is 32px or less from the edge. We need to change these values to 400px for the horizontal border and 250px for the vertical border. These values leave a little bit of "space" for the player to move in before the view starts to move, but give a large enough view around the player to be helpful.

NOTE: If you set the border values to anything over half the width and height of the room, the follow instance will be clamped to the center of the view.

The Object Following preferences also permit you to set a speed for horizontal and vertical scrolling. The values set here tell GameMaker Studio 2 how many pixels to update the view position per-step for the horizontal and vertical axis, so you can have views that "slide" into position. For example, if you set the horizontal speed to 2 and test the project, you can see that when the player moves left or right the camera view will lag behind and only "catch up" when the player stops moving. However, this is not what we want, so we set it to -1, which is how we tell GameMaker Studio 2 to update the position instantly.

NOTE: Setting the horizontal or vertical speed to 0 will prevent the camera from following the instance on that axis, and also note that regardless of the border or speed values, the view will never go outside of the room when using the Room Editor view preferences (this is possible in code, though).

Play the game again and you can see that the player can move left or right, up or down and the view will follow them around. Now, let's look at how to set this up using code...

Creating Cameras

We are now going to move on and make a camera using the GameMaker Language (GML). To start with, we are going to set up the same camera and view port as we have in the room editor, but using code, so open the Room Editor just now and un-check the Enable Viewports option in the Room Properties:

Switch Off Views In The Room Properties

With that done, running the game will once more create a window the width of your display and show the full room scaled down to fit, so we now need to add some code to fix this. You need to open the controller object "oGame" for this (a controller object is an object, generally with no sprite assigned, that is placed in a room to "control" things behind the scenes, and is generally used for room setup and drawing HUDs to the GUI, etc...). With "oGame" open, click the Create Event to open it:

Game Controller Object Create Event

Here we are going to set up all the view port properties as well as create a camera and define what it views. To start with, we need to enable custom viewports and cameras, as well as enable a specific view port to use. For that we add the following after the existing code:

oGame: Create Event
// Initialise Viewports
view_enabled = true;
view_visible[0] = true;


The first line is simply an identifying comment so we don't get lost in the code, while the second line sets the built-in global scope variable view_enabled to true. This variable can be set to true or false and mimics the Enable Viewports checkbox in the Room Properties of the room editor.

The second line tells GameMaker Studio 2 which viewport to enable. If you look in the Room Editor, you can see that you have available up to 8 view ports, and so to access them in code we use an array, where the first view is the [0] element, and the last view is the [7] element. A number of the built-in values for view ports are stored as global scope arrays, so make sure that the array element you are accessing corresponds to the view you want to use or change.

With that done, we can now setup the viewport itself using the following code:

oGame: Create Event
view_xport[0] = 0;
view_yport[0] = 0;
view_wport[0] = 960;
view_hport[0] = 540;


Here we target the view port [0] array, since that is the port that we made visible previously, and we set the offset position to (0,0), and the width and height to 960px and 540px, respectively. This is exactly the same as what we had in the room editor for the Viewport Properties.

NOTE: These view port array values can be read as well as written to, so you can set up views in the Room Editor, for example, and then get the values set in GML using these built-in arrays.

We can now go ahead and set up our camera view. This is easily done with a single function which not only sets the view position and size, but will also set the object that the camera has to follow and the follow borders:

oGame: Create Event
view_camera[0] = camera_create_view(0, 0, view_wport[0], view_hport[0], 0, oPlayer, -1, -1, 400, 250);


The function camera_create_view() creates a new camera and sets up the view from that camera, and you supply the same values that you'd supply to the room editor for the Camera Properties and the Follow Object. The function then returns a unique ID value for the camera that has been created. Now, you can simply store this ID value in a regular variable and then use it whenever you want, since cameras are actually dynamic resources that can be created and destroyed and passed around much like any other resource. This means that the act of creating a camera does not actually draw anything to the screen, and is instead "preparing" a camera for use, so you could potentially have various cameras setup and then assign them one at a time to a view port to create a cut-scene, for example. However, in this case we return the ID of the camera to the built-in global array view_camera. This array is what connects a camera to a view port, so by giving the [0] element our camera ID we are telling GameMaker Studio 2 to draw everything that camera "sees" to the screen.

Let's run the game now and see how it works...

Stretched Game Window

Something isn't quite right there, is it? You may be thinking that you've done something wrong at this point, but don't worry, you haven't! What you are seeing is a result of the code only setting the camera and view ports, but not setting the things that GameMaker Studio 2 does automatically for you when you use the Room Editor to set up cameras. Previously we mentioned that when you have no view active in the Room Editor, GameMaker Studio 2 will set the game window to the size of the first room of the game, so now that we no longer set the view in the Room Editor, GameMaker Studio 2 is setting up the "default" camera and view ports before our code takes over. This sets the game window to be the wrong size.

The solution to the problem outlined above is to set the window to the correct size for the view port that we have set using the following function:

oGame: Create Event
var _dwidth = display_get_width();
var _dheight = display_get_height();
var _xpos = (_dwidth / 2) - 480;
var _ypos = (_dheight / 2) - 270;
window_set_rectangle(_xpos, _ypos, 960, 540);


This sets the window to the same size as the view port we have defined. Note that we have used the function window_set_rectangle() rather than simply using window_set_size() because resizing the window will not reposition it, meaning that it will be resized at the position of the original top left corner. This way we resize it and position it correctly in the center of the display.

Run the game again and see what happens...

The Application Surface Is The Wrong Size

The window is now the correct size but... it's is still not right. What have we done wrong? Again, this is an issue with the automatic things that GameMaker Studio 2 does when initialising a room. Not only does it set the window size based on the view port, it also sets the size of the Application Surface. This surface is what everything you draw in the Draw Event is sent to before being displayed on the screen, so by default it will be created at the size of the room if no view port is defined in the Room Editor (and it will be created the size of the view port if one is defined). We need to resize this just the same as we did the game window with the following code:

oGame: Create Event
surface_resize(application_surface, 960, 540);


If you run the game now, it should all look exactly as it did before when you used the Room Editor to set up the views:

Camera Correctly Set Up

Before we continue, there is one final - but very important - step to take, and that is to destroy the camera at the end of the room. As mentioned previously, cameras are considered a dynamic resource, much like particles, or surfaces or data structures, and as such they are allocated a memory space when they are created. If you do not destroy the camera when it is not required you run the risk of creating a "memory leak", which is what happens when you start to fill up memory space with references to dynamic resources that can no longer be accessed by GameMaker Studio 2. To fix this issue we need to add a Clean Up event to our controller object "oGame". Add this event now and give it the following code:

oGame: Clean Up Event
/// Clean Up Cameras
camera_destroy(view_camera[0]);


All this does is frees up the memory for the camera assigned to the view port element [0]. This is an important step to remember, so keep it in mind when creating cameras in your own projects!

Advanced Moving

What we have at the moment is a basic replica of what we set up originally in the room editor, which is fine for simple games and basic motion, but what if we want something more complex? Well, there are a number of functions available for dealing with cameras and we can make use of some of them to make the view a bit more dynamic. In this case, we're going to make the view change position depending on what keys are pressed, so that the player can see a little more of the room in the direction of movement.

To start with, we need to deactivate object following in the camera, as we will do this manually through code later. For that, you simply need to open the Create Event of the object "oGame" and change the line where we create the camera to this:

oGame: Create Event
view_camera[0] = camera_create_view(0, 0, view_wport[0], view_hport[0], 0, noone, -1, -1, -1, -1);


By setting the follow object to the keyword noone, we are telling GameMaker Studio 2 that this camera should not automatically follow anything. Now open the object "oPlayer" and go to the Step Event. Beneath the call to the script scrProcessPlayer we are going to add in the following code:

oPlayer: Step Event
var _hor = keyboard_check(vk_right) - keyboard_check(vk_left);
var _ver = keyboard_check(vk_down) - keyboard_check(vk_up);


This code gets a value that is either -1, 0, or 1 for both horizontal and vertical movement by checking the appropriate keys (on the horizontal axis it will return -1 for left and 1 for right, and on the vertical axis it will return -1 for up and 1 for down). We will use these values later to set the distance from the player that the view should move to show more in that direction.

We next need to get the current position and size of the camera view so that we can modify it:

oPlayer: Step Event
var _viewX = camera_get_view_x(view_camera[0]);
var _viewY = camera_get_view_y(view_camera[0]);
var _viewW = camera_get_view_width(view_camera[0]);
var _viewH = camera_get_view_height(view_camera[0]);


So, first we get the current position of the camera view assigned to view port element [0], and then we get the width and height. We need this because we will subtract half of those values from the current player position to correctly center the window, which we'll do with the following:

oPlayer: Step Event
var _gotoX = x + (_hor * 200) - (_viewW * 0.5);
var _gotoY = y + (_ver * 150) - (_viewH * 0.5);


So, we now have local variables holding the current view position and the position we want the view to actually move to. Now, we could just set the camera to the new position and leav e it at that, but the movement will be really abrupt and not very pleasant for the player. To get around this we are going to use the lerp function to ease the camera into position (using linear interpolation between the current and goto positions):

oPlayer: Step Event
var _newX = lerp(_viewX, _gotoX, 0.1);
var _newY = lerp(_viewY, _gotoY, 0.1);


Now we can use these x/y values to set the camera:

oPlayer: Step Event
camera_set_view_pos(view_camera[0], _newX, _newY);


Note that throughout the code we refer to the view_camera array, since this holds the ID of the camera that we want to edit, but you can use these functions to edit any camera view at any time, regardless of whether it is an active camera (ie: assigned to a view port) or not. You would simply substitute the view_camera array for the variable that holds the camera ID to be modified.

Your Step Event for the object "oPlayer" should now look like this:

oPlayer Step Event Code

If all has gone correctly, when you run the game you should now have something that looks like this:

Camera Easing Example


Adding Zoom

The camera view now still follows the player around the room, but it does so in a more "organic" way, easing into different positions depending on the direction keys that the user has pressed. This is a great way to let the player see a bit more of the room and so avoid traps or enemies etc... However, the current setup has a problem, which is that the camera can leave the room bounds. This is fine for some games, but for this one it's not very good, as you can see the "edge" of the room which breaks immersion. This needs to be dealt with but we'll do it as we add the next feature... zoom.

Another way we can use camera views is to zoom the visible area of the room in and out. For this we'll use the mouse wheel  to control the "level" of zoom. To start with, we need to have the "oPlayer" Create Event open, as we are going to add a new instance scope variable to control the zoom factor:

oPlayer: Create Event
zoomF = 1;


This will be the multiplying factor that we'll use to set the size of the camera view and so zoom in and out, where 1 is a zoom factor 1:1 with the view port, and less than one will be zooming in and greater than 1 will zoom out. With that added, we need to go to the "oPlayer" Step Event and add this after the code we already have:

oPlayer: Step Event
var _factor = 0.2;
var _mouseW = mouse_wheel_down() - mouse_wheel_up();
zoomF = clamp(zoomF + (_mouseW * _factor), _factor, 2);


Here we set a variable to the value that we want to add/subtract to the zoom factor, and then we check the mouse wheel for movement (as we did with the keyboard checks before, where 1 is wheel down, 0 is no movement, and -1 is mouse wheel up). The final line is where we set the zoomF instance variable to the new value for zooming. Notice that here we use the function clamp to make sure that the returned value does not go below 0.2 or above 2. We obviously don't want the user to zoom out too far or in too much, so using clamp permits us to control this, and we'll be using it again several times in the following code.

The next block of code to add after that is the following:

oPlayer: Step Event
var _lerpH = lerp(_viewH, zoomF * 540, _factor);
var _newH = clamp(_lerpH, 0, room_height);
var _newW = _newH * (960 / 540);
camera_set_view_size(view_camera[0], _newW, _newH);


The code above uses linear interpolation on the current camera view height, using our new zoom value and the amount to add/subtract, to create a new value for the height. Why are we using the height for this? Well, the room is longer than it is higher, so we don't want the zoom to be any larger than the height otherwise we'll be showing the "outside" are, so our maximum zoom level is the one that takes us to that value and no higher. We ensure this on the next line where we use clamp again to make sure that our lerped value is no larger than the room height.

We need to scale the camera view width too, which we do by taking the new height and multiplying it by the aspect ratio of the "base" zoom, before finally using these values to set the size of the camera view.

Now, we could just leave it at that, and if you run the project now then you'll see it works. Sort of! The zoom works fine but will not zoom in to the area around the player, but rather to the current camera position, which then lerps to show the player due to the code we have for following. This is far from ideal so we need to remove this line of code that we have already:

oPlayer: Step Event
camera_set_view_pos(view_camera[0], _newX, _newY); // Remove this line


The following code should now be added after all the other code in the event:

oPlayer: Step Event
var _offsetX = _newX - (_newW - _viewW) * 0.5;
var _offsetY = _newY - (_newH - _viewH) * 0.5;
_newX = clamp(_offsetX, 0, room_width - _newW);
_newY = clamp(_offsetY, 0, room_height - _newH);
camera_set_view_pos(view_camera[0], _newX, _newY);


First we get the offset values for the view based on the previously calculated view position minus the difference between the old view width/height and the new view width/height. We then clamp these values to ensure that they are within the confines of the room area so we don't see anything outside, and then finally we set the camera position.

Your full code block for the Step Event of the object "oPlayer" should now look like this:

oPlayer Step Event Code

If all has gone correctly, when you run the game you should now have something that looks like this:

Camera Zooming Example

Summary

That brings us to the end of our Cameras And Views tutorial. By finishing this tutorial you should now have the following basic knowledge:

  • What the difference is between a camera, the view, and the view port.
  • How to set up a camera view and a view port in the room editor.
  • How to make a camera follow an instance of an object.
  • How to set up a camera - and have it follow an object instance - using code.
  • How to get values from a camera and then use them to change its position.
  • How to create a zoom effect.

You should take some time now to play with the demo and see what else you can do with cameras. For example, you could add a screen shake effect when the player hits a ghost using the camera view angle value - the code would look something like this:

oPlayer: Create Event
shakeMag = 0;
oPlayer: Collision With Ghost Event
shakeMag = 20; 
oPlayer: Step Event
if shakeMag > 0.1
{
shakeMag -= shakeMag / 5;
}
else shakeMag = 0; camera_set_view_angle(view_camera[0], -shakeMag + random(shakeMag * 2));

Or maybe add a second view to show a "mini map" of the whole room? The code for that would look something like this:

oGame: Create Event
view_visible[1] = true;
view_xport[1] = 32;
view_yport[1] = view_hport[0] - (room_height / 10) - 32;
view_wport[1] = room_width / 10;
view_hport[1] = room_height / 10;
view_camera[1] = camera_create_view(32, 32, room_width, room_height, 0, noone, -1, -1, 0, 0);

Or if you feel like tackling something more complicated, how about making the game have two players and then split the game window between them? That's a bit more than we can fit into this summary, but there is a Demo project called Split Screen Cameras which shows how this can be done. Whatever you do, we hope you have a better grasp of cameras now and can use them in your projects to maximum effect.