SOLVED Simple light effect

Papa Doge

Member
I'm attempting to add a simple light effect to my game where certain objects have a light radius around them when the world is dark. I have the surface drawing correctly with the dark overlay but my light punch outs don't seem to be working.

I've create an object called obj_lighting_render to do this and place it on a separate Lighting layer:

Code:
/// CREATE EVENT

/* -------------------- */

lighting_surface = -1;
cw = camera_get_view_width(view_camera[0]);
ch = camera_get_view_height(view_camera[0]);
depth = -999999; // I had to do this because my room objects have a depth of -bbox_bottom and where floating above the overlay
Code:
/// DRAW

/* -------------------- */

if (!surface_exists(lighting_surface)) {
    
    lighting_surface = surface_create(cw, ch);
    
}

// Set the surface we want to draw on
surface_set_target(lighting_surface);

// Paint this surface black with some transparency
draw_clear_alpha(c_black, 0.8);

gpu_set_blendmode(bm_subtract);

// Draw the punch out for the player
draw_circle_color(obj_player_suit.x, obj_player_suit.y, 64, c_white, c_white, false);

gpu_set_blendmode(bm_normal);

// Reset to the application surface
surface_reset_target();

// With the application surface reset, let's draw that lights surface over it
var cx = camera_get_view_x(view_camera[0]);
var cy = camera_get_view_y(view_camera[0]);
draw_surface(lighting_surface, cx, cy);
I have a parent object for lights that I'd like to add my player object to along with some other objects at some point, but since nothing is working I was testing to see if I could even draw a white circle around the player and it's not working. Any ideas what I might be doing wrong here?
 

rytan451

Member
Consider using the draw end event to avoid having to do that depth hack.

You might also wish to to surface_set_target(application_surface);, and then draw the lighting surface at (0, 0), to avoid problems with views.
 

Papa Doge

Member
Thanks for the tip concerning draw_end. As for `surface_set_target(application_surface)` where would I use that? Because if I replace `surface_reset_target()` Game Maker gets upset and throws an error saying I have to use it.

Code:
___________________________________________
############################################################################################
ERROR in
action number 1
at time step0
of time line <undefined>:

Unbalanced surface stack. You MUST use surface_reset_target() for each set.############################################################################################
 

Papa Doge

Member
Update: I found out why I'm not seeing the punch out but I'm still not sure how to achieve my original goal.

Code:
draw_circle_color(cw / 2, ch / 2, 48, c_blue, c_blue, false);
I used this instead of drawing a circle at the player's location and I see a white punch out right in the middle of the camera view, which makes sense, because my surface is drawing over the camera view and not the room.

So what I need to figure out is how to draw that circle on the player's current location knowing that the surface will draw to the camera view and the camera is moving.
 

Papa Doge

Member
Ah, OK I figured it out.

Code:
draw_circle_color(obj_player_suit.x - cx, obj_player_suit.y - cy, 48, c_white, c_white, false);
I just needed to subtract the position of the camera from the position of the object in the room to get the position relative to the surface being drawn.
 

CMAllen

Member
You're mostly on the right track, but blend modes don't work intuitively with the alpha channel. So there are couple ways you can get around this, but if your lighting method is basic, I'll suggest the simplest method -- draw your lighting surface itself in bm_subtract mode.

"But wait, you just said alpha channels don't work right with blend modes."

You're right. I did. That's why you won't use them. You don't need them, in fact. The 'alpha value' of your darkness is already there based on the luminosity of each pixel. If you've ever played around in Photoshop, what happens if you set a white-filled layer to multiply? That's right. It disappears. bm_subtract is the same, only with the exact opposite math. To elaborate, white pixels have a value of 255, 255, 255, and black pixels have a value of 0, 0, 0. In bm_subtract, the value of the source pixel is literally subtracted from the destination pixel, so drawing a white pixel means subtract 255 from all three color channels: ie black. Drawing a black pixel means subtracting 0 from all 3 color channels, ie draw nothing.

Knowing this, you would create your light surface 'inverted' from what you want drawn. Using your pre-existing code:
Code:
/// DRAW

/* -------------------- */

if (!surface_exists(lighting_surface)) {
   
    lighting_surface = surface_create(cw, ch);
   
}

// Set the surface we want to draw on
surface_set_target(lighting_surface);

// Paint this surface ~80% white, 100 opaque.
var fill_color =   make_colour_rgb(200, 200, 200);
draw_clear_alpha(c_white, 1.0); //(note: I forget if 1.0 is fully opaque or transparent)

// Draw the punch out for the player
draw_circle_color(obj_player_suit.x, obj_player_suit.y, 64, c_black, c_black, false);

// Reset to the application surface
surface_reset_target();

// With the application surface reset, let's draw that lights surface over it
var cx = camera_get_view_x(view_camera[0]);
var cy = camera_get_view_y(view_camera[0]);

gpu_set_blendmode(bm_subtract);
draw_surface(lighting_surface, cx, cy);
gpu_set_blendmode(bm_normal);

"Okay. But what if I want softer lights?"

Same basic idea. Say, for example, you wanted to draw your lights like this instead of a solid circle:


Well, you draw exactly that in bm_subtract to your lighting surface in place of the solid circle. Notice that this process is still actively ignoring the alpha channel. That means the above image is also 100% opaque.
 
Last edited:
Top