Quick and Easy Gamma/CRT options

GM Version: Studio 2
Target Platform: Windows
Download: n/a (see code below)
Links: n/a (see video)

Summary:
Tutorial aimed at beginners showing how to implement a quick and easy CRT-like overlay, and how to adjust the "gamma" of your game.

I notice the "CRT" and "Gamma" options are becoming scarce among indie games. They can add charm, wow-factor, and increase UX for a very minimal cost, time and performance-wise.
This quick tutorial is aimed at beginners who want to implement a quick and easy CRT-like overlay, and how to adjust the "gamma" of your game. More advanced users probably figured they could do this already long ago, but this simplified version is attractive because it's so quick and easy, so maybe you'll find use for it, or the principles.
In under 5 minutes, you will be able to do something like this at the click of a button (or the scroll of a bar!)
(Use fullscreen for the video, or it will look like scrap)

First, you will need to create 2 new sprites:
-> spr_pixel, which is a 1x1 solid black pixel
-> spr_crt, which is a 1x3 sprite with a black gradient, like this. Choose the alpha values that look best to you. The video uses alpha of 140, 100 and 40, from top to bottom.
b802c3e5-467d-4a70-b334-94d186e065b0.pngb802c3e5-467d-4a70-b334-94d186e065b0.png the forum antialiases pictures, apparently, blehh

Frist thing, you are going to need something to manage your graphics. I suggest making a new game object obj_display and placing it on a fresh layer at the very top of all your layers (excluding path layers).
This object will be really simple, it contains only 2 variables (4, if you include the surfaces).
In the create event, we make sure to initialize all that we'll need. We'll also setup the surface there for initialization.
GML:
//Initialize our fields
gamma = 0;        // Range 0-1  -> 0 is totally bright
crt = false;    // CRT overlay

//Surfaces are the same size as display
surf_gamma = surface_create(window_get_width(), window_get_height());
surf_crt = surface_create(window_get_width(), window_get_height());


//Setup gamma surface
surface_set_target(surf_gamma);
draw_sprite_tiled(spr_pixel, 0, 0, 0);      //This is the "magic" function
surface_reset_target();

//Setup CRT surface
surface_set_target(surf_crt);
draw_sprite_tiled(spr_crt, 0, 0, 0);
surface_reset_target();

Now, you noticed we set the surface the size of the display, and not the room. As we will draw our overlays in the Draw GUI event, the effect will cover the whole map no matter what.
The draw event seems more complex than it really is, and this is because of the nullchecks needed to avoid bugs. Surfaces can go Houdini on you at any time, and most certainly will on any screen resize.
If all is going smooth sailing, the Draw GUI will simply draw 2 surfaces, and that's it. The performance cost is microscopic. The drawing of the debug values on the last 2 lines actually takes 3x more processing power than drawing both overlays combined.
GML:
//CRT overlay
if(crt){
    if(surface_exists(surf_crt)){
        draw_surface(surf_crt, 0, 0);
    }
    else {
        surf_crt = surface_create(window_get_width(), window_get_height());
        surface_set_target(surf_crt);
        draw_sprite_tiled(spr_crt, 0, 0, 0);
        surface_reset_target();
        draw_surface(surf_crt, 0, 0);
    }
}

//Set our draw alpha to our gamma
draw_set_alpha(gamma);

//Draw the GAMMA overlay
if(surface_exists(surf_gamma)){
    draw_surface(surf_gamma, 0, 0);
} else {
    surf_gamma = surface_create(window_get_width(), window_get_height());
    surface_set_target(surf_gamma);
    draw_sprite_tiled(spr_pixel, 0, 0, 0);
    surface_reset_target();
    draw_surface(surf_gamma, 0, 0);
}

//Reset our draw alpha
draw_set_alpha(1);

//Debug values
var _crt = crt ? "True" : "False";
draw_text(32, 32, "CRT: " + _crt);
draw_text(32, 48, "Gamma: " + string(gamma));
That's pretty much it for the meat and potatoes.
You can use the not boolean operator ! to toggle the CRT effect in pretty much any event you want (menu option, key stroke, transition effect, ect.)
GML:
//Toggles between true and false
crt = !crt;
As for the gamma, it's pretty much the same principle, but you'll probably want to implement it in some sort of sliding bar thing.
Don't forget to clamp your gamma value when you modify them, and add a ceiling under 1, so the screen never turns 100% black (unless you want to allow that, of course).
Note that you need to ADD to gamma value to DARKEN, and vice-versa
GML:
// Darken screen
gamma += 0.01;
//Clamp so it's never completely black
gamma = min(gamma, 0.9);
GML:
// Brighten screen
gamma -= 0.01;
// Clamp so it does not go below 0
gamma = max(gamma, 0);
There you have it! Hope it was useful.

**Bonus**
Improvement ideas:
-> Use a white 1x1 white pixel instead of a black one, and set the color in code for a quick change in tone.
-> Animate the CRT pixel to simulate scanlines. Use another variable to toggle the scanlines on or off.
 
Last edited:
Top