It’s been more than a whole year I posted here about the shadow support for tilemap. A Whole year! And Still ! no support for the shadow tilemap!
If anyone else is like me and is doing some 2D stuff with tilemaps and just wants a convenient way to get shadows going with this experimental system, without modifying the package scripts, I’ve adapted the approach from this thread & another into a component that’ll work with an arbitrary input list of CompositeCollider2Ds and create a set of children ShadowCaster2Ds that you can use with a sister CompositeShadowCaster2D script on the parent to conveniently manage a scene with stuff like tilemap colliders.
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Experimental.Rendering.Universal;
/**
* Usage: Make an empty game object and attach this component script to it. Drag & drop CompositeCollider2D components
* into the sourceColliders field.
*
* This script isn't ideal, but it's simple and an okay jumping off point for more advanced changes (like caching).
*
* The main caveat is that it'll regenerate the child objects that comprise the shadow group every time you enter
* edit mode. For larger scenes this might be slow or annoying. It's a rough sketch I'm using for a jam game so I'm
* not too concerned about this, future travelers may way to dig into more optimal ways of not having to recreate the
* child object shadow casters.
*/
[ExecuteInEditMode]
[RequireComponent(typeof(CompositeShadowCaster2D))]
[DisallowMultipleComponent]
public class ShadowWrangler : MonoBehaviour
{
public CompositeCollider2D[] sourceColliders = { };
private static BindingFlags accessFlagsPrivate =
BindingFlags.NonPublic | BindingFlags.Instance;
private static FieldInfo shapePathField =
typeof(ShadowCaster2D).GetField("m_ShapePath", accessFlagsPrivate);
private static FieldInfo meshHashField =
typeof(ShadowCaster2D).GetField("m_ShapePathHash", accessFlagsPrivate);
// Start is called before the first frame update
void OnEnable()
{
// only refresh this stuff in edit mode since the ShadowCaster components will serialize/persist their
// shadow mesh on their own.
// TODO: maybe only trigger the refresh when a difference is detected
if (Application.isEditor) RefreshWorldShadows();
}
private void RefreshWorldShadows()
{
ClearChildShadows();
foreach (var source in sourceColliders)
{
CreateGroupsForCollider(source);
}
}
private void ClearChildShadows()
{
var doomed = new List<Transform>();
for (int i = 0; i < transform.childCount; i++)
{
var child = transform.GetChild(i);
if (child.GetComponent<ShadowCaster2D>() == null || !child.name.StartsWith("_caster_")) continue;
doomed.Add(transform.GetChild(i));
}
foreach (var child in doomed)
{
DestroyImmediate(child.gameObject);
}
}
/**
* This is adapted from strategies in these two discussion threads:
* - https://discussions.unity.com/t/769178
* - https://discussions.unity.com/t/776893
* Hopefully a day comes where we're allowed to programmatically mutate the shadow caster shape
* programmatically.
*/
private void CreateGroupsForCollider(CompositeCollider2D source)
{
for (int i = 0; i < source.pathCount; i++)
{
// get the path data
Vector2[] pathVertices = new Vector2[source.GetPathPointCount(i)];
source.GetPath(i, pathVertices);
Vector3[] finalVerts = Array.ConvertAll<Vector2, Vector3>(pathVertices, input => input);
// make a new child
var shadowCaster = new GameObject("_caster_" + i + "_" + source.transform.name);
shadowCaster.transform.parent = transform;
// create & prime the shadow caster
var shadow = shadowCaster.AddComponent<ShadowCaster2D>();
shadow.selfShadows = true;
shapePathField.SetValue(shadow, finalVerts);
// invalidate the hash so it re-generates the shadow mesh on the next Update()
meshHashField.SetValue(shadow, -1);
}
}
}
Example scene hierarchy (inputted from a single tilemap’s CompositeCollider2D):
Example of the shadow meshes in the scene (the white outline around the main tilemaps):
Cross post from here .