GameMaker [HELP] Draw Shadows Beneath Player

T

TheOakNuggins

Guest
Currently I am drawing a shadow at my player object's coordinates. This doesn't look to good when you jump in the air. So I have a few questions.


1Q.a) How should I go about creating a system that will draw the shadow on whatever object is underneath the player. This way you can jump and still see the shadow underneath you (like in Mario 64)
1Q.b) Can this be scaleable so that the shadow shrinks by an adjustable factor when in the air. (i.e. out of 1, 1 would be touching the ground, full shadow. 0.1 would be at max jump height).

2Q) Is there any way to rotate it when walking up a slope? I mean to transform-rotate it where the pixels don't rotate, but the sprite is shifted into a tilted image

How I check for ground:
Code:
//check for ground
if (place_meeting(x, y+1, oSolid)) and (jkey) {
    vspd = jspd;
}

if jkeyrelease {
    vspd += 1.5;
    }
Attatched image is how it currently looks.

DYgiGHvW4AEGBor.jpg-large.jpg

Screen Shot 2018-03-17 at 9.29.15 PM.png

Screen Shot 2018-03-18 at 1.18.42 AM.png

Code:
//CREATE EVENT:
solidxScale = sprite_width;
solidyScale = sprite_height;



//DRAW EVENT:
draw_sprite_stretched(sSolid, 0, self.x, self.y, solidxScale, solidyScale);    //draws sprite going to mask

gpu_set_blendenable(false)            //turns colors off
gpu_set_colorwriteenable(false,false,false,true);
draw_set_alpha(0);
draw_rectangle(0,0, room_width,room_height, false);    //draw a large rectangle to be drawn on
  
draw_set_alpha(1);    //draw masking alpha
draw_sprite_stretched(sSolid, -1, self.x, self.y, solidxScale, solidyScale);
gpu_set_blendenable(true);
gpu_set_colorwriteenable(true,true,true,true);

gpu_set_blendmode_ext(bm_dest_alpha,bm_inv_dest_alpha);    //colors back on
gpu_set_alphatestenable(true);

draw_sprite(sPlayer,0, mouse_x,mouse_y);    //draws covering mask
gpu_set_alphatestenable(false);
gpu_set_blendmode(bm_normal);
 
Last edited by a moderator:

bbbower

Member
What are you using for your collision detection?
You could do a while loop to check starting at the players x and y to check for collision then draw after.
Code:
var shadowx = x;
var shadowy = y;
while (!howeveryoucheckcollision(shadowx,shadowy)) {
  shadowy++;
}
//Draw shadow @ shadowx / shadowy
as far as detecting slopes you'd have to check a certain tile type, and honestly it'd probably be easier to find or use a pixel fragment shader to do that. or make the shadow smaller when you are air born so you couldn't see the ugly over tilting that might occur at the points where the tilt meets flat ground.
 
T

TheOakNuggins

Guest
What are you using for your collision detection?
You could do a while loop to check starting at the players x and y to check for collision then draw after.
Code:
var shadowx = x;
var shadowy = y;
while (!howeveryoucheckcollision(shadowx,shadowy)) {
  shadowy++;
}
//Draw shadow @ shadowx / shadowy
as far as detecting slopes you'd have to check a certain tile type, and honestly it'd probably be easier to find or use a pixel fragment shader to do that. or make the shadow smaller when you are air born so you couldn't see the ugly over tilting that might occur at the points where the tilt meets flat ground.
just updated post
 

bbbower

Member
Code:
// Put this in your movement code
shadowx = x;
shadowy = y;
while (!place_meeting(shadowx,shadowy)) {
  shadowy++;
}

// In your draw event Draw your shadow  @ shadowx / shadowy
 
T

TheOakNuggins

Guest
Code:
// Put this in your movement code
shadowx = x;
shadowy = y;
while (!place_meeting(shadowx,shadowy)) {
  shadowy++;
}

// In your draw event Draw your shadow  @ shadowx / shadowy
Okay cool. That worked with keeping it on the ground. How can I prevent the picture in the post with the ledge. Also how does the shadowy++ work? What does that ++ do?
 

bbbower

Member
Okay cool. That worked with keeping it on the ground. How can I prevent the picture in the post with the ledge. Also how does the shadowy++ work? What does that ++ do?
++ just adds one to it. So it basically checks one pixel at a time below the player until it hits the solid object then stops the while loop. T
hinking about that a little further you should change it to
while (!place_meeting(shadowx,shadowy,oSolid) && shadowy < room_height) {
So if you jump over a ledge with no bottom it doesn't lose its mind in an infinite loop.
As far as the hanging over and tilting part thats where the shader would likely come into play as a better option. Unless someone has a genius design already that would probably be a coding nightmare.
Edit I changed the || to && and > to < whoops

If you look at most platformers with that style they either don't have shadows or their shadows are not much bigger than the base of the character to hide that drag over.
 
Last edited:

CMAllen

Member
To clip your shadows so they don't draw into your background space, you could look up dynamic masks. YoYo's dev journal

As far as tilting goes, however, that's a little more complicated. You could simplify if your slopes fall into specific angles. Instead of adjusting the way the sprite draws, use different sprites for different ground slopes. That's probably the simplest solution, but it's also easy to implement, since the slope below the player (and thus the sprite angle to draw) should be readily available.
 
T

TheOakNuggins

Guest
To clip your shadows so they don't draw into your background space, you could look up dynamic masks. YoYo's dev journal

As far as tilting goes, however, that's a little more complicated. You could simplify if your slopes fall into specific angles. Instead of adjusting the way the sprite draws, use different sprites for different ground slopes. That's probably the simplest solution, but it's also easy to implement, since the slope below the player (and thus the sprite angle to draw) should be readily available.
With angling I was hoping that the sprite would change with the angle. So if you were standing half the flat and half on the slope, the shadow would represent that by being in a sort of obtuse L shape.

I'm taking a look at that Dynamic rendering now. :D
 

CMAllen

Member
With angling I was hoping that the sprite would change with the angle. So if you were standing half the flat and half on the slope, the shadow would represent that by being in a sort of obtuse L shape.

I'm taking a look at that Dynamic rendering now. :D
If you split your shadow into slices, you can draw it that way, applying a progressive vertical shift to each slice based on slope. That's about the only method I can think of that isn't going to delve into vertex batches or really complex shaders.
 
T

TheOakNuggins

Guest
If you split your shadow into slices, you can draw it that way, applying a progressive vertical shift to each slice based on slope. That's about the only method I can think of that isn't going to delve into vertex batches or really complex shaders.
I figured out the dynamic rendering... Currently in the process of applying the tool to objects. Is there a good way to get the width and height of an object?

specifically of multiple instances.
 
Last edited by a moderator:

CMAllen

Member
I figured out the dynamic rendering... Currently in the process of applying the tool to objects. Is there a good way to get the width and height of an object?

specifically of multiple instances.
sprite_width and sprite_height are the read-only values, in pixels, of the an object's assigned sprite, including transforms applied via image_xscale and image_yscale. The functions sprite_get_width() and sprite_get_height() will return the width/height of the sprite passed in. Note that in both cases they will return the width/height of the full sprite, including transparent pixels, regardless of how GMS packs and trims them on a texture page, so if you have extra padding for frame alignment reasons, those will be included in the return values.
 
T

TheOakNuggins

Guest
sprite_width and sprite_height are the read-only values, in pixels, of the an object's assigned sprite, including transforms applied via image_xscale and image_yscale. The functions sprite_get_width() and sprite_get_height() will return the width/height of the sprite passed in. Note that in both cases they will return the width/height of the full sprite, including transparent pixels, regardless of how GMS packs and trims them on a texture page, so if you have extra padding for frame alignment reasons, those will be included in the return values.
I just figured it out right before you sent that lol. Now I just have to work on having this be semi transparent... I don't understand how to set the alpha down to half because when I change the alpha values it doesn't do anything.
 
Last edited by a moderator:
T

TheOakNuggins

Guest
sprite_width and sprite_height are the read-only values, in pixels, of the an object's assigned sprite, including transforms applied via image_xscale and image_yscale. The functions sprite_get_width() and sprite_get_height() will return the width/height of the sprite passed in. Note that in both cases they will return the width/height of the full sprite, including transparent pixels, regardless of how GMS packs and trims them on a texture page, so if you have extra padding for frame alignment reasons, those will be included in the return values.
Do you know how I could change the opacity of a drawn sprite with blend modes? From the link you sent me? I got that working, I just can't figure out how to change the alpha and have it draw to the alpha layer at the same time.
 

CMAllen

Member
Parts of a sprite or the whole sprite? Parts are a no-go. You can't change parts of a sprite like that at run-time. At least not in the normal way. You can draw it to a surface, drop the surface into a buffer, manually edit the alpha values of the pixels you want to change, then convert he buffer back into a surface. It won't be exactly fast, but if it's not something you need to do every step, it should be fine.

If it's the alpha value of the entire sprite, well, that's just a simple draw_set_alpha() call away (definitely for 1.4, but I don't recall if the function name was changed with GMS2).
 
D

drowned

Guest
Parts of a sprite or the whole sprite? Parts are a no-go.
That's not entirely true, if we're talking about parts being fully transparent and others being fully opaque. How about draw_sprite_part? You know the size of the shadow sprite, the position of the character relative to the ground, and you can check the ground object to see if there's nothing adjacent to it and at which point you should cut the shadow sprite.
 
Last edited by a moderator:
T

TheOakNuggins

Guest
That's not entirely true, if we're talking about parts being fully transparent and others being fully opaque. How about draw_sprite_part? You know the size of the shadow sprite, the position of the character relative to the ground, and you can check the ground object to see if there's nothing adjacent to it and at which point you should cut the shadow sprite.
Parts of a sprite or the whole sprite? Parts are a no-go. You can't change parts of a sprite like that at run-time. At least not in the normal way. You can draw it to a surface, drop the surface into a buffer, manually edit the alpha values of the pixels you want to change, then convert he buffer back into a surface. It won't be exactly fast, but if it's not something you need to do every step, it should be fine.

If it's the alpha value of the entire sprite, well, that's just a simple draw_set_alpha() call away (definitely for 1.4, but I don't recall if the function name was changed with GMS2).

Here's my current code for the shadow clipping mask. I did look into drawing this onto a surface, but the problem for me was that the surface was always in the top left corner of the room. You have to move the coordinates of the desired object to that corner and I just couldn't figure out how to work the numbers.
Code:
draw_sprite_stretched(sSolid, 0, self.x, self.y, solidxScale, solidyScale);    //draws sprite going to mask

gpu_set_blendenable(false)          
gpu_set_colorwriteenable(false,false,false,true);    //turns colors off

draw_set_alpha(0);
draw_rectangle(0,0, room_width,room_height, false);    //draw a large rectangle to be drawn on
 
draw_set_alpha(1);    //draw masking alpha
draw_sprite_stretched(sSolid, -1, self.x, self.y, solidxScale, solidyScale);

gpu_set_blendenable(true);
gpu_set_colorwriteenable(true,true,true,true); //colors back on

gpu_set_blendmode_ext(bm_dest_alpha,bm_inv_dest_alpha);    //bm_inv_dest_alpha is what masks it down.
gpu_set_alphatestenable(true);

draw_sprite_ext(sPlayer,0, mouse_x,mouse_y, 1, 1, 0, c_white, .2);    //draws covering mask
gpu_set_alphatestenable(false);
gpu_set_blendmode(bm_normal);
 
1Q.a) How should I go about creating a system that will draw the shadow on whatever object is underneath the player. This way you can jump and still see the shadow underneath you (like in Mario64)
I'd handle it like this.
Shoot a ray at the ground using 'collision_line'. This will return an instance id. You can then draw the shadow on this instance, rather than below the player, and it lets you use 'point_distance', which you can then use to scale the shadow the further away the player is.

It's a lot quicker than checking for collisions every step with a while loop, since you only need to call the functions once.

Edit:
I love the art style btw, that looks like an excellent project.
 
Last edited:

hogwater

Member
Collision_line is how I did that part (actually collision_line_first(), which is a script on gmlscripts.com). I've been lazy and haven't done the masking part yet, so hopefully you'll figure that part out and I can crib.

Code:
//control shadow size
if (on_ground)
{
shadow_scale = 1;   
}
else if (vspd < 0) 
{
shadow_scale -= .028;
}
else if (vspd > 0) && (shadow_scale < 1)
{
shadow_scale += .028;   
}

if (shadow_scale > 1)
{
shadow_scale = 1;   
}

//draw_shadow
var ground_close_enough = scr_collision_line_first(x,y,x,y+500,obj_all_ground_parent,false,true) 

if (image_xscale == 1) && (ground_close_enough) 
{
y_start = ground_close_enough.bbox_top;
draw_sprite_ext(spr_harry_shadow,0,x-25,y_start+12,shadow_scale,shadow_scale,0,c_dkgray,0.8);
}
else if (ground_close_enough) 
{
y_start = ground_close_enough.bbox_top;
draw_sprite_ext(spr_harry_shadow,0,x+25,y_start+12,shadow_scale,shadow_scale,0,c_dkgray,0.8);
}
This is a first draft, needs a bit of cleanup. I still need to add a check so it doesn't prematurely jump down when you're still on a platform/ground.
 
T

TheOakNuggins

Guest
Code:
///oPlayer Object Draw Event
while (!place_meeting(shadowx, shadowy, oSolid)) and  (shadowy < room_height)  {
  shadowy++;
}

shadowxScale = 1 + (1 / abs((oPlayer.y) - (shadowy)));

if  shadowxScale < 2 {
    draw_shadowxScale = lerp(draw_shadowxScale, shadowxScale, .2);
    } else {
        draw_shadowxScale = lerp(draw_shadowxScale, shadowxScale, .5);
}

surfShadowx = oPlayer.shadowx;
surfShadowy = round(oPlayer.shadowy);
surfShadowxScale = oPlayer.draw_shadowxScale/1;


draw_self();
Code:
///oShadow Object Draw Event

//Shadow Perspective Scaling
gpu_set_fog(true, c_black, 0, 1);

with(oParent) {

if (!surface_exists(shadowSurf)) {        //if there's no surface, create one
    shadowSurf = surface_create(sprite_width, sprite_height);
}

draw_set_alpha(.5);
draw_rectangle(0, 0, sprite_width, sprite_height, false);
draw_set_alpha(1);


surface_set_target(shadowSurf);
draw_clear_alpha(c_black, 0);

draw_sprite_ext(spr_shadow, -1, oPlayer.surfShadowx - x, oPlayer.surfShadowy + 2 - y, oPlayer.surfShadowxScale , 1, 0, 0, .8);
surface_reset_target();

draw_set_alpha(.5);
draw_surface(shadowSurf, x, y - 2);
draw_set_alpha(1);

}

gpu_set_fog(false, c_white, 0, 0);
I've figured it out!! Here's the code ^
 

Attachments

D

drowned

Guest
...or you could have just specified the alpha using draw_sprite_part_ext and solved both problems at once, with a lot less code. Right?
 
Y

yaragad

Guest
Nice game looking and nice shadowing.

BTW, Im not able to know what all variables mean and how to set them. Do you guys have a complete example on this? Do you know any tutorial or something?

Thanks!!
 
Top