• Hey! Guest! The 40th (!!!) GMC Jam will take place between February 25th, 12:00 UTC to March 1st 12:00 UTC. Why not join in this very special anniversary jam! Click here to find out more!

GMS 2 [Solved] Trying to obscure lights in a top-down game (i.e. lights that appear to have depth)

Hi everyone,

I'm working on a top down perspective game that has a day/night cycle. I've got some lights in the game, and am using the blendmode bm_add when drawing them on the night sky surface (night_cycle_surf). After the lights are drawn, the surface is drawn over the player's view and all is good, except... those lights appear on top of some objects that are meant to be in front of them (as shown here)
pix.jpg
Above, you can see two clear problems with the lights. The street lamp's light is shining onto the roof of the house across the street, and the bus's lights are shining through the house itself.

Here's how I'm coding this current system...

DRAW EVENT CODE for the obj_TOD (time of day object):
GML:
if(outdoors == true)
{
    if(!surface_exists(night_cycle_surf))
    {
        night_cycle_surf = surface_create(camera_get_view_width(view_camera[0]) * 1.2,camera_get_view_height(view_camera[0]) * 1.2);
    }

    surface_set_target(night_cycle_surf);
    
        draw_clear(tod_colour); //created the surface and told it to be black
        
        
    //ACCOUNT FOR LIGHTS
    if(lights == true) //aka obj light exists
    {
        gpu_set_blendmode(bm_add);
            
        with(obj_light)
        {
            if(active == true)
            {
                draw_set_alpha(g_alpha);
                draw_sprite_ext(my_sprite,0,my_x + offset_x,my_y + offset_y,scale, scale,0,colour,g_alpha);
                draw_set_alpha(1.0);   
            }
        }       
        gpu_set_blendmode(bm_normal); //change to normal
    }   
        
    surface_reset_target(); //******
    
    if(obj_player.tea_active != true) //not relevant at the moment
    {
        gpu_set_blendmode_ext(bm_dest_color,bm_zero);
    
        //draw the actual surface we've created
        alpha = 0.1;
        draw_surface_ext(night_cycle_surf,camera_get_view_x(view_camera[0]),camera_get_view_y(view_camera[0]),1,1,0,c_white,alpha);
    
        gpu_set_blendmode(bm_normal);
    }
    else //if tea active IS true
    {
        
            
    }
}
So, I need to find a way to block those lights from shining through objects meant to be in front of them. I was chatting with a friend and he suggested a solution where the objects blocking the light (I'm calling them light covers) are drawn onto the night cycle surface after the lights are, but since the lights are using the bm_add blendmode they still shine through.

Is there a way I can draw the light covers onto the surface after the lights in a way that can counteract the bm_add effect of the lights? I'd love to hear some suggestions as my current solution leaves many rooms for errors.
 
I've done some reading and experimenting and I'm getting close. Not quite there, but very close I think. I'm now drawing the lights and light covers on a new surface, as well as making use of drawing only in the alpha channel.
New Draw Code:
GML:
if(outdoors == true)
{
    if(!surface_exists(night_cycle_surf))
    {
        night_cycle_surf = surface_create(camera_get_view_width(view_camera[0]) * 1.2,camera_get_view_height(view_camera[0]) * 1.2);
    }
    if(!surface_exists(light_alpha_surf))
    {
        light_alpha_surf = surface_create(camera_get_view_width(view_camera[0]) * 1.2,camera_get_view_height(view_camera[0]) * 1.2);
    }
    
    surface_set_target(light_alpha_surf);
    
    gpu_set_blendenable(false); //sprites are drawn full opaque
    gpu_set_colorwriteenable(false,false,false,true); //only draw to the alpha channel
    draw_clear_alpha(c_black,0); //clear the surface to 0 alpha
    
    draw_set_alpha(1);
    //draw the lights only in the alpha channel of light alpha surface
    if(lights == true)
    {
        with(obj_light)
        {
            if(active == true)
            {
                draw_sprite_ext(my_sprite,0,my_x + offset_x,my_y + offset_y,scale, scale,0,colour,1);
            }
        }       
    }   
    //draw the light covers only in the alpha channel to hopefully remove any previously drawn light
    draw_set_alpha(0);
    if(light_cover == true)
    {
        with(obj_light_cover)
        {
            draw_sprite(sprite_index,image_index,x - camera_get_view_x(view_camera[0]),y - camera_get_view_y(view_camera[0])); //the subtraction is to account for drawing on a surface
        }
    }
    draw_set_alpha(1);
    
    //now draw the light sprites ONLY where the alpha is not 0
    gpu_set_blendenable(true); //only opaque parts of sprits are drawn
    gpu_set_colorwriteenable(true,true,true,true); //RGB and A channels will all be drawn in now
    gpu_set_blendmode_ext(bm_dest_alpha,bm_inv_dest_alpha);
    gpu_set_alphatestenable(true);
    //draw the lights as normal, but shouldn't be appearing where covers are obscuring the light
    if(lights == true)
    {
        with(obj_light)
        {
            if(active == true)
            {
                draw_set_alpha(1); //rather than g_alpha
                draw_sprite_ext(my_sprite,0,my_x + offset_x,my_y + offset_y,scale, scale,0,colour,1);
                draw_set_alpha(1.0);   
            }
        }       
    }
    gpu_set_alphatestenable(false); //reset
    gpu_set_blendmode(bm_normal); //reset
        
    surface_reset_target(); //******
    
    //now combine night surf with the lights
    surface_set_target(night_cycle_surf);
    
    draw_clear(tod_colour); //draw the night sky colour
    gpu_set_blendmode(bm_add);
    draw_surface_ext(light_alpha_surf,0,0,1,1,0,c_white,1); //draw the light/shadow surf onto the night surf
    gpu_set_blendmode(bm_normal);
    
    surface_reset_target();
    
    //FINALLY draw the finished altered surface at 0.1 alpha over the screen
    if(obj_player.tea_active != true) //not relevant at the moment
    {
        gpu_set_blendmode_ext(bm_dest_color,bm_zero);
    
        //draw the actual surface we've created
        alpha = 0.1;
        draw_surface_ext(night_cycle_surf,camera_get_view_x(view_camera[0]),camera_get_view_y(view_camera[0]),1,1,0,c_white,alpha);
    
        gpu_set_blendmode(bm_normal);
    }
    else //if tea active IS true
    {
        
            
    }
}
Resulting in this:
pix2.jpg
The light cover is managing to counteract the light, and cover it up, but I'm unfortunately drawing the transparent part of the sprite as opaque. I know this has to do with the gpu_set_blendenable function, but I still need to figure out how to avoid this.

Again, any help is much appreciated as I pick away at this.
 
Hello, it's me again. I think I've managed to do the impossible, and solve my own problem, haha.

pix 3.jpg
It's a slightly different location, but showing off the same concept. The train is passing underneath a bridge, and its headlights aren't shining through. This wouldn't have been possible with the duct tape solution I had been using for the house, so I'm glad I didn't go with that in the end.

Here's my final draw code for anyone looking to do this similar effect. May my banging my head against a wall serve the greater good. Also, shout out to @Nocturne for some guidance :)
Draw Code:
GML:
if(outdoors == true)
{
    if(!surface_exists(night_cycle_surf))
    {
        night_cycle_surf = surface_create(camera_get_view_width(view_camera[0]) * 1.2,camera_get_view_height(view_camera[0]) * 1.2);
    }
    if(!surface_exists(light_alpha_surf))
    {
        light_alpha_surf = surface_create(camera_get_view_width(view_camera[0]) * 1.2,camera_get_view_height(view_camera[0]) * 1.2);
    }
    if(!surface_exists(light_cover_surf))
    {
        light_cover_surf = surface_create(camera_get_view_width(view_camera[0]) * 1.2,camera_get_view_height(view_camera[0]) * 1.2);
    }
    //DRAW THE LIGHTS USING ONLY ALPHA CHANNEL
    surface_set_target(light_alpha_surf);
    
    gpu_set_blendenable(false); //sprites are drawn fully opaque
    gpu_set_colorwriteenable(false,false,false,true); //only draw to the alpha channel
    draw_clear_alpha(c_black,0); //clear the surface to 0 alpha
    
    draw_set_alpha(1);
    //draw the lights only in the alpha channel of light alpha surface
    if(lights == true)
    {
        with(obj_light)
        {
            if(active == true)
            {
                draw_sprite_ext(my_sprite,0,my_x + offset_x,my_y + offset_y,scale, scale,0,colour,1);
            }
        }       
    }   
    /*
    //draw the light covers only in the alpha channel to hopefully remove any previously drawn light
    draw_set_alpha(0);
    if(light_cover == true)
    {
        with(obj_light_cover)
        {
            draw_sprite(sprite_index,image_index,x - camera_get_view_x(view_camera[0]),y - camera_get_view_y(view_camera[0])); //the subtraction is to account for drawing on a surface
        }
    }
    draw_set_alpha(1);
    */
    surface_reset_target();
    
    //CLEAR A NEW SURFACE TO BLACK, DRAW LIGHTS, COVER LIGHTS WITH BLACK SHADOWS
    surface_set_target(light_cover_surf);
    //now draw the light sprites ONLY where the alpha is not 0
    gpu_set_blendenable(true); //only opaque parts of sprits are drawn
    gpu_set_colorwriteenable(true,true,true,true); //RGB and A channels will all be drawn in now
    gpu_set_blendmode(bm_normal);
    //clear surface with black
    draw_clear_alpha(c_black,1);
    //draw lights
    if(lights == true)
    {
        with(obj_light)
        {
            if(active == true)
            {
                draw_sprite_ext(my_sprite,0,my_x + offset_x,my_y + offset_y,scale, scale,0,colour,1);
            }
        }       
    }
    //cover lights
    if(light_cover == true)
    {
        with(obj_light_cover)
        {
            draw_sprite_ext(sprite_index,image_index,x - camera_get_view_x(view_camera[0]),y - camera_get_view_y(view_camera[0]),1,1,0,c_black,1); //the subtraction is to account for drawing on a surface
        }
    }
    surface_reset_target();
    
    //COMBINE SHADOWS SURFACE WITH ALPHA SURFACE, DRAWING ONLY WHERE THE ALPHA IS NOT 0 ON THE ALPHA SURFACE
    surface_set_target(light_alpha_surf);
    gpu_set_blendmode_ext(bm_dest_alpha,bm_inv_dest_alpha); //only drawing where the alpha is not 0
    gpu_set_alphatestenable(true);
    //draw the lights as normal, but shouldn't be appearing where covers are obscuring the light
    
    draw_surface_ext(light_cover_surf,0,0,1,1,0,c_white,1); //draw the light/shadow surf onto the night surf
    
    gpu_set_alphatestenable(false); //reset
    gpu_set_blendmode(bm_normal); //reset
        
    surface_reset_target(); //******
    
    //DRAW THE MODIFIED ALPHA LIGHT SURFACE TO THE NIGHT CYCLE SURFACE USING ADD BLENDMODE
    //now combine night surf with the lights
    surface_set_target(night_cycle_surf);
    
    draw_clear(tod_colour); //draw the night sky colour
    gpu_set_blendmode(bm_add);
    draw_surface_ext(light_alpha_surf,0,0,1,1,0,c_white,1); //draw the light/shadow surf onto the night surf
    gpu_set_blendmode(bm_normal);
    
    surface_reset_target();
    
    //FINALLY draw the finished altered surface at 0.1 alpha over the screen
    if(obj_player.tea_active != true) //not relevant at the moment
    {
        gpu_set_blendmode_ext(bm_dest_color,bm_zero);
    
        //draw the actual surface we've created
        alpha = 0.1;
        draw_surface_ext(night_cycle_surf,camera_get_view_x(view_camera[0]),camera_get_view_y(view_camera[0]),1,1,0,c_white,alpha);
    
        gpu_set_blendmode(bm_normal);
    }
    else //if tea active IS true
    {
        
            
    }
}
 
Top