Best way to create a 2D platformer shadow under character?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By p7f

Hi,
I was wondering about doing shadows under character in 2d Platformes. It can be just a gray circle below it, but the problem is that it must always be on floor, even when character is on air. I tried raycast down and set shadow in collision point, but this gives a odd efect when near a edge of a platfor (half of the shadows stays on air for example).
What would be the best way to implement this?

I know this has been asked already here a year ago, but there was no answer then, perhaps someone knows now.

:bust_in_silhouette: Reply From: rustyStriker

not sure if the best way but you can have a raycast2d casting downwards(while being really long) and placing a shadow sprite in the collision point where it collides with the ground(you only need the raycast2d to be long enough to leave the screen so if there is no visible ground(or ground at all) the player wont see it due to the viewport not being big enough)

hi, thanks for the answer… but that is exactly what i’ve said in my question i’m using right now. The problem is that if the character is near a cliff, part of the shadow stays on air, cause raycast hits the ground in the edge. That’s why i asked for better way. Do you know how can i solve this?

p7f | 2018-12-21 15:25

Multiple raycasts?

SIsilicon | 2018-12-21 23:09

I thought that, but should i also make my shadow in multiple parts then, so each part of the shadow shows in the place it should. I thought maybe there was a better way. Could it be some way to get the intersection area of 2 areas? that could also solve my problem.

p7f | 2018-12-21 23:46

i think there is a way, well you can also make 3 raycasts that are close to each other in order to detect edges(like the idea above)… but you can take the shape of the collider beneath and with a lot of ugly coding you can get the edges and use that

rustyStriker | 2018-12-22 00:33

Thanks for the help, i tried your advice but it was too complex for me. However, i found an easier way that meets my requirements, and i post is as answer. Anyway, i’ll upvote your answer thanking your help.

p7f | 2018-12-26 12:35

:bust_in_silhouette: Reply From: p7f

Hi,
I found a solution myself. For the record, i don’t know if i was enough clear with my question, but the problem was not to set the shadow on the floor (that was easy with raycasts). The problem was that on an edge of the terrain, part of the shadow stayed on air. I wanted to show just the part that was on terrain. So what i did is to use Light2D instead of Sprite. I set mode to mix and item_cull_mask to bit 2. Then, i set the same bit to light mask on terrain, but not on the rest of the elements. Then, when standing near a cliff, instead of showing all the shadow, and part of it on air, it just shows the part that is on the terrain. This is a code snippet wich includes energy and size scaling according to distance:

func _process(delta):
 $ShadowRayCastRight.position.x = $ShadowLight.texture.get_width()/2
 $ShadowRayCastLeft.position.x = -$ShadowLight.texture.get_width()/2
 $ShadowLight.global_position.y = min($ShadowRayCastRight.get_collision_point().y, $ShadowRayCastLeft.get_collision_point().y)
 $ShadowLight.energy = exp(-$ShadowLight.position.y*0.01)
 $ShadowLight.texture_scale = shadow_scale*exp(-$ShadowLight.position.y*0.005)
1 Like

Many Years later. This helped me. Works Great and fast! Thank you for posting your script!

Small Adjustment. Justs hide it when Raycast don’t Hit anything

extends Node2D

@export var shadow_scale = 1

func _process(delta):
	$ShadowRayCastRight.position.x = $ShadowLight.texture.get_width()/2
	$ShadowRayCastLeft.position.x = -$ShadowLight.texture.get_width()/2
	if !$ShadowRayCastRight.is_colliding() && !$ShadowRayCastLeft.is_colliding():
		$ShadowLight.energy=0 #Just Hide it when not hitting anything

	else:
		$ShadowLight.global_position.y = min($ShadowRayCastRight.get_collision_point().y, $ShadowRayCastLeft.get_collision_point().y)
		$ShadowLight.energy = exp(-$ShadowLight.position.y*0.005)	

	$ShadowLight.texture_scale = shadow_scale*exp(-$ShadowLight.position.y*0.005)