Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Bug Unable to set "_DrawOrder" of URP shader graph decal material from script during runtime

Discussion in 'Scripting' started by collinpatrick15, Sep 7, 2023.

  1. collinpatrick15

    collinpatrick15

    Joined:
    Nov 7, 2018
    Posts:
    35
    As the title says, I can't seem to consistently set the _DrawOrder variable of a URP shader graph decal material during runtime.

    I am trying to use decals to display grid selection overlays on the ground. Each selection uses its own decal projector and they all share the same material that gets cloned during runtime. I need to be able to manually set the rendering priority of the material to make the decals layer in the correct order. I am able to access and set all the custom variables in the shader, but can't seem to set the "Priority" variable under the advanced shader options. From my testing, editing the variable in edit mode, the priority slider is linked to the _DrawOrder variable when looking at the raw material data in a text editor. I've been trying to set this value but it just does not seem to work consistently. In some cases, it works, in others it does not. If I manually edit the priority during runtime from the inspector, it works correctly. Am I missing something? I feel like I'm doing something obviously wrong here, but am overlooking it.

    The material:
    upload_2023-9-6_17-48-22.png

    The code
    upload_2023-9-6_17-50-21.png

    What it looks like:
    upload_2023-9-6_17-54-31.png

    What it should look like:
    upload_2023-9-6_17-55-4.png

    When I change the draw order from the object pool projectors, it seems to work, but the projectors assigned to
    _allEnemyUnitDangerAreas
    and
    _selectedEnemyUnitDangerAreas
    seem to always remain the default value no matter what.
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Rendering.Universal;
    5.  
    6. public class ObjectPoolDictionary<T1, T2> where T2 : UnityEngine.Object {
    7.  
    8.     private Dictionary<T1, T2> _active = new Dictionary<T1, T2>();
    9.     private Queue<T2> _pool = new Queue<T2>();
    10.  
    11.     public Dictionary<T1, T2> Active => _active;
    12.  
    13.     public Action<T2> OnInstantiate;
    14.     public Action<T2> OnGet;
    15.     public Action<T2> OnReturn;
    16.  
    17.     private T2 _prefab;
    18.  
    19.     public void Initialize( T2 aPrefab, Action<T2> aOnInstantiate = null, Action<T2> aOnGet = null, Action<T2> aOnReturn = null ) {
    20.         _prefab = aPrefab;
    21.         OnInstantiate = aOnInstantiate;
    22.         OnGet = aOnGet;
    23.         OnReturn = aOnReturn;
    24.     }
    25.  
    26.     public T2 GetObject( T1 aKey ) {
    27.  
    28.         T2 lObj = null;
    29.  
    30.         if ( _pool.Count > 0 ) {
    31.             lObj = _pool.Dequeue();
    32.         }
    33.         else {
    34.             lObj = GameObject.Instantiate( _prefab );
    35.             OnInstantiate?.Invoke( lObj );
    36.         }
    37.  
    38.         _active.Add( aKey, lObj );
    39.         OnGet?.Invoke( lObj );
    40.         return lObj;
    41.     }
    42.  
    43.     public void ReturnObject( T1 lKey ) {
    44.         if( _active.TryGetValue( lKey, out T2 lObj ) ) {
    45.             _active.Remove( lKey );
    46.             OnReturn?.Invoke( lObj );
    47.             _pool.Enqueue( lObj );
    48.         }
    49.     }
    50. }
    51.  
    52. public class TileSelectionVisualizer : MonoBehaviour
    53. {
    54.     [Header( "Asset Dependencies" )]
    55.     [SerializeField] private DecalProjector _visualizerPrefab;
    56.  
    57.     [Header( "Configuration" )]
    58.     [SerializeField] private int _depth = 10;
    59.     [Space]
    60.     [SerializeField] private Color[] _movementRingColors;
    61.     [Space]
    62.     [SerializeField] private Color _targetableColor;
    63.     [Space]
    64.     [SerializeField] private Color[] _playerHitRingColors;
    65.     [SerializeField] private Color[] _enemyHitRingColors;
    66.     [SerializeField] private Color[] _dangerRingColors;
    67.     [SerializeField] private Color[] _assistRingColors;
    68.     [SerializeField] private Color[] _interactRingColors;
    69.     [Space]
    70.     [SerializeField] private Color _selectedEnemyDangerColor;
    71.     [SerializeField] private Color _allEnemyDangerColor;
    72.  
    73.     private ObjectPoolDictionary<string, DecalProjector> _pool = new ObjectPoolDictionary<string, DecalProjector>();
    74.  
    75.     private DecalProjector _selectedEnemyUnitDangerAreas;
    76.     private DecalProjector _allEnemyUnitDangerAreas;
    77.     private HashSet<UnitController> _selectedEnemyUnits = new HashSet<UnitController>();
    78.     private List<GridSelection> _selectedEnemyGridSelections = new List<GridSelection>();
    79.  
    80.     #region DEBUG
    81.     [Header( "Debug" )]
    82.     public UnitController unitToAdd;
    83.     #endregion
    84.  
    85.     private bool _rebuildSelected = false;
    86.     public UnitController[] enemyUnits;
    87.     private bool _rebuildAll = false;
    88.  
    89.     private void Update() {
    90.         if( _rebuildAll ) {
    91.             _rebuildAll = false;
    92.             RebuildAllEnemyDangerAreas( enemyUnits );
    93.         }
    94.        
    95.         if( _rebuildSelected ) {
    96.             _rebuildSelected = false;
    97.             RebuildSelectedUnitDangerArea();
    98.         }
    99.     }
    100.  
    101.     public void Initialize() {
    102.         _pool.Initialize( _visualizerPrefab, CreateProjector, GetProjector, ReturnProjector );
    103.      
    104.         if( _selectedEnemyUnitDangerAreas == null ) {
    105.             _selectedEnemyUnitDangerAreas = GameObject.Instantiate( _visualizerPrefab );
    106.             Material lMaterial = Material.Instantiate( _selectedEnemyUnitDangerAreas.material );
    107.             _selectedEnemyUnitDangerAreas.material = lMaterial;
    108.             _selectedEnemyUnitDangerAreas.transform.parent = this.transform;
    109.             _selectedEnemyUnitDangerAreas.gameObject.name = "Selected Enemy Unit Danger Areas";
    110.         }
    111.  
    112.         if ( _allEnemyUnitDangerAreas == null ) {
    113.             _allEnemyUnitDangerAreas = GameObject.Instantiate( _visualizerPrefab );
    114.             Material lMaterial = Material.Instantiate( _allEnemyUnitDangerAreas.material );
    115.             _allEnemyUnitDangerAreas.material = lMaterial;
    116.             _allEnemyUnitDangerAreas.transform.parent = this.transform;
    117.             _allEnemyUnitDangerAreas.gameObject.name = "All Enemy Unit Danger Areas";
    118.         }
    119.  
    120.         _selectedEnemyUnitDangerAreas.enabled = false;
    121.         _allEnemyUnitDangerAreas.enabled = false;
    122.  
    123.         _selectedEnemyGridSelections.Clear();
    124.         _selectedEnemyUnits.Clear();
    125.     }
    126.  
    127.     public void ShowMovementSelection( UnitController aUnitController ) {
    128.         DecalProjector lVisualizer = null;
    129.         if ( _pool.Active.TryGetValue( aUnitController.UnitData.UnitID, out lVisualizer ) == false ) {
    130.             lVisualizer = _pool.GetObject( aUnitController.UnitData.UnitID );
    131.         }
    132.  
    133.         (Texture2D mainTex, Texture2D subTex) lTextures = GridCalculator.GetMovementGridTexture( aUnitController.UnitData.TotalStats.Movement, aUnitController.UnitData.MaxWeaponRange, aUnitController.MovementSelection, _movementRingColors, _targetableColor, -1 );
    134.         (Vector3Int minExtent, Vector3Int maxExtent) lExtents = GridCalculator.GetSelectionExtents( aUnitController.MovementSelection );
    135.         //int lSize = lTextures.mainTex.width * 2; //( aMoveStat * 2 * 4 ) + ( aWeaponDistance * 4 ) + 2 + 4; // +4 = Padding around selection
    136.         lVisualizer.material.SetTexture( "_Selection_Texture", lTextures.mainTex );
    137.         lVisualizer.material.SetTexture( "_Selection_Subtexture", lTextures.subTex );
    138.         lVisualizer.material.SetFloat( "_Use_Subtexture", 1 );
    139.         lVisualizer.material.SetVector( "_Grid_Size", new Vector4( lTextures.mainTex.width, lTextures.mainTex.height ) );
    140.         lVisualizer.material.SetFloat( "_DrawOrder", 3 );
    141.         lVisualizer.size = new Vector3( lTextures.mainTex.width * GridCalculator.TileSize, lTextures.mainTex.height * GridCalculator.TileSize, _depth );
    142.  
    143.         Vector3 lPosition = Vector3.Lerp( lExtents.minExtent, lExtents.maxExtent, 0.5f ) * GridCalculator.TileSize + GridCalculator.GridData.CenterWorldCoordinate;
    144.  
    145.         lVisualizer.transform.position = lPosition + Vector3.up * ( _depth / 2 );
    146.     }
    147.  
    148.     public void ShowRangeSelection( UnitController aUnitController, bool aMaxRange = false ) {
    149.         DecalProjector lVisualizer = null;
    150.         if ( _pool.Active.TryGetValue( aUnitController.UnitData.UnitID, out lVisualizer ) == false ) {
    151.             lVisualizer = _pool.GetObject( aUnitController.UnitData.UnitID );
    152.         }
    153.  
    154.         Texture2D lTexture = GridCalculator.GetRangeGridTexture( 1, aMaxRange ? aUnitController.UnitData.MaxWeaponRange : aUnitController.UnitData.EquippedWeaponRange, aUnitController.TargetableTilesSelection, _targetableColor, true );
    155.  
    156.         int lSize = Mathf.Max( lTexture.height, lTexture.width ) * 2; //( aMoveStat * 2 * 4 ) + ( aWeaponDistance * 4 ) + 2 + 4; // +4 = Padding around selection
    157.         lVisualizer.material.SetTexture( "_Selection_Texture", lTexture );
    158.         lVisualizer.material.SetTexture( "_Selection_Subtexture", null );
    159.         lVisualizer.material.SetFloat( "_Use_Subtexture", 0 );
    160.         lVisualizer.material.SetVector( "_Grid_Size", new Vector4( lSize / 2, lSize / 2 ) );
    161.         lVisualizer.size = new Vector3( lSize, lSize, _depth );
    162.  
    163.         lVisualizer.transform.position = GridCalculator.ToWorldPosition( aUnitController.TargetableTilesSelection.CenterPosition ) + Vector3.up * ( _depth / 2 );
    164.  
    165.         Debug.Log( "Show Range Visualizer" );
    166.     }
    167.  
    168.     public void ShowRangeSelection( UnitController aUnitController, GridSelection aGridSelection ) {
    169.         DecalProjector lVisualizer = null;
    170.         if ( _pool.Active.TryGetValue( aUnitController.UnitData.UnitID, out lVisualizer ) == false ) {
    171.             lVisualizer = _pool.GetObject( aUnitController.UnitData.UnitID );
    172.         }
    173.  
    174.         Texture2D lTexture = GridCalculator.GetRangeGridTexture( aGridSelection, _targetableColor, true );
    175.  
    176.         int lSize = Mathf.Max( lTexture.height, lTexture.width ) * 2;
    177.         lVisualizer.material.SetTexture( "_Selection_Texture", lTexture );
    178.         lVisualizer.material.SetTexture( "_Selection_Subtexture", null );
    179.         lVisualizer.material.SetFloat( "_Use_Subtexture", 0 );
    180.         lVisualizer.material.SetVector( "_Grid_Size", new Vector4( lSize / 2, lSize / 2 ) );
    181.         lVisualizer.size = new Vector3( lSize, lSize, _depth );
    182.  
    183.         lVisualizer.transform.position = GridCalculator.ToWorldPosition( aGridSelection.CenterPosition ) + Vector3.up * ( _depth / 2 );
    184.  
    185.         Debug.Log( "Show Range Visualizer" );
    186.     }
    187.  
    188.     public void HideSelection( UnitController aUnitController ) {
    189.         HideSelection( aUnitController.UnitData.UnitID );
    190.     }
    191.  
    192.     public void HideSelection( string aSelectionId ) {
    193.         _pool.ReturnObject( aSelectionId );
    194.     }
    195.  
    196.     #region Danger Visualizers
    197.     public bool IsEnemySelected( UnitController aUnit ) {
    198.         return _selectedEnemyUnits.Contains( aUnit );
    199.     }
    200.  
    201.     public void AddSelectedUnit( UnitController aUnit ) {
    202.         if ( _selectedEnemyUnits.Add( aUnit ) == false ) return;
    203.  
    204.         GridSelection lSelection = _selectedEnemyGridSelections.Find( aElement => aElement.ID == aUnit.PositionId );
    205.  
    206.         if ( lSelection == null ) {
    207.             TileInfo lOrigin = aUnit.UnitStateMachine.GetOriginTile();
    208.             int lMoveStat = aUnit.UnitData.TotalStats.Movement;
    209.             int lWeaponRange = aUnit.UnitData.GetEquippedWeapon()?.Range ?? 0;
    210.             Debug.Log( lOrigin.GridPosition );
    211.             lSelection = GridCalculator.GetDangerSelection( lOrigin, lMoveStat, lWeaponRange, aUnit );
    212.             _selectedEnemyGridSelections.Add( lSelection );
    213.  
    214.             RebuildSelectedUnitDangerArea();
    215.         }
    216.     }
    217.  
    218.     public void RemoveSelectedUnit( UnitController aUnit ) {
    219.         if ( _selectedEnemyUnits.Remove( aUnit ) == false ) return;
    220.  
    221.         GridSelection lSelection = _selectedEnemyGridSelections.Find( aElement => aElement.ID == aUnit.PositionId );
    222.         if ( lSelection != null ) {
    223.             _selectedEnemyGridSelections.Remove( lSelection );
    224.             RebuildSelectedUnitDangerArea();
    225.         }
    226.     }
    227.  
    228.     public void RebuildSelectedUnitDangerArea() {
    229.         if ( _selectedEnemyUnits.Count <= 0 ) {
    230.             _selectedEnemyUnitDangerAreas.enabled = false;
    231.             return;
    232.         }
    233.  
    234.         TileInfo lGridOrigin = GridCalculator.GetTile( GridCalculator.GridData.CenterWorldCoordinate, Vector3Int.zero );
    235.         GridSelection lMergedSelection = GridCalculator.CombineSelections( lGridOrigin, _selectedEnemyGridSelections.ToArray() );
    236.         (Vector3Int minExtent, Vector3Int maxExtent) lExtents = GridCalculator.GetSelectionExtents( lMergedSelection );
    237.         float lDepth = Mathf.Abs( lExtents.maxExtent.y - lExtents.minExtent.y ) * GridCalculator.TileSize + _depth;
    238.  
    239.         Texture2D lTexture = GridCalculator.GetDangerGridTexture( lMergedSelection, _selectedEnemyDangerColor );
    240.  
    241.         _selectedEnemyUnitDangerAreas.material.SetFloat( "_DrawOrder ", 0 );
    242.         Debug.Log( _selectedEnemyUnitDangerAreas.material.GetFloat( "_DrawOrder" ) );
    243.  
    244.         _selectedEnemyUnitDangerAreas.material.SetTexture( "_Selection_Texture", lTexture );
    245.         _selectedEnemyUnitDangerAreas.material.SetTexture( "_Selection_Subtexture", null );
    246.         _selectedEnemyUnitDangerAreas.material.SetFloat( "_Use_Subtexture", 0 );
    247.         _selectedEnemyUnitDangerAreas.material.SetVector( "_Grid_Size", new Vector4( lTexture.width, lTexture.height ) );
    248.         //_selectedEnemyUnitDangerAreas.material.renderQueue = 2000 - 9;
    249.         _selectedEnemyUnitDangerAreas.size = new Vector3( lTexture.width * GridCalculator.TileSize, lTexture.height * GridCalculator.TileSize, lDepth );
    250.         _selectedEnemyUnitDangerAreas.pivot = new Vector3( 0, 0, lDepth / 2 );
    251.  
    252.         Vector3 lPosition = Vector3.Lerp( lExtents.minExtent, lExtents.maxExtent, 0.5f ) * GridCalculator.TileSize + GridCalculator.GridData.CenterWorldCoordinate;
    253.  
    254.         _selectedEnemyUnitDangerAreas.transform.position = lPosition + ( Vector3.up * ( lDepth / 2 ) );
    255.  
    256.         _selectedEnemyUnitDangerAreas.enabled = true;
    257.     }
    258.  
    259.     public void ToggleShowAllEnemyDangerAreas() {
    260.         if( _allEnemyUnitDangerAreas.enabled == true ) {
    261.             HideAllEnemyDangerAreas();
    262.         }
    263.         else {
    264.             ShowAllEnemyDangerAreas();
    265.         }
    266.     }
    267.  
    268.     public void ShowAllEnemyDangerAreas() {
    269.         _allEnemyUnitDangerAreas.enabled = true;
    270.      
    271.         if ( _delayedAllEnemyRebuildDangerAreas == true ) {
    272.             RebuildAllEnemyDangerAreas( _delayedAllEnemyRebuildUnits );
    273.         }
    274.     }
    275.  
    276.     public void HideAllEnemyDangerAreas() {
    277.         _allEnemyUnitDangerAreas.enabled = false;
    278.     }
    279.  
    280.     private bool _delayedAllEnemyRebuildDangerAreas = false;
    281.     private UnitController[] _delayedAllEnemyRebuildUnits = null;
    282.  
    283.     public void RebuildAllEnemyDangerAreas( UnitController[] aUnits ) {
    284.         if ( aUnits == null || aUnits.Length <= 0 ) {
    285.             HideAllEnemyDangerAreas();
    286.             _delayedAllEnemyRebuildUnits = null;
    287.             _delayedAllEnemyRebuildDangerAreas = false;
    288.             return;
    289.         }
    290.  
    291.         if ( _allEnemyUnitDangerAreas.enabled == false ) {
    292.             _delayedAllEnemyRebuildDangerAreas = true;
    293.             _delayedAllEnemyRebuildUnits = aUnits;
    294.             return;
    295.         }
    296.  
    297.         GridSelection[] lSelections = new GridSelection[aUnits.Length];
    298.         for ( int i = 0; i < aUnits.Length; i++ ) {
    299.             TileInfo lOrigin = aUnits[i].UnitStateMachine.GetOriginTile();
    300.             int lMoveStat = aUnits[i].UnitData.TotalStats.Movement;
    301.             int lWeaponRange = aUnits[i].UnitData.GetEquippedWeapon()?.Range ?? 0;
    302.             lSelections[i] = GridCalculator.GetDangerSelection( lOrigin, lMoveStat, lWeaponRange, aUnits[i] );
    303.         }
    304.  
    305.         TileInfo lGridOrigin = GridCalculator.GetTile( GridCalculator.GridData.CenterWorldCoordinate, Vector3Int.zero );
    306.         GridSelection lMergedSelection = GridCalculator.CombineSelections( lGridOrigin, lSelections );
    307.         (Vector3Int minExtent, Vector3Int maxExtent) lExtents = GridCalculator.GetSelectionExtents( lMergedSelection );
    308.         float lDepth = Mathf.Abs( lExtents.maxExtent.y - lExtents.minExtent.y ) * GridCalculator.TileSize + _depth;
    309.  
    310.         Texture2D lTexture = GridCalculator.GetDangerGridTexture( lMergedSelection, _allEnemyDangerColor );
    311.  
    312.         _allEnemyUnitDangerAreas.material.SetFloat( "_DrawOrder ", 1 );
    313.         Debug.Log( _allEnemyUnitDangerAreas.material.GetFloat( "_DrawOrder" ) );
    314.  
    315.         _allEnemyUnitDangerAreas.material.SetTexture( "_Selection_Texture", lTexture );
    316.         _allEnemyUnitDangerAreas.material.SetTexture( "_Selection_Subtexture", null );
    317.         _allEnemyUnitDangerAreas.material.SetFloat( "_Use_Subtexture", 0 );
    318.         _allEnemyUnitDangerAreas.material.SetVector( "_Grid_Size", new Vector4( lTexture.width, lTexture.height ) );
    319.         //_allEnemyUnitDangerAreas.material.renderQueue = 2000 - 10;
    320.         _allEnemyUnitDangerAreas.size = new Vector3( lTexture.width * GridCalculator.TileSize, lTexture.height * GridCalculator.TileSize, lDepth );
    321.         _allEnemyUnitDangerAreas.pivot = new Vector3( 0, 0, lDepth / 2 );
    322.  
    323.         Vector3 lPosition = Vector3.Lerp( lExtents.minExtent, lExtents.maxExtent, 0.5f ) * GridCalculator.TileSize + GridCalculator.GridData.CenterWorldCoordinate;
    324.  
    325.         _allEnemyUnitDangerAreas.transform.position = lPosition + Vector3.up * ( lDepth / 2 );
    326.  
    327.         _delayedAllEnemyRebuildUnits = null;
    328.         _delayedAllEnemyRebuildDangerAreas = false;
    329.         //_allEnemyUnitDangerAreas.enabled = true;
    330.     }
    331.     #endregion Danger Visualizers
    332.  
    333.     #region Pool Callbacks
    334.     private void CreateProjector( DecalProjector aProjector ) {
    335.         Material lMaterial = Material.Instantiate( aProjector.material );
    336.         aProjector.material = lMaterial;
    337.         aProjector.transform.parent = this.transform;
    338.     }
    339.  
    340.     private void GetProjector( DecalProjector aProjector ) {
    341.         aProjector.gameObject.SetActive( true );
    342.     }
    343.  
    344.     private void ReturnProjector( DecalProjector aProjector ) {
    345.         aProjector.gameObject.SetActive( false );
    346.     }
    347.     #endregion Pool Callbacks
    348.  
    349. }
     
    Last edited: Sep 7, 2023
  2. collinpatrick15

    collinpatrick15

    Joined:
    Nov 7, 2018
    Posts:
    35
    After slamming my head into my keyboard for a while, I figured out the issue. Changes to the projector material's sort order stop working if the projector component gets disabled. Enabling the component again does not allow the changes or any future changes to propagate. In my code, the projectors that were used in a pool were being disabled via their game objects, not the component, which is why they worked. I changed the two managed projectors that did not work to disable the game objects instead and they began working as expected.

    I don't see why this would be intended behavior and am inclined to believe this is a bug.

    I am using Unity version 2023.1.7f1, URP version 15.0.6