Search Unity

What's the mean of ExposedReference<T>?

Discussion in 'Timeline' started by UnityEdge, Jul 18, 2017.

  1. UnityEdge

    UnityEdge

    Joined:
    Jul 18, 2017
    Posts:
    1
    hi, these days i'm learning about the timeline. i found 'ExposedReference<GameObject> ObjectToMove' at timeline example scripts, i google about it but only get few things. i want to know what it and the Resolve () means, and why we should use it, how it works ? thanks.
     
    wolilio and elhispano like this.
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Playable Assets, including Timeline, are assets and cannot directly contain references to game objects and components in the scene.

    An exposed reference is a placeholder for the scene object. Resolve() uses a 'Resolver' as a look up table that maps exposed references to scene objects.

    In the case of Timeline, all (non-prefab) GameObjects and Component references are stored inside the PlayableDirector that is playing the Timeline. The resolver is actually the PlayableDirector, and Resolve() is asking it to retrieve the assigned game object.

    The drawback is if you reuse a timeline asset, you need to reassign the ExposedReferences.
     
    sungkukparkwooga likes this.
  3. winxalex

    winxalex

    Joined:
    Jun 29, 2014
    Posts:
    166
  4. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    @winxalex Thank you for the video. It was very helpful in understanding how to implement the IExposedPropertyTable interface. However, I think I am trying to do the opposite of your example and resolve the exposed reference from a method called on the ScriptableObject itself and not the MonoBehaviour.

    I think I need to create a method like PlayableGraph.GetResolver() to do this because just passing the script that implements IExposedPropertyTable into the Resolve() function doesn't seem to work, but I don't know how to use the ExposedPropertyResolver stuct in order to do this. Any help with this would be greatly appreciated!
     
  5. tinyant

    tinyant

    Joined:
    Aug 28, 2015
    Posts:
    127
    thanks for explain.
     
  6. tinyant

    tinyant

    Joined:
    Aug 28, 2015
    Posts:
    127
    IsaiahKelly likes this.
  7. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    418
    @tinyant Thanks for the example code! Maybe now I can finally figure out how to use this thing . Unity really needs better documentation and tutorials on things like this.

    Sorry to hear you're stuck on Unity 5.6.2. Is this maybe there under a different name/space and/or implementation? Believe the API was at first experimental and has changed a lot in the final release.
     
  8. tinyant

    tinyant

    Joined:
    Aug 28, 2015
    Posts:
    127
    Yeah Unity has some undocument or unclear document for Us.
    In Unity5.6.2
    Code (CSharp):
    1. namespace UnityEngine
    2. {
    3.     internal interface IExposedPropertyTable
    4.     {
    5.         void ClearReferenceValue(PropertyName id);
    6.         Object GetReferenceValue(PropertyName id, out bool idValid);
    7.         void SetReferenceValue(PropertyName id, Object value);
    8.     }
    9. }
    so we can't inherent from IExposedPropertyTable.

    In Unity2017 IExposedPropertyTable marked public for Us, I think Unity must use ExposedReference for their TimeLine system.:)
    Code (CSharp):
    1. namespace UnityEngine
    2. {
    3.     public interface IExposedPropertyTable
    4.     {
    5.         //
    6.         // Summary:
    7.         //     ///
    8.         //     Remove a value for the given reference.
    9.         //     ///
    10.         //
    11.         // Parameters:
    12.         //   id:
    13.         //     Identifier of the ExposedReference.
    14.         void ClearReferenceValue(PropertyName id);
    15.         Object GetReferenceValue(PropertyName id, out bool idValid);
    16.         //
    17.         // Summary:
    18.         //     ///
    19.         //     Assigns a value for an ExposedReference.
    20.         //     ///
    21.         //
    22.         // Parameters:
    23.         //   id:
    24.         //     Identifier of the ExposedReference.
    25.         //
    26.         //   value:
    27.         //     The value to assigned to the ExposedReference.
    28.         void SetReferenceValue(PropertyName id, Object value);
    29.     }
    30. }
     
  9. amatiasq

    amatiasq

    Joined:
    Jun 8, 2017
    Posts:
    3
    I created a `MonoBehaviour` that implements `IExposedPropertyTable` and I can get from any other component.

    That way the playables can get a resolver from any GameObject, as long as they always get the resolver from the same GameObject they will always get right scene references.

    I don't know why Unity API can't be simpler.

    Code (CSharp):
    1. public class ExposedReferencesTable : MonoBehaviour, IExposedPropertyTable
    2. {
    3.     public List<PropertyName> properties = new();
    4.     public List<Object> references = new();
    5.  
    6.     public T Get<T>(ExposedReference<T> reference) where T : Object
    7.     {
    8.         var result = GetReferenceValue(reference.exposedName, out var idValid);
    9.         return idValid ? result as T : null;
    10.     }
    11.  
    12.     public void Set<T>(ExposedReference<T> reference, T value)
    13.         where T : Object
    14.     {
    15.         SetReferenceValue(reference.exposedName, value);
    16.     }
    17.  
    18.     public Object GetReferenceValue(PropertyName id, out bool idValid)
    19.     {
    20.         var index = properties.IndexOf(id);
    21.  
    22.         if (index == -1) {
    23.             idValid = false;
    24.             return null;
    25.         }
    26.  
    27.         idValid = true;
    28.         return references[index];
    29.     }
    30.  
    31.     public void SetReferenceValue(PropertyName id, Object value)
    32.     {
    33.         var index = properties.IndexOf(id);
    34.  
    35.         if (index == -1) {
    36.             properties.Add(id);
    37.             references.Add(value);
    38.         } else {
    39.             references[index] = value;
    40.         }
    41.     }
    42.  
    43.     public void ClearReferenceValue(PropertyName id)
    44.     {
    45.         var index = properties.IndexOf(id);
    46.  
    47.         if (index == -1)
    48.             return;
    49.  
    50.         properties.RemoveAt(index);
    51.         references.RemoveAt(index);
    52.     }
    53. }
    Code (CSharp):
    1. public static class ComponentExtensions
    2. {
    3.     public static ExposedReferencesTable GetExposedReferencesResolver(this Component transform)
    4.     {
    5.         var found = gameObject.GetComponent<ExposedReferencesTable>();
    6.         return found == null ? gameObject.AddComponent<ExposedReferencesTable>() : found;
    7.     }
    8. }
    Usage:

    Code (CSharp):
    1. public class MyPlayable : PlayableBehaviour
    2. {
    3.     private ExposedReferencesTable referenceResolver;
    4.    
    5.     [SerializeField]
    6.     private ExposedReference<SomeType> _someType;
    7.  
    8.     public SomeType someType {
    9.         get => referenceResolver.Get(_someType);
    10.         set => referenceResolver.Set(_someType, value);
    11.     }
    12.  
    13.     public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    14.     {
    15.         if (playerData is not Component component)
    16.             return;
    17.        
    18.         if (referenceResolver == null)
    19.             referenceResolver = component.GetExposedReferencesResolver();
    20.            
    21.         // magic
    22.         Debug.Log(someType);
    23.     }
    24. }
     
    Last edited: Feb 3, 2023
    ModLunar and akent99 like this.
  10. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    Thanks for sharing the code! I was just bumping into ExposedReference yesterday when trying to create a CinemachineShot clip for a Timeline. I ended up with the following code (avoiding needing to implementing anything - and its not the same use case as you were describing), but I don't know if its a "good" approach! I assume the references are local per Timeline, so the scoping of names is relative to that Timeline...?

    Code (CSharp):
    1. // The virtual camera reference uses the ExposedReference<> type magic.
    2. var cmShot = clip.asset as CinemachineShot;
    3. cmShot.DisplayName = cm.name;
    4. cmShot.VirtualCamera.exposedName = cm.name; // Unity normally uses a GUID, I think just has to be unique.
    5. shot.GetComponent<PlayableDirector>().SetReferenceValue(cmShot.VirtualCamera.exposedName, cm.GetComponent<CinemachineVirtualCamera>());
     
    Yuchen_Chang likes this.
  11. fbmd

    fbmd

    Joined:
    Dec 4, 2016
    Posts:
    16
    Thanks for this code. Looks awesome. I just tried it out in a little experiment and created a little ExposedReference<Transform> in a dummy ScriptableObject and (probably too naively) expected to see a Transform field in the ScriptableObjects inspector view. But nothing shows up.

    Code (CSharp):
    1. public class TestSO : ScriptableObject
    2. {
    3.     private ExposedReferencesTable referenceResolver;
    4.  
    5.     [SerializeField]
    6.     private ExposedReference<Transform> _transform;
    7.    
    8.     public Transform transform {
    9.         get => referenceResolver.Get(_transform);
    10.         set => referenceResolver.Set(_transform, value);
    11.     }
    12. }
    What am I missing?
     
  12. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    Not sure what the "ScriptableObjects Inspector view is" so what I did was create a game object with a script that had a component with a property of type TestSO then clicked through.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. public class Foo : MonoBehaviour
    4. {
    5.     public TestSO test = new();
    6.     public void OnValidate()
    7.     {
    8.         test = new TestSO();
    9.     }
    10. }
    In the inspector I double clicked on the test property and it opened up the TestSO object - its showing 'transform' for me.

    upload_2023-3-14_10-26-51.png

    The other thing you can try is the top right corner of the inspector window (the three dots) there is "normal" and "debug" view modes. Try Debug if Normal does not show what you want.
     
  13. fbmd

    fbmd

    Joined:
    Dec 4, 2016
    Posts:
    16
    Well, normally when I have a public Field in a ScriptableObject, it will appear in the Unity-Inspector when selecting the ScriptableObject in the Project-Panel. So in my above Example, if I just write
    public Transform myTransform;
    then myTransform shows up in the Inspector. If I make an ExposedRefenrence<Transform> instead, I would expect that it also shows up in the Unity inspector. But it doesn't.
     
  14. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588

    Attached Files:

  15. fbmd

    fbmd

    Joined:
    Dec 4, 2016
    Posts:
    16
    No, it's a field

    ...
    [SerializeField]
    private ExposedReference<Transform> _transform
    ...

    This should definitely show up in the ScriptableObjects inspector.
     
  16. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    When I tried your code, I could see the private field "_transform" of type exposed reference in the inspector (in 2022.2) in both Normal and Debug mode. The property was not shown. I confirmed by renaming it, and the name in the inspector changed as well. I wonder if its a bug they fixed or something? E.g. "transform" and "_transform" are both shown as "Transform" in the inspector. I got a funky result with "transform" and "_transform" fields for example.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. [CreateAssetMenu]
    4. public class TestSO : ScriptableObject
    5. {
    6.     [SerializeField]
    7.     private ExposedReference<Transform> _transform;
    8.     public Transform transform;
    9. }
    10.  
    Normal mode for inspector, both are visible, but the indentation is weird:
    upload_2023-3-15_15-31-42.png

    Debug mode for inspector looks better:
    upload_2023-3-15_15-32-25.png

    But I am out of ideas. Good luck with it!
     
  17. JDatUnity

    JDatUnity

    Joined:
    May 15, 2017
    Posts:
    7
    I can see the fields just fine on my ScriptableObject, but I can't actually drag any scene objects into the fields, any ideas?
     
  18. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    I don’t know sorry. Since it has to create an exposed reference (it has to use a key it can resolve later to fetch the object) I would have guessed the object you are dragging in would have to be in the resolver table so you can do a reverse lookup to find the key for that object in the scene. But that is pure guesswork.
     
  19. tsukimi

    tsukimi

    Joined:
    Dec 10, 2014
    Posts:
    80
    In normal way, you can only start assigning scene objects to ExposedReference when the inspector is a in-scene GameObject Inspector and with a IExposedPropertyTable script attached (like playable director, or your ExposedReferencesTable script).
    If the ExposedReference is in a ScriptableObject, you'll need to write custom editor code to show the contents. (some editor code such as, when attaching the ScriptableObject to some component, the content of the ScriptableObject will be shown in the compnoent's editor)

    example inspector, that you can assign scene objects to ExposedReference:
    GameObject Inspector
     ├ ExposedReferencesTable component editor
     └ Some component's editor, that has [SerializedField] TestSO in it
       └ attached TestSO's contents, containing the ExposedReference field (←normally Unity won't show attached content for you, so you have to write this Editor yourself! just iterate through the SerializeProperties and use PropertyField to show them)

    The reason why it is so complicated is that, ExposedReference Editor is using SerializedObject.context to resolve the reference, which will be set when it is "GameObject Inspector and with a IExposedPropertyTable script attached". (context == IExposedPropertyTable)
     
  20. JDatUnity

    JDatUnity

    Joined:
    May 15, 2017
    Posts:
    7
    Last edited: Apr 12, 2023
    Yuchen_Chang likes this.
  21. Sarai

    Sarai

    Joined:
    Jul 20, 2014
    Posts:
    30
    I have the same issue where it shows "Type mismatch" when it actually just working fine

    upload_2023-6-25_21-51-9.png

    As you can see, the Debug.Log is successful
    upload_2023-6-25_21-48-52.png
     

    Attached Files: