Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Shadow Caster 2D not working on TileMap?

Discussion in '2D Experimental Preview' started by ayockel90, Dec 14, 2019.

  1. ayockel90

    ayockel90

    Joined:
    Jan 27, 2019
    Posts:
    17
    I have Shadow Caster 2D working on regular objects (doors in my game) but it doesn't seem to work on my TileMap walls.

    https://imgur.com/a/n7OvxAm

    Is this a known issue or am I doing something wrong?
     
  2. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    The current ShadowCaster2D component appears to only take the bounds of a collider and/or object. You can modify the Awake() function in ShadowCaster2D.cs to check if the type of collider is a polygon, then use the path of the polygon as the shape for the generated shadow mesh. The file was located at "Library\PackageCache\com.unity.render-pipelines.universal@7.1.7\Runtime\2D" for me.
    Code (CSharp):
    1. private void Awake() {
    2.             if(m_ApplyToSortingLayers == null)
    3.                 m_ApplyToSortingLayers = SetDefaultSortingLayers();
    4.  
    5.             Bounds bounds = new Bounds(transform.position, Vector3.one);
    6.          
    7.             Renderer renderer = GetComponent<Renderer>();
    8.             if (renderer != null)
    9.             {
    10.                 bounds = renderer.bounds;
    11.             }
    12.             else
    13.             {
    14.                 Collider2D collider = GetComponent<Collider2D>();
    15.                 if (collider != null)
    16.                     if (collider.GetType() == typeof(PolygonCollider2D)) {
    17.                         m_ShapePath = Array.ConvertAll<Vector2, Vector3>(((PolygonCollider2D)collider).GetPath(0), vec2To3);
    18.                         m_UseRendererSilhouette = false;
    19.                     } else {
    20.                         bounds = collider.bounds;
    21.                     }
    22.             }
    23.  
    24.             Vector3 relOffset = bounds.center - transform.position;
    25.  
    26.             if (m_ShapePath == null || m_ShapePath.Length == 0)
    27.             {
    28.                 m_ShapePath = new Vector3[]
    29.                 {
    30.                     relOffset + new Vector3(-bounds.extents.x, -bounds.extents.y),
    31.                     relOffset + new Vector3(bounds.extents.x, -bounds.extents.y),
    32.                     relOffset + new Vector3(bounds.extents.x, bounds.extents.y),
    33.                     relOffset + new Vector3(-bounds.extents.x, bounds.extents.y)
    34.                 };
    35.             }
    36. }
    I then use a different script that requires a CompositeCollider2D to be attached to my tilemap and builds a ShadowCaster2D for each distinct path in the composite collider, which looks like the following:
    Code (CSharp):
    1.  
    2. public void Start() {
    3.             tilemapCollider = GetComponent<CompositeCollider2D>();
    4.             GameObject shadowCasterContainer = GameObject.Find("shadow_casters");
    5.             for (int i = 0; i < tilemapCollider.pathCount; i++) {
    6.             Vector2[] pathVertices = new Vector2[tilemapCollider.GetPathPointCount(i)];
    7.             tilemapCollider.GetPath(i, pathVertices);
    8.             GameObject shadowCaster = new GameObject("shadow_caster_" + i);
    9.             PolygonCollider2D shadowPolygon = (PolygonCollider2D)shadowCaster.AddComponent(typeof(PolygonCollider2D));
    10.             shadowCaster.transform.parent = shadowCasterContainer.transform;
    11.             shadowPolygon.points = pathVertices;
    12.             shadowPolygon.enabled = false;
    13.             ShadowCaster2D shadowCasterComponent = shadowCaster.AddComponent<ShadowCaster2D>();
    14.             shadowCasterComponent.selfShadows = true;
    15.             }
    16. }
    Editing ShadowCaster2D should be a somewhat temporary fix until Unity alter the implementation, as in my case it can be refetched and your changes will be lost (unless you fork the ScriptableRenderPipeline repository and point the package manager to use your changes) - so I'd recommend saving the modified ShadowCaster2D script somewhere as a backup to copy and paste if it does the same for you.
     
    Last edited: Dec 29, 2019
  3. ayockel90

    ayockel90

    Joined:
    Jan 27, 2019
    Posts:
    17


    Thanks, I've tried your fix but I receive the following error after modifying ShadowCaster2D.cs

    Library\PackageCache\com.unity.render-pipelines.universal@7.1.5\Runtime\2D\ShadowCaster2D.cs(102,116): error CS0103: The name 'vec2To3' does not exist in the current context
     
  4. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    Ah I forgot to add this method in my original snippet as-well, add this method in and it should hopefully work:
    Code (CSharp):
    1. private Vector3 vec2To3(Vector2 inputVector) {
    2.     return new Vector3(inputVector.x, inputVector.y, 0);
    3. }
     
    ToastandBananas likes this.
  5. ayockel90

    ayockel90

    Joined:
    Jan 27, 2019
    Posts:
    17
    That fixed the compiler error but sadly the light is still going through my walls.

    At least I know it's a limitation of the component. Sounds like it will be better to wait for them to implement Tilemap shadows.

    Thanks for your help anyway.
     
  6. japhib

    japhib

    Joined:
    May 26, 2017
    Posts:
    65
    @thomasedw Your solution works great! @ayockel90 just make sure that you have a TilemapCollider2D on the tilemap, with the "Used By Composite" checkbox checked, in addition to having a CompositeCollider2D component on there.
     
    thomasedw likes this.
  7. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    CherryFake likes this.
  8. ToastandBananas

    ToastandBananas

    Joined:
    Jun 8, 2018
    Posts:
    8
    @thomasedw you're amazing! Your fix works perfectly!
     
    thomasedw likes this.
  9. ToastandBananas

    ToastandBananas

    Joined:
    Jun 8, 2018
    Posts:
    8
    @thomasedw Well I just discovered that when you close down and reopen Unity, that the ShadowCaster2D script reverts back to it's original form, so anything I added isn't there anymore. Do you know of a way to prevent this?
     
  10. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    You need to fork this GitHub repository https://github.com/Unity-Technologies/ScriptableRenderPipeline, make changes in that and then you link to your forked version in the package manager by clicking the "+" icon and choosing "Add package from git URL..."
     
    ToastandBananas likes this.
  11. ToastandBananas

    ToastandBananas

    Joined:
    Jun 8, 2018
    Posts:
    8
  12. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Perhaps change the name of the package... like CustomScriptableRenderPipeline
     
  13. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    It's been a while since I first added it - but @dotaxis posted on this here: https://forum.unity.com/threads/the-new-2d-lighting-on-tilemaps-advice-and-suggestions.810927/, they altered their manifest.json file directly (incase using the package manager doesn't work).
     
    ToastandBananas likes this.
  14. ToastandBananas

    ToastandBananas

    Joined:
    Jun 8, 2018
    Posts:
    8
    @thomasedw Ahhhh beautiful, this worked perfectly...thanks again, you're awesome!
     
    thomasedw likes this.
  15. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
  16. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    I replied to this here:
    https://forum.unity.com/threads/the...s-advice-and-suggestions.810927/#post-5436126
     
  17. alevillalba

    alevillalba

    Joined:
    Jun 29, 2017
    Posts:
    7
    Is there any plan by Unity dev team to implement a built-in solution for this?
     
    darthdeus and polerin like this.
  18. castor76

    castor76

    Joined:
    Dec 5, 2011
    Posts:
    2,517
    This should really be officially supported by Unity....
     
  19. epord

    epord

    Joined:
    Dec 15, 2018
    Posts:
    1
  20. Ratt

    Ratt

    Joined:
    Jul 21, 2014
    Posts:
    7
    If you would like to avoid having to mess around with packages and forks, you could use reflection instead. I've done:

    Code (CSharp):
    1.     private static FieldInfo sShapeField = typeof(ShadowCaster2D).GetField("m_ShapePath", BindingFlags.Instance | BindingFlags.NonPublic);
    2.  
    3.     private static void SetShapePath(ShadowCaster2D caster, Vector2[] points)
    4.     {
    5.         Vector3[] shapePath = Array.ConvertAll(points, vec2To3);
    6.         sShapeField.SetValue(caster, shapePath);
    7.     }
    8.  
    9.     private static Vector3 vec2To3(Vector2 inputVector)
    10.     {
    11.         return new Vector3(inputVector.x, inputVector.y, 0);
    12.     }
     
  21. castor76

    castor76

    Joined:
    Dec 5, 2011
    Posts:
    2,517
    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!
     
  22. midoski

    midoski

    Joined:
    Apr 26, 2015
    Posts:
    6
    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.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Reflection;
    4. using UnityEngine;
    5. using UnityEngine.Experimental.Rendering.Universal;
    6.  
    7. /**
    8. * Usage: Make an empty game object and attach this component script to it. Drag & drop CompositeCollider2D components
    9. * into the sourceColliders field.
    10. *
    11. * This script isn't ideal, but it's simple and an okay jumping off point for more advanced changes (like caching).
    12. *
    13. * The main caveat is that it'll regenerate the child objects that comprise the shadow group every time you enter
    14. * 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
    15. * not too concerned about this, future travelers may way to dig into more optimal ways of not having to recreate the
    16. * child object shadow casters.
    17. */
    18. [ExecuteInEditMode]
    19. [RequireComponent(typeof(CompositeShadowCaster2D))]
    20. [DisallowMultipleComponent]
    21. public class ShadowWrangler : MonoBehaviour
    22. {
    23.     public CompositeCollider2D[] sourceColliders = { };
    24.  
    25.     private static BindingFlags accessFlagsPrivate =
    26.         BindingFlags.NonPublic | BindingFlags.Instance;
    27.  
    28.     private static FieldInfo shapePathField =
    29.         typeof(ShadowCaster2D).GetField("m_ShapePath", accessFlagsPrivate);
    30.  
    31.     private static FieldInfo meshHashField =
    32.         typeof(ShadowCaster2D).GetField("m_ShapePathHash", accessFlagsPrivate);
    33.  
    34.  
    35.     // Start is called before the first frame update
    36.     void OnEnable()
    37.     {
    38.         // only refresh this stuff in edit mode since the ShadowCaster components will serialize/persist their
    39.         // shadow mesh on their own.
    40.         // TODO: maybe only trigger the refresh when a difference is detected
    41.         if (Application.isEditor) RefreshWorldShadows();
    42.     }
    43.  
    44.     private void RefreshWorldShadows()
    45.     {
    46.         ClearChildShadows();
    47.         foreach (var source in sourceColliders)
    48.         {
    49.             CreateGroupsForCollider(source);
    50.         }
    51.     }
    52.  
    53.     private void ClearChildShadows()
    54.     {
    55.         var doomed = new List<Transform>();
    56.         for (int i = 0; i < transform.childCount; i++)
    57.         {
    58.             var child = transform.GetChild(i);
    59.             if (child.GetComponent<ShadowCaster2D>() == null || !child.name.StartsWith("_caster_")) continue;
    60.             doomed.Add(transform.GetChild(i));
    61.         }
    62.  
    63.         foreach (var child in doomed)
    64.         {
    65.             DestroyImmediate(child.gameObject);
    66.         }
    67.     }
    68.  
    69.     /**
    70.      * This is adapted from strategies in these two discussion threads:
    71.      * - https://forum.unity.com/threads/shadow-caster-2d-not-working-on-tilemap.793803/
    72.      * - https://forum.unity.com/threads/allow-programmatic-manipulation-for-shadowcaster2d-shape.829626/
    73.      * Hopefully a day comes where we're allowed to programmatically mutate the shadow caster shape
    74.      * programmatically.
    75.      */
    76.     private void CreateGroupsForCollider(CompositeCollider2D source)
    77.     {
    78.         for (int i = 0; i < source.pathCount; i++)
    79.         {
    80.             // get the path data
    81.             Vector2[] pathVertices = new Vector2[source.GetPathPointCount(i)];
    82.             source.GetPath(i, pathVertices);
    83.             Vector3[] finalVerts = Array.ConvertAll<Vector2, Vector3>(pathVertices, input => input);
    84.  
    85.             // make a new child
    86.             var shadowCaster = new GameObject("_caster_" + i + "_" + source.transform.name);
    87.             shadowCaster.transform.parent = transform;
    88.  
    89.             // create & prime the shadow caster
    90.             var shadow = shadowCaster.AddComponent<ShadowCaster2D>();
    91.             shadow.selfShadows = true;
    92.             shapePathField.SetValue(shadow, finalVerts);
    93.             // invalidate the hash so it re-generates the shadow mesh on the next Update()
    94.             meshHashField.SetValue(shadow, -1);
    95.         }
    96.     }
    97. }
    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):
     
  23. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,455
    Cross post from here.