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

Allow programmatic manipulation for ShadowCaster2D shape

Discussion in '2D Experimental Preview' started by ilyaryzhenkov, Feb 16, 2020.

  1. ilyaryzhenkov

    ilyaryzhenkov

    Joined:
    Mar 4, 2017
    Posts:
    6
    ShadowCaster2D shadow shape is not programmatically accessible, which severely limits what can one do with shadows in 2D. Please make a public "Shape" property, which will modify internal shape variable and reset the hash.

    https://github.com/Unity-Technologi...niversal/Runtime/2D/ShadowCaster2D.cs#L32-L33

    What I see can be done with this:
    * transfer shapes from one point in program to another. E.g. in my project I have single game object with sprite being switched, and thus shapes should be switched too. I can encode shapes in my ScriptableObject (game database), but I can't set the shape programmatically on a shadow caster.
    * automatic shape generation from the sprite (by tracing edges)
    * animating shapes programmatically to produce various light effects
    * use PCG to generate objects and their shapes

    It would also be cool to expose not a single path, but list of paths as a shadow outline to generate shadows for tilemap mazes and such, but that is slightly more complex story, I suppose.
     
    LushkinR and KaneFreeman like this.
  2. KaneFreeman

    KaneFreeman

    Joined:
    Apr 3, 2015
    Posts:
    1
    I would love to see this. I am looking to use this lighting system in a 2D Voxel game where the shape is constantly changing and cannot be preset in the editor.

    It would also be good to expose the Target Sorting Layer field. This would help when dynamically adding a ShadowCaster2D to a gameobject without having to use a prefab.

    The repo moved apparently. Link is now: https://github.com/Unity-Technologi...elines.universal/Runtime/2D/ShadowCaster2D.cs
     
  3. the_Simian

    the_Simian

    Joined:
    Mar 13, 2011
    Posts:
    37
    I came across a somewhat backhanded way to do this, and it kills your performance if you try to do it once the game is running, but I was able to use
    System.Reflection


    Code (CSharp):
    1. private ShadowCaster2D shadowCaster;
    2. private static BindingFlags accessFlagsPrivate =
    3.   BindingFlags.NonPublic | BindingFlags.Instance;
    4. private static FieldInfo meshField =
    5.   typeof(ShadowCaster2D).GetField("m_Mesh", accessFlagsPrivate);
    6. private static FieldInfo shapePathField =
    7.   typeof(ShadowCaster2D).GetField("m_ShapePath", accessFlagsPrivate);
    8. private static MethodInfo onEnableMethod =
    9.   typeof(ShadowCaster2D).GetMethod("OnEnable", accessFlagsPrivate);
    10. void Start()
    11. {
    12.   modifyShadowShape();
    13. }
    14. void modifyShadowShape()
    15. {
    16.   shadowCaster =
    17.   GetComponent<UnityEngine.Experimental.Rendering.Universal.ShadowCaster2D>();
    18.   var testPath = new Vector3[]{
    19.     new Vector3(0,0,0),
    20.     new Vector3(0,1,0),
    21.     new Vector3(1,1,0),
    22.   };
    23.   shapePathField.SetValue(shadowCaster, testPath);
    24.   meshField.SetValue(shadowCaster, null);
    25.   onEnableMethod.Invoke(shadowCaster, new object[0]);
    26. }
    I'dl like to be able to modify the shadows as well, I was thinking I could preprocess the sprites to get polygons or something
     
    AzureMasters likes this.
  4. 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 hierarchy view of the results:


    Example scene view (white outlines are the shadow mesh/path):


    I hope this can be of use to other people googling for this kind of thing, and maybe Unity will consider formalizing an approach to editing the path programmatically in a way that carefully acknowledge it's expensiveness (maybe we can only edit it in edit mode or something?)
     
    EzraHuffman and AzureMasters like this.
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    Please do not cross-post as per you other post here.

    Thanks.
     
  6. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    please implement official solution haha. i realy see no harm in the crosspost since both threats discuss this topic and its relevant to both.
     
  7. PostleService

    PostleService

    Joined:
    Feb 28, 2021
    Posts:
    3
    Watch out, man. They'll call You out for necroposting XD
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    It wasn't a necropost. Yours was however. :(

    If you want to participate in these forums, please make sure to read the Community Code of Conduct. In your case, section 1i.

    Thank you.
     
    nanodeath and MousePods like this.