Introduction to Surfaces – Paint Demo

Hey! This tutorial will be an introduction to surfaces. As an example, I will make a simple paint demo, where you can draw using a brush tool.

This tutorial was made for GameMaker Studio 2, but should work with 1.4 too.

Surfaces

A surface is like a canvas with a certain size. After you create a surface, you can draw stuff onto it. Then you can draw that surface to the screen with all the information in it.

In other words, think of it like a separate room where you can draw whatever you want, and collectively draw everything in that room on the current screen with a single function.

To create a surface, you use surface_create(width, height) and store its ID in a variable.

//creates a surface of size 640x480
mySurf = surface_create(640, 480);

Then you can draw stuff onto it by setting it as the target:

surface_set_target(mySurf);

//draws sprite sBoi at 48x48 inside the mySurf surface, NOT on the screen
draw_sprite(sBoi, 0, 48, 48);

surface_reset_target();

The function at the end is used to reset the target, because you no longer want to keep drawing on that surface.

Then you can draw the surface to the screen:

//draws the mySurf surface at 0, 0
draw_surface(mySurf, 0, 0);

This is basically how a surface works.

The “screen” I refer to here is the main game surface. Whatever you draw by default goes to the “application surface”, which is the default surface of the game. Everything in your game is drawn to it (except the GUI events). That surface is drawn automatically.

Surface Memory

After you’re done with a surface, you should remove it from the memory by using this function:

surface_free(mySurf);

However, sometimes the surface will be automatically removed from the memory, if that memory is needed for something else. So, before using a surface, you should check whether it exists – and if it doesn’t, recreate it.

if (!surface_exists(mySurf)){
 mySurf = surface_create(640, 480);
}

So, if the surface mySurf doesn’t exist, a new surface will be created, and assigned to the same variable.

Since this should be checked before using a surface, I recommend putting this code in the Draw Begin event, for every surface you use.

Paint Example

Now we’ll build a little paint like example to demonstrate how surfaces are used.

First of all we need to create an object, I’ll call it oMain. In its Create event, we’ll add:

//Create surface
surf = surface_create(room_width, room_height);

//Vars
brushSize = 8;

This will create a surface named surf with the size of the room. The variable brushSize will be the radius of the paint brush.

Now, we’ll add the Draw Begin event, and add this to recreate the surface if it gets deleted:

//Check surface
if (!surface_exists(surf)){ 
 surf = surface_create(room_width, room_height); 
}

Now let’s come to the main part in the Draw event. Here, we’ll add this:

//Draw to surface
if (mouse_check_button(mb_left)){
 surface_set_target(surf);

 draw_circle(mouse_x, mouse_y, brushSize, false);

 surface_reset_target();
}

//Draw surface
draw_surface(surf, 0, 0);

//Draw to surface

This part will run if the left mouse button is being pressed.

surface_set_target(surf) will set the target to the surface surf. Then, a circle will be drawn at the mouse’s position, with the radius brushSize. So that will be the brush. Then, the surface target will be reset.

//Draw surface

This will draw the surface at (0, 0) in the room.

Now if you place this object in a room and run the game, you will be able to draw:

0.gif

So, to sum up the process, you are drawing circles on a surface, and drawing that surface to the screen.

Surface Backup

There is a problem with this example. Remember the code in Draw Begin? If the surface gets deleted, it will be recreated. That’s the problem. When the surface gets deleted, you will lose everything you had on it.

You can see this problem if you minimize the window and open it again. That removes the surface from the memory. When it is recreated, everything is lost:

1.gif

So… you need to find some way to save the surface.

That’s where buffers come in. Buffers are used to store some data in the memory. We can convert the surface to a buffer and keep it saved; and when it gets deleted, we can restore the surface data from the buffer!

I won’t be explaining how buffers work in this post. But worry not! Read this. Everything you need to know about buffers is in there, including examples.

So, we’ll add this in the Create event:

//Buffer
buffer = buffer_create(4 * room_width * room_height, buffer_grow, 1);

This will create a buffer that we’ll use to save the surface.

4 * room_width * room_height is the size of the buffer, according to the size of the surface. If the width and height of your surface is different, make sure you edit this part.

Now, we’ll add some code to the Draw Begin event. Make sure you remove the previous code from there.

//Check surface
if (!surface_exists(surf)){
 surf = surface_create(room_width, room_height);
 buffer_set_surface(buffer, surf, 0, 0, 0);
}

//Save surface
buffer_get_surface(buffer, surf, 0, 0, 0);

This is very similar to the previous code we had in this event, except for the two new buffer functions.

In the “save surface” part; buffer_get_surface() will be used to save the surface. Basically, that function writes the surface data to a buffer. So, the surface surf will be saved in the buffer buffer.

In the “check surface” part; if the surface doesn’t exist, a new surface will be created, and buffer_set_surface() will copy the surface data in the buffer to the surface. So, the surface data saved in the buffer will be restored.

Now, if you minimize the window and open it, the information on the surface will stay:

2.gif

If it doesn’t work in GameMaker: Studio 1.x, it’s a GM issue. The devs at YoYo Games are working on fixing it.

To optimize this method, you can add a condition to the buffer_get_surface() function, so that it only saves the surface data when the left mouse button is released:

//Save surface
if (mouse_check_button_released(mb_left)){
 buffer_get_surface(buffer, surf, 0, 0, 0);
}

If you do this, make sure you copy the buffer_get_surface() line and put it in the Create event as well (after buffer_create()), so that you have a surface backup to begin with.

Better Brush

The current brush is not perfect. If you draw fast, you will be able to see the circles separately:

3.gif

To fix this, we’ll use a custom script, and call it draw_circle_line():

/// draw_circle_line(x1, y1, x2, y2, radius);
/// @arg x1
/// @arg y1
/// @arg x2
/// @arg y2
/// @arg radius

var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;
var radius = argument4;

var dir = point_direction(x1, y1, x2, y2);
var dist = point_distance(x1, y1, x2, y2);

for(var i=0; i<=dist; i++){
 var xx = x1 + lengthdir_x(i, dir);
 var yy = y1 + lengthdir_y(i, dir);

 draw_circle(xx, yy, radius, false);
}

This script will draw a rounded line, made up of many circles.

But before using this script, we need to create two variables, which we’ll do in the Create event:

mouse_x_previous = mouse_x;
mouse_y_previous = mouse_y;

These variables will contain the mouse position for the previous Step of the game.

Next, we’ll add this same code to the Draw GUI End event, because that is the last event that runs in a Step. So, do that.

Then in the Draw event, we’ll replace the “draw_circle(mouse_x, mouse_y, brushSize, false)” line with this:

draw_circle_line(mouse_x, mouse_y, mouse_x_previous, mouse_y_previous, brushSize);

This will draw a rounded line from the current mouse position to the previous mouse position.

Now if you draw fast, you can see that it draws correctly:

4.gif

Color

Coloring is simple. Make a variable that will store the color for the brush (Create event):

brushColor = c_white;

Then use that color in a draw_set_color() function before drawing the brush (Draw event), and then reset the color to white after you’re done drawing it:

//Draw to surface
if (mouse_check_button(mb_left)){
 surface_set_target(surf);

 draw_set_color(brushColor);

 draw_circle_line(mouse_x, mouse_y, mouse_x_previous, mouse_y_previous, brushSize);

 draw_set_color(c_white);

 surface_reset_target();
}

Change that variable when you want to draw in a different color:

brushColor = c_red;

5.gif

Read this tutorial if you want to make a color picker like this:

Capture.PNG

Conclusion

Hope this tutorial helped you! I’d appreciate if you helped in getting the word out by sharing this post. 🙂

Download this project (GMS2)


Check out my Udemy course (discount link) to learn how to create a platformer like this:

swim.gif


If you encounter any problems with this tutorial or just want to ask something, contact me on Discord through this server (my username is Senpai Matharoo).

See you later, and till then, happy dev’ing!

Advertisements

3 thoughts on “Introduction to Surfaces – Paint Demo

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s