Legacy GM Dynamic Shadow

C

CoderJoe

Guest
GM Version: GM:Studio
Target Platform: ALL
Download: N/A
Links: My Blog Post on this
Old forum post

Summary:
Tutorial on how to make dynamic shadow. Based on code by flokkienathur from the archived forums.

Tutorial:
(BTW this is my first tutorial post. I hope it helps)
Hey there. On the old GM forums there was a post on dynamic light. It's pretty cool, however I wanted to make just dynamic shadows to give a different look. Some of the code is based on flokkienathur's post (linked above). I also did a blog post for this tutorial (also linked above).
The basic idea is this: we create casters and give them a shape in code (just points in an array). Then we create a trianglestrip primitive to draw the shadow. (Below is just what I put on my blog)

SETUP

First off let’s create some objects. Create one called oParCaster and oParLight. These will be the parent object for our effects. Also create an object called oLightMap. This object will store our surface for our light map.

Lightmap scripts

Next we will create some scripts for the light map.

scrLightMapInit:
Code:
///scrLightMapInit(width, height, parCaster, parLight)

//Use width and height of room as long as the room is somewhat small
lm_width = argument0; //width of lightmap (usually width of room)
lm_height = argument1; //height of lightmap (usually height of room)
lm_surface_id = surface_create(lm_width, lm_height); //Store the id of the surface so we can access it

//Parents of casters and lights
lm_parent_caster = argument2;
lm_parent_light = argument3;
We will call this script in the create event of our lightmap object. Like this:
Code:
scrLightMapInit(room_width, room_height, oParCaster, oParLight);
The next script will update the light map (put it in the step event of the light map object). If we didn’t update the light map then we would just have static shadows (ones that don’t move). This can be what you want if you want to “bake” in the shadows. In that case you would only call this script once.

scrLightMapUpdate:
Code:
///scrLightMapUpdate()

//check if the lightmap surface exists
if(!surface_exists(lm_surface_id)){
  //if it does not exist, recreate it
  lm_surface_id = surface_create(lm_width,lm_height);
}

surface_set_target(lm_surface_id); //do all the next events to our light map

draw_clear(make_colour_rgb(0,0,0)); //clear the surface

var lmid = id; //lightmap object id

if (lm_parent_light != -1) { //check that there is a parent object for the light
with (lm_parent_light) { //do the next events for all lights that exist

  var lx = x; //light x pos
  var ly = y; //light y pos

  var lr = light_radius; //light radius (set in the light object)

  var lid = id; //light object id

  with(lmid.lm_parent_caster) { //get the caster parent (from the lightmap)
  if point_in_circle(x,y,lx,ly,lr) { //check if the caster is in the light’s radius
  //start drawing shadow
  draw_primitive_begin(pr_trianglestrip);
  draw_set_color(c_white);
  //draw shadow primitive from each point on caster
  for(i = 0; i < caster_point_count-1; i++){
  scrLightMapDrawShadow(id, lid, lr, caster_point_x[i], caster_point_y[i], caster_point_x[i+1], caster_point_y[i+1]);
  }
  //close the shape from the last point of the caster to the first point
  scrLightMapDrawShadow(id, lid, lr, caster_point_x[caster_point_count-1],caster_point_y[caster_point_count-1], caster_point_x[0], caster_point_y[0]);
  draw_primitive_end();
  //stop drawing shadow
  }

  }

}

}

//reset this light surface
surface_reset_target();
I’m gonna explain how the shadows are drawn in a minute (there are some bugs still that I’m working out). Don’t run anything yet we still have a ways to go. Call this next script in the draw event of the light map object. It simply draws our surface will blending.

scrLightMapDraw:
Code:
///scrLightMapDraw()
draw_set_blend_mode(bm_subtract);
draw_surface(lm_surface_id,0,0);
draw_set_blend_mode(bm_normal);
Lights:

Put this next script in the create event of you light object (YOUR light object not the parent light object). Make sure to set the parent as the parent light object!

scrLightInit:
Code:
///scrLightInit(radius)
light_radius = argument0;
Pretty simple. The original light engine by flokkienathur had support for other things such as sprite lights and other fancy stuff. Since we are making a shadow engine though our lights aren’t actually doing anything other than telling the game where we want shadows to be.

Casters:

Casters are going to be our “light blockers” so to speak. We will set up point in an array that store their shape. We then use this shape to cast shadows. Like with the lights, simply add these scripts to your caster objects (walls, player, etc.) and make the caster parent the parent.

scrCasterInitPolygon:
Code:
///scrCasterInitPolygon()
caster_point_count = 0;
scrCasterAddPoint:
Code:
///scrCasterAddPoint(x, y)
caster_point_x[caster_point_count] = argument0;
caster_point_y[caster_point_count] = argument1;
caster_point_count += 1;
These next scripts are for specifying what shape. You could always make your own shapes by using the previous two scripts. These are just easy presets.

scrCasterInitCircle:
Code:
///scrCasterInitCircle(radius, fractions)
scrCasterInitPolygon();
var radius = argument0;
var fractions = argument1; //how accurate you want the circle to be
var i;
var m;
m = 2 * 3.141592654 / fractions;
for(i = 0; i < fractions; i++){
  scrCasterAddPoint(cos(m * i) * radius, sin(m * i) * radius);
}
scrCasterInitRectangle:
Code:
//scrCasterInitRectangle(left, top, bottom, right)
scrCasterInitPolygon();
var left = argument0;
var top = argument1;
var right = argument2;
var bottom = argument3;

scrCasterAddPoint(left,top);
scrCasterAddPoint(right,top);
scrCasterAddPoint (right,bottom);
scrCasterAddPoint (left,bottom);
scrCasterInitSprite
Code:
///scrCasterInitSprite()
scrCasterInitPolygon();

scrCasterAddPoint (-sprite_xoffset,-sprite_yoffset);
scrCasterAddPoint (-sprite_xoffset + sprite_width,-sprite_yoffset);
scrCasterAddPoint (-sprite_xoffset + sprite_width,-sprite_yoffset + sprite_height);
scrCasterAddPoint (-sprite_xoffset,-sprite_yoffset + sprite_height);
Great the caster scripts are all setup! Put one of the shape scripts in the create event of your caster object and set the parent as the caster parent object and there you go!

Next we will actually set up the drawing script. This one requires more explanation so I left it for last.

scrLightMapDrawShadow:
Code:
///scrLightMapDrawShadow(caster, light, lightRadius, x1, y1, x2, y2)
var caster = argument0; //caster id
var light = argument1; //light id

var lightRadius = argument2; //radius

//caster point are relative to the caster, thus we must add the caster’s position to compensate and get the actual position in the world
var Ax = argument3 + caster.x; //startx
var Ay = argument4 + caster.y; //starty

var Bx = argument5 + caster.x; //endx
var By = argument6 + caster.y; //endy

var Lx = light.x; //light x
var Ly = light.y; //light y


//angles to calculate end points (these are just from the light to the caster points)
var shadowAngleA = point_direction(Lx, Ly, Ax, Ay);
var shadowAngleB = point_direction(Lx, Ly, Bx, By);

//Next we calculate the end points of the shadow
//The shadow will be drawn from our caster points to the end of the light’s radius
var E1x, E1y, E2x, E2y;
E1x = Lx+lengthdir_x(lightRadius, shadowAngleA);
E1y = Ly+lengthdir_y(lightRadius, shadowAngleA);
E2x = Lx+lengthdir_x(lightRadius, shadowAngleB);
E2y = Ly+lengthdir_y(lightRadius, shadowAngleB);

//We will fade the shadow out as it gets to the end of the light radius so that it will look better

var c = c_white;
var alphaEnd = 0;
var dist = point_distance(caster.x, caster.y, Lx, Ly);
if dist == 0
  dist = 1;
var lightToCaster = point_direction(Lx,Ly, caster.x, caster.y);
var totalDist = point_distance(Lx,Ly, Lx+lengthdir_x(lightRadius,lightToCaster), Ly+lengthdir_y(lightRadius,lightToCaster));

//This is our start alpha, it will be darker if the caster is closer to the light
var alphaStart = 0.25*(1-(dist/totalDist));

//We now add vertexes to our shadow primitive
draw_vertex_colour(Ax, Ay,c,alphaStart); //start of point a
draw_vertex_colour(E1x, E1y,c,alphaEnd); //end of point a
draw_vertex_colour(Bx, By, c, alphaStart); //start of point b
draw_vertex_colour(E2x, E2y, c, alphaEnd); //end of point b
The main glitch here is that one shadow can be drawn over another shadow which messes up our nice effect. I have yet to figure out how to fix this. My idea would be to test that the end point’s of the shadow don’t run into another caster. This should prevent shadows from drawing on top of each other. I will post an update when I fix this.
 
Last edited by a moderator:

chance

predictably random
Forum Staff
Moderator
Your example would be much improved if you included some screenshots of the effect it creates. That would also make it clear how if differs from the earlier example you referenced.

And as always when your example uses multiple objects or multiple scripts as this does, providing a GM source file for download is useful. But that's up to you.
 
T

Trontastic

Guest
Since this seems to be the only remaining forum with anyone who knows about flokkienathur's tutorial I'm going to post this question here.

I'm attempting to attach one of his lights to an object to make it function as a flashlight. However I can't seem to get the sprite being used to either follow the image angle of the flashlight or the image angle of the light object itself. In the script for initializing the sprite it gives you an option to set the rotation

Code:
//DOES: Makes this object a point light

//USAGE: light_init_sprite(<sprite>, <index>);

light_set_type(1);                                                     //sets the type (1 = sprite light)
light_set_color(c_white);                                          //sets the color
light_set_radius(sprite_get_width(argument0));      //sets the radius
light_set_sprite(argument0);                                    //set the sprite
light_set_rotation(0);                                                //sets the lights rotation (0 for point lights of course)
light_set_sprite_index(argument1)                          //sets the sprite index
light_set_height(8);                                                  //sets the light height
However, my attempts to create a variable in either the flashlight object or the light object for this sprite to derive its angle from have failed.

Code:
Flashlight CREATE
dir = image_angle;

with (instance_create(x+64,y,obj_light_flashlight))
{
image_angle = other.image_angle;
attached = other.id;
offsetDir = x - other.x;
offsetDist = y - other.y;
initialAngle = 0;
}

// appear on top of player object
depth = obj_player_skin.depth-1;


obj_light_flashlight END STEP
if (instance_exists(obj_flashlight)) {
    x = attached.x + lengthdir_x(offsetDist, offsetDir + attached.image_angle);
    y = attached.y + lengthdir_y(offsetDist, offsetDir + attached.image_angle);
    image_angle = offsetDir + obj_flashlight.image_angle + initialAngle;
}
The light object itself does attach and move properly, however the light sprite only points at where the rotation variable is set, which is 0. I've attempted to create a variable as you can see called dir in the flashlight create event but that didn't work. I tried the same variable in the light create event itself and that didn't work.

Can anyone think of a way to fix this?
 
Last edited by a moderator:
C

CoderJoe

Guest
Im not sure I completely understand your question but I think all you need to do is use the light_set_rotation to update the rotation to your flashlight object
 
T

Trontastic

Guest
Sorry for the delayed reply. I'm trying to get the light from the flashlight to follow the direction of the flashlight object. The green box is the light origin you can ignore that. The problem is that the light doesn't rotate with the object as you can see from the image I posted. I tried directing "r" to follow the image angle and the direction of the flashlight but it didn't seem to work. Does that clear it up a bit? The code is below.

Code:
// MUST HAVE: Create surface for light;
sur = surface_create(sprite_width, sprite_height);

//NEW LIGHTING
light_init_point(576,c_white);
light_init_sprite(spr_flashlightlight,0);
//light_set_color(make_color_rgb(random(255),random(255),random(255)));
light_set_color(c_white);
r = obj_flashlight.direction;
 

Attachments

C

CoderJoe

Guest
In your update code (probably in step event) is the rotation being set? Is the light parented to anything?
 
T

Trontastic

Guest
I see what I did wrong. You were right in the step event I was still using the variable "r" to be passed as the argument and r was set on create as the direction of the flashlight object. I had to change that to obj_flashlight.image_angle and that fixed it. Thanks for helping me troubleshoot this.
 
R

Resurgam

Guest
First, thanks for this great light system and tutorial, been searching around a bit and this suits my need perfectly! :)

Small mistake (just a typo), in scrCasterInitRectangle you wrote
(left, top, bottom, right)
instead of​
(left, top, right, bottom)​
(Also a picture of the end result if anyone wants one)
I know it's a year and a half since this was posted, but I just found this tutorial and wanted to say thanks :)
 

Attachments

Y

Yummy Funny Bunny

Guest
I also have a question about flokkienathur's Light and Shadows tutorial, so I apologize if this is not the right place to post anymore:

What I am trying to achieve is to have the room fully illuminated except for what is behind shadows.

In this first picture I am using "light_init_point" function within the player (the player is the light source). I would like to get rid of all of that light fading at the edge of the screen and I am not sure if that is possible.

In the second picture I am using "light_init_sprite" in the player, and used a big circle sprite that covers the whole room. I got some funky results from the shadows. The first result is that shadows stop appearing until you are within a curtain distance to them. Secondly, the shadows do not cast across the room, they only go a certain distance. Is there a fix to that?

While I'm asking, I was wondering if it is possible to make the edges of the shadow lines fuzzy (sort of like the fading out that the light is doing in the first picture), instead of being hard lines?
 

Attachments

jujubs

Member
I know this is old, but could anyone help me out with this? I've tried replicating the code here, but when I run the game, nothing appears to happen.
 

Genetix

Member
Old post I know, but do have a new question. Is there any easy way to handle caster objects that have been rotated? I have fence objects in my game that are Casters, however in my rooms I rotate them to do different angles but can't change the angle of the shadows they cast.
 
Top