Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

ScriptableObject References in Addressables

Discussion in 'Addressables' started by Colin_MacLeod, Nov 13, 2019.

  1. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    Hi there!

    I have some UI assets that aren't used all the time, so I would like to dynamically load and unload them using Addressables.

    These UI assets refer to various ScriptableObjects storing my game state.

    When I load the Addressable objects, it looks like they are creating new instances of the referenced ScriptableObjects - so they don't share the game state.

    Is there a way to tell these dynamically loaded components to all use the same (preloaded) instance of each SO?
     
  2. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    I guess the silence speaks volumes?
     
    gooby429, tsirimak and Tymianek like this.
  3. Ramobo

    Ramobo

    Joined:
    Dec 26, 2018
    Posts:
    212
    Welcome to the Unity forum. This is probably not exclusive to here, but don't be surprised when you're downright ignored.
     
  4. Jribs

    Jribs

    Joined:
    Jun 10, 2014
    Posts:
    154
    I will preface this by saying I have no idea, but that use case kind of seems like its against what addressables is for.

    If you need a universal game state that is populated by something from a bundle you should probably load up your scriptable object at the beginning and save the reference to it, then anything that needs to interact with it should target the references object.
     
  5. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    Hey, thanks for replying, @Jribs

    My universal game state is not populated from a bundle. I have some scriptable objects that live on the filesystem and are accessed by various scene objects.

    The issue is that if you take one of those scene objects and make a prefab out of it, and then instantiate that prefab using addressables, it seems the reference in the instance points to a new copy of the ScriptableObject - not the common one shared with the rest of the app.
     
    rocky1138, Efril and glitchers like this.
  6. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Referenced assets is copied inside each used bundle if they are not addressable themselves. Check dependency analyzer.
     
    laurentlavigne likes this.
  7. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    @TextusGames Not sure I follow. I'm looking for some way to map the addressables to an existing ScriptableObject which is not an addressable (currently).
     
  8. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
  9. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    @unity_bill I see you have posted replies to a few similar threads.

    Can you please chip in here? I don't want two copies of the scriptable objects - ideally, I want all my addressables to reference the original instances of the scriptable objects referenced by all the scene objects - not create a new instance of each for the addressables world.

    Using scenes (regular scenes, not using addressables) work in this way. If I reference the same scriptable object in two separate scenes, the data is shared between them.

    Is there a way to get the addressables for a given key to point to the original instance of the scriptable object?
     
    glitchers likes this.
  10. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,088
    At some point I thought that using scriptable objects for shared states was nice to have but together with addressables it is a bit of a hell due to new instances of the scriptable object. So instead of sharing states using scriptable objects I reverted back to static singleton classes that contain the state. Since most of it is singleton anyway.

    So what I now tend to do is use scriptable objects strictly for copies of data only.
    i.e. if you have stats for a monster -> load in the scriptable object stats -> copy stat values to the instance of the monster -> unload the scriptable object.

    If converting references to static references is not possible or too much work, one workaround would be loading in the scriptable objects at the start and have any other object then load in the scriptable object by AssetReference as well.
    As long as the scriptable object isn't unloaded it should get the same reference.
    I had some trouble with that as well at some point. Now I just refrain from using scriptable objects for states at all costs as a result.

    bottomline: it sucks but thats just how it works.
     
  11. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    180
    I had similar problem with ScriptableObjects. Then I had one Global scene, which is supposed to always exist. It was a non-addressable scene, which loaded all the other Addressable scenes. As Global scene used the same Scriptables that were used in Addressable scenes, Scriptables were duplicated. I fixed this by turning Global scene into addressable too. Now I have one non-addressable GlobalLoader scene in Build Settings, which loads Global, and have no problems with duplicate Scriptables anymore.
    Problem with Scriptables appeared once more when I turned Prefabs into addressables. I then had 2 Groups, one with scenes, another with prefabs. Then I ran "Check Duplicate Bundle Dependencies" rule in Analyze Tool and created separate Group with dependencies that were previously duplicated in scenes and prefabs groups.
     
    QuestionsBrown likes this.
  12. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    @RunninglVlan

    Yes, that does sound very similar to my situation.

    Interesting. I've not done anything with scenes via Addressables - how do you tell Unity to fire up the first scene via addressables - usually that's just part of the build settings?

    Fundamentally, I don't really understand why there is a need for these extra instances in the first place? This all feels really counterintuitive. When I mark a Scriptable Object as addressable in Unity Inspector and give it a key, I expect that instance to be assigned the key, not a copy of that instance.

    @unity_bill why does Addressables make this copy in the first place? Is there a way to force the addressables system to assign the key to the same ScriptableObject instance as the rest of the app? If there were, I wouldn't have to worry about all this stuff, and I could refer to the same instance via an AssetReference in the loaded assets.
     
    Last edited: Nov 26, 2019
    RunninglVlan likes this.
  13. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    180
    GlobalLoader is now the only scene in Build Settings. In Built game Unity loads it. Then GlobalLoader loads Global scene, and immediately GlobalLoader is unloaded. Afterwards I work only with Addressable Scenes. That's how it is if I understood your question correctly.
     
  14. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    So, is GlobalLoader just the name of your bootstrap scene, with your loader as a monobehaviour in that scene? Or is GlobalLoader some part of Addressable Assets I've not come across?
     
  15. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    180
  16. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    Ah cool, thought so - just checking :)
     
  17. Paul_H23

    Paul_H23

    Joined:
    Jun 19, 2019
    Posts:
    45
    I had a similar problem, using ScriptableObjects all over the place, following Ryan Hipple's great presentation, and found that all addressable SO's were being duplicated, voiding the hole purpose of addressables if you ask me.

    I didn't find a suitable solution out of the box I'm afraid. I had to build a complex system of initialisation. I ended up using AssetReferences, but this didn't solve my problem either, as being asynchronous, I had no way of knowing easily when a reference was available to use, without flooding my code with tonnes of annoying code to check and get if not ready. In the end, I created a system based on marker interfaces. Every Monobehaviour (and some others, but that complicates it even further) that has an AssetReference to a SO implements this marker interface, and references the SO through a wrapper class. Then, at startup, a loader object is responsible for going through all objects that implement the marker interface and resolving their asset references. The final, and most annoying part, is that then all objects with any Monobehaviour that has these marker interfaces has to start out disabled, otherwise startup code that relies on the availability of the SO might run. I've done this by putting everything under a "Game Controller" object, that I turn off, and if I turn it on to do some editing, and forget to turn it off, everything goes pear shaped when I play, so I've gotten used to noticing that weird behaviour.

    It's a real pain, and I wish I hadn't had to spend so much time on something that seems like it should just work, but hey, onwards and upwards. The advantage is, I can use assetreferenced SO's throughout my code, without concern for whether they have been retrieved or not, and be quite safe in the knowledge that, as long as I follow the arcane rules, it'll work. Fingers crossed.
     
  18. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    @Paul_H23 Spot on. Yes, that's pretty much exactly where I am. My approach is also based on Ryan Hipple's presentation from Austin 2017. It works wonderfully - until you try to use addressables.

    I experimented with AssetReferenceT a bit - hit the same asynchronous issues.

    My use case is specific: to dynamically load/unload parts of the app to conserve memory. It seems like a perfect fit for addressables, but as everyone is saying it's incompatible with the Richard Fine/Ryan Hipple approach, so I may have to go with small scenes loaded additively, each scene only containing a few prefabs.

    For me, the other option does seem to be to use addressables but maintain my own ServiceLocator style map of ScriptableObjects to address and replace the duplicate entries - but that seems messy and goes against the grain of the whole Dependency Injection architecture I'm using.

    Like you, it feels to me like this is something addressables should address "out of the box" - especially since we are both following well respected (and Unity promoted) architectural guidelines. Is there anyone else over at Unity I should be asking about how to approach this?
     
    Last edited: Nov 27, 2019
    rocky1138, Paul_H23 and glitchers like this.
  19. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,088
    There are many ways around it. But you should pick something that you are comfortable to work with.
    After I experimented heavily with ryan hipple's approach, at first I found it nice to just reference the scriptable objects but once the project grew larger and larger it became a hassle. Managing that many scriptable objects is not great at all.
    When implementing addressables it became even more of a hassle due to duplicate instances of the scriptable objects.

    At that point you have a few options:
    - Get rid of the scriptable objects and replace them with static class entries i.e.
    - Preload scriptable objects and reference them in a list somewhere so that additional loads take that same instance of the scriptable object. (that's how it should work but not certain)
    - Initialize them via a higher up management system to make sure that the scriptable object is of the same instance.
    Something like a dictionary which takes an asset reference as key and keeps the instance as value.
    When the key doesn't exist in the dictionary you load the instance and add it to the dictionary.

    It's hard to tell what to do without knowing your project. People can only give you some workarounds, but thats about it.

    In my case I just got rid of most scriptable object variables / references and replaced them with static class entries.
    Some of them I didn't replace and loaded them in in a bootstrap scene and then loaded the reference additionally in other scripts.
    Basically learned my lesson to use scriptable objects for data only and not for coupling systems together.
    When projects become pure DOTS based then this doesn't matter much anymore as everything is DoD based and not OOP based.
     
  20. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    Hi @MaskedMouse, well I can only speak from personal experience of course but over here the Ryan Hipple approach works well. I don't want to use singletons because that statically binds relationships - you need to change code to remap a reference to a subtype. Creating hard references between entities like this makes it difficult to test, isolate problems or evolve the design over time.

    With scriptable objects, the whole system is mapped out via inspector-defined relationships. This is a fundamental principle of inversion of control and one of its main benefits. Having worked on a few large projects like this, I have found it scales very well. I'm not sure what the hassles were that you experienced - but linking scriptable objects via the inspector was straightforward for us; it's been easy to swap out a single instance (or multiple instances).

    For these reasons, I don't see removing scriptable objects as a solution - they are not the problem, in our system at least.

    Most ways of using addressables with scriptable objects seem to involve a workaround - either a dictionary to preload & "fix" the references at instantiation time or using the service locator pattern possibly via AssetReferenceT. Both solutions lose this fundamental advantage of the Ryan Hipple / Inversion of Control approach (i.e. it's better to link components in just one place, in Unity Inspector).

    Again, I would side with @Paul_H23 to suggest this feels fundamentally like an issue that should be addressed in Addressable Assets. Scriptable objects are supposed to work the way they do everywhere else in the Unity stack. Of course, that's a design decision for the folks over at Unity. For our specific use case (remove assets dynamically to conserve memory), changing the whole architecture to use Addressable Assets doesn't seem to be the right trade off.

    If there is no way to assign the original instance of each scriptable object to a single Addressable Assets key, the best solutions for us are probably back to basics:

    - use prefabs and instantiate/destroy them directly
    or
    - use small scenes to group prefabs and instantiate/destroy those directly
     
    Last edited: Nov 28, 2019
    Kazko likes this.
  21. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    So just ran into this issue myself. Working on a solution and will post what I come up with.

    To me it doesn't seem entirely counter intuitive. Or perhaps I should say once I understood the issue it seems congruent with how everything works. An AssetBundle will grab a copy of all files it uses, unless it is using that file from another assetbundle. This seems to work, move your ScriptableObjects into an assetbundle and load them through that. But then the async issue, where I want to ensure my reference is set up in Awake so I can start using the object in Start.

    I am thinking that one should differentiate between 'Service' scriptable objects and 'Datum' scriptable object. Where Datum just holds data, like a color or a float. Then a 'Service' holds state some kind.

    I made this here to Pool loaded addressables:
    https://github.com/rygo6/GTPooling

    My current thinking is to add a 'AddressableServicesPool', where you can prepool a bunch of AddressableServices, then retrieve them synchronously in Awake.

    Using Addressable for services does have the odd benefit that you could theoretically update the serialized data of a service by updating the assetbundle for it.
     
  22. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I updated that repo. My solution is a 'ServiceReference' and a 'AddressableServicesPool'. The Pool will load all the services in the begining and pool them. Then a ServiceReference has a path that lets it retrieve them synchronously in await. Basically this kind of ends up just using my pooling system like a Service Locator.

    For the purpose of IOC, this oddly is like even more IOC. Because your services are decoupled. Then your services are loaded via Addressables, so you could switch the service that everything points to via addressables.

    Inside my open source codebases I also have a ComponentContainer service scriptableobject which you can use as a ServiceLocator for components rather than FindObjectOfType.
     
  23. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    I am also going through the hell of migrating to Addressables with a project heavily based on SO Architecture. My problem is somewhat similar so I decided to post it ... maybe this will help investigate further.

    @techmage @Colin_MacLeod

    Rephrased issue based on further findings (see Edits):
    Instantiating ScriptableObjects referenced by Addressable scenes does not produce a full clone when in Use Existing Build mode. However, full clones are produced if the scene referencing the originals is not addressable.

    -----
    (EDIT 2 - turns out, the cloning works as it should in build! So, the only mode where the SO is not cloned properly is Use Existing Build. Use Asset Database, Simulate Groups, as well as final build - the cloning works as expected.)

    (EDIT - I realized that the scene difference in fact is that the scenes are referencing different instances of the SO (as was explained earlier). However, in this case, both instances seem identical (in terms of data), until you clone them. Cloned instance referenced by Scene in build, is a perfect clone. Cloned instanced referenced by a scene in addressables, is incomplete. Why is that?)
    -----
    Original post:

    The issue seems very complex and there are many combinations to test, so maybe I'm not getting this right at the moment, BUT, after couple of hours since "why nothing works out of a sudden" moment, the issue could be described as:

    Instantiating SO assets from components in Addressable scenes clones them in a very shallow way (basic fields retain values, but custom classes do not). Rephrase for clarity: a monobehavior component instantiates SO (that is addressable). If that component is in a scene that is also addressable (loaded by Addressables.LoadSceneAsync), the SO copy is incomplete. If the same component is in a scene that is not addressable (it's in build), the cloned copy is perfect. Also, this happens only using Play Mode Script - Use Existing Build.

    Test Setup: I have tested in a playmode session which has three scenes.
    - First is loaded at start, as it's index 0 in build. The component instantiates addressable SO (by direct reference), and the copy is a perfect clone.
    - Next, Addressable scene is loaded async with the same component. This time, the SO clone is incomplete.
    - Finally, another "build" scene (with index 1) is loaded by scenemanager. Again, the SO clone is perfect.

    Aside from the fact that it would be perfect if it behaved the same way, this is counter intuitive. I have moved my scenes to addressables based on analyzer, as when done so, duplicate dependencies were resolved.

    Haven't tested in Build, only editor, but I presume it should behave the same way when Using Existing Build.
     
    Last edited: Dec 2, 2019
  24. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    @Kazko Sounds a little different to my situation. I really just wanted to find a way to index objects that should be loaded/unloaded, to conserve memory.

    @techmage Sounds like a solution. But it's really nice to be able to just edit scriptable objects in the inspector at runtime, and have those values persist. Don't you lose that if you decide to instantiate everything using addressables?

    In the end, I removed the addressables and rolled my own "Dependency Manager" that maintains references to prefab instances and destroys them when they are no longer referenced by any other component.
     
    rocky1138 and Kazko like this.
  25. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    @Colin_MacLeod This is where it's actually kind of interesting. If you set it to use "AssetDatabase" to simulate AssetBundles, then it uses the ones in the actual project, so they save when you stop playing. If you set it to use built AssetBundles, then it loads the SO from the bundle and it is a clone, thus you lose changes. This in a back-handed way actually gives you the ability to turn saving of SO changes on and off.

    I think some more classes need to be refactored in my GTPooling lib, but the way it ended up working so far I think actually brought more pros to the table compared to just using SO's directly.

    - Settings on your Service SO's can now be updated in a production build by pushing new assetbundles. This kind of ends up being a kind of 'LiveOps' solution.
    - You can toggle the behavior to have SO's reset on play/stop by switching it between using bundles or assetdatabase.
    - You can change the Service SO all your components reference by changing it in addressables, and not having to drag a new reference into all your components.
    - You can use the AssetReference UI to select your desired references on components. This is one of the things I wish to further refactor, as I just inherited the AssetReference UI. But I'd like when you make a 'ServiceReference' it would limit it to only addressables marked with 'Service'.

    @Kazko I think your understanding of it is actually a bit more intricate than it needs to be. It's pretty simple. Anything referencing a SO which gets put into a Bundle will clone the SO. If things from different Bundles, or from outside a Bundle need to reference the same SO, then that SO must be seen as a dependency and loaded from an external shared location.

    I would actually expect you could set this up so all SO's go in a shared dependency bundle that would get auto-loaded first. I haven't played with that yet though.
     
    Kazko likes this.
  26. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    @Colin_MacLeod Yeah sorry, didn't mean to derail, but first I thought it's a referencing problem, and it turned out to be something else.

    @techmage Thanks for the reply. If I may ... perhaps you can help if I explain a bit more. I do have every SO in a group, and analyzer says there are no issues. But this turned out to be a different problem than dependency clones. What I found out is that instantiating SO at runtime sometimes does not produce a full clone of the SO. There are very specific conditions when this happens.

    First, it only happens when Use Existing Build Play Mode Script is used. In other words, a game logic based on instantiating SOs suddenly stop working. Everything works in the other two modes, even in build, but not in the mentioned play mode. This seems like a bug, or I am not understanding something.

    And second - this is where it gets interesting - this only happens when a component that is doing the instantiation of the SO, is a part of a scene that is addressable. If the scene is not addressable, the component produces a proper clone. For the record, I checked that in both scenes, the actual referenced SO has all the data. But it loses some of the data when instantiated from a scene that is addressable.

    What I'm saying is that it should not matter if the scene is addressable (nor it should definitely not matter if we're using Use Existing Build script), instantiate should produce a proper clone.
     
    Colin_MacLeod likes this.
  27. Colin_MacLeod

    Colin_MacLeod

    Joined:
    Feb 11, 2014
    Posts:
    324
    Ah - of course! That makes sense! Yes, the AssetDatabase mode is just simulated, so it'll just see the disk-based asset.

    This is good stuff - I hadn't thought of that. Think we're too far along on this project to reengineer everything (or all SOs at least) to be addressable, but I do see how that could work really well. Nice!
     
  28. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    @Kazko maybe I didn't quite understand. I also don't quite follow when you say a "proper clone". If you instantiate something, whether a prefab or a SO, the only state that gets carried over is serialized state. So anything marked with [SerializeField], this is expected behavior.

    When you say "In other words, a game logic based on instantiating SOs suddenly stop working." I assume you have a SO which is storing state in some fields or properties, and these values are changing and supposed to be shared to many things which reference it. It would break because the things referencing that SO are actually referencing different instances of the SO. Call Debug.Log(SO.GetInstanceID) on your things referencing the shared SO to make sure its actually the same instance.

    If your referencing an SO directly in an inspector field, then that component gets put into an AssetBundle, it will make a copy of that SO. When your AssetBundle loads you somehow have to make that field reference the common shared SO.
     
  29. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    @techmage Thanks for the reply. I tried to reproduce this in a clean project and have found out that the problem I'm having is I'm using Odin Inspector. Now it's clear that Odin's special serialization is not carried over to the cloned SO. But still, only in that particular play mode and only in addressable scenes. Thanks for your patience, really appreciate it. Now it's time to take this to Odin's devs to see if it's a bug or some weird limitation. I would understand more if it didn't work at all, than to have these special corner cases, if you know what I mean.
     
  30. glitchers

    glitchers

    Joined:
    Apr 29, 2014
    Posts:
    64
    I also my structured my project using Scriptable Objects to store game state / references at run time. I have found it an excellent way to work without having to make everything tightly coupled.

    However I have been updating a project to work with Addressables and ran into the cases you are talking about. I've tried a few different 'fixes' but they are cumbersome and have different trade offs and refactoring requirements.

    I've attached a project with my current workaround. It uses a FixedAddressID which is a wrapper around a GUID. Then on your ScriptableObjects either implement IFixedAddressObject or inherit from FixedAddressObject. They just have a reference to a FixedAddressID. The benefit of this system is it not asyncronous and requires very minimal work to your project to use.

    Then before you reference your object, wherever it is, you call fixedObject = FixedAddressCollection.FixReference(fixedObject); and it will ensure you are using the 'correct' reference object. It works by using the GUID as a key and the first FixReference call will assign the reference every object will use.

    Depending on how your project is setup, it could make it so the ScriptableObject used is not the one actually in your project window. I made a small editor so if you do need to use the Editor to inspect the runtime object you still can. If you select the object in your project it, underneath the FixedAddressID field there can be a warning which tells you this is not the shared reference currently in use with a button to select the loaded one.

    I hope there is an official fix for this in the future, but for now I hope this at least helps someone else.
     

    Attached Files:

    Last edited: Mar 10, 2020
  31. FlightOfOne

    FlightOfOne

    Joined:
    Aug 1, 2014
    Posts:
    668
    After rummaging through the forums and looking around for over a week I found this post. I am glad that I am not alone but sad to see that this problem is here to stay. I too have been fallen victom to Ryan Hipple! I use SO for everything.


    I would love to hear what you ended up doing? Also, I'd love to hear what Unity's take on this.

    @glitchers, I like your idea, thanks!

    @MaskedMouse
    You make some good points though, maybe it is time to ditch the ScriptableObjects... I am not using DoTs yet but might as well get used to living without relying on ScriptableObjects for everything.
     
  32. glitchers

    glitchers

    Joined:
    Apr 29, 2014
    Posts:
    64
    @FlightOfOne

    I have been using my above suggestion with a few minor tweaks for a couple production projects since then and it’s mostly ok.

    Biggest issue is something I am assuming is a bug. Very rarely but if it happens, it happens consistently - some statically referenced ScriptableObject becomes null. It doesn’t call any OnDestroy etc methods but I’m not sure ScriptableObjects do. Honestly it’s infuriating. Sometimes renaming the addressable group or doing a clean build can fix it.

    I’ve now started working around that by serialising a struct with the object and its id/key rather than relying on the object to get the key.

    Other than that, the bugs which come from me realising this object is being duplicated or me just forgetting to run “FixReference”

    I really wish this could be fixed at the engine level. The objects already have ids, let me use them! Addressables let’s us tick a box in the inspector to make it addressable, but let us tick one to make it always the same.
     
    serpin likes this.
  33. FlightOfOne

    FlightOfOne

    Joined:
    Aug 1, 2014
    Posts:
    668
    I completely agree! Addressables have been around for a while too, maybe I missed it but, I am suprised to see that there is not even a mention of this.

    I am very much on the newer side when it comes to Addressables and I do not have experience with Asset Bundles. But how I have come to understand Addressables is once you build a bundle it might as well be a separate game. Everything you set in the editor (build time?) is unreliable. I guess you have to solely rely on runtime systems and references.

    Well at least now I know, I am not doing something wrong to cause this with SOs. Thanks again for sharing!
     
  34. magmagma

    magmagma

    Joined:
    Oct 27, 2017
    Posts:
    41
    Hello guys.
    It is viable to use scriptable objects with addressables, but you do need to be careful with a few things.
    Also, this is a problem that already existed with asset bundles too.

    1. If you have direct references in your scenes, they will be completely different instances of any scriptable objects that you load from addressables.

    2. If you load a scriptable object (from addressables) in a scene, and then load it again after closing it and opening a different scene (maybe calling Resources.UnloadUnusedAssets ), the previous instance will become null and you will be loading a new one (unless you keep a reference and don't explicitly unload it I guess).

    3. If you load a scriptable SCENE that includes your scriptable object addressable, then that instance will disappear when you unload the scene (even if you keep a reference, it will become null).

    The solution is to keep a permanent reference (with the first one not being part of an addressable scene), and everytime the scriptable object is instantiated/accessed, check if the reference is already there and use that one instead. That way you can make sure it is always the same instance that is being referenced.

    Edit: I see that other people have proposed this solution ( e.g. MaskedMouse ), so it seems we agree on that part.
     
    Last edited: Nov 5, 2020
    FlightOfOne likes this.
  35. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Wait so if you load a scriptable object with addressables, and nothing in any script has a reference to it, and you call UnloadUnusedAssets, then it will unload the scriptable object?
     
    Last edited: Nov 23, 2020
  36. magmagma

    magmagma

    Joined:
    Oct 27, 2017
    Posts:
    41
    Yes that is correct as far as I understand. I believe at that point it will be unloaded.
     
  37. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    That's also true for non-addressable Scriptable Objects (SOs). I used to add them to a static list in OnEnable, to avoid unloading them. It's a problem with normal assets too.

    Now, I've been using HideFlags.DontUnloadUnusedAsset. It seems to work well with addressables, although I've not explicitly tested it, I don't see any reason for it not to work. Maybe calling Don'tDestroyOnLoad would work too, but I haven't explored what happens to those when explicitly unloading ununsed assets. I like HideFlags because they are serialized, so they can be set from editor-only logic; for example, that helps to only apply them to assets and not their instances.

    I've found the real special case with addressables to be that bundles can be released explicitly. That's normally very useful, because it's faster than unloading unused assets; but I think it's the source of some problems. This applies when you're solving the adressable problem by putting your SOs in a bundle and every scene in different bundles: If the SO bundle is only loaded as a dependency of the scenes, when the scene bundles are released, the data in the SOs bundle can be lost. Or sometimes it seems it's not lost, but it's duplicated again when another bundle reloads it as a dependency.

    So, it seems that, no matter the HideFlags in your SOs, you also need to load the bundle they are in explicitly, so the ref count is always at least 1. It can be done by loading any of the assets in the bundle once. It seems to have worked, as far as I can see.

    If you're interested in a weirder solution, in my specific case, all this kinds of Scriptable Object assets are never used directly; they are only used as an identifier. Each identifier serves to access a specific instance of an SO (or a POCO generated by an SO), usually stored in a dictionary. I don't do this because of addressables; I do it because it allows me to do things like having different health variables on different enemies. I even do this for things like global variables too, because it allows me to easiliy reset them, even when DomainReloading is disabled; I just clear the static dictionary where they are stored.

    But, given that I already do that, I've been experimenting with serializing the Asset GUID as part of the Scriptable Object and using it as an identifier instead of the InstanceID. That means that even SOs duplicated by AssetBundles refer to the same data. It does use a little more memory per object, and I'd still do the other thing, just to save memory by preventing duplicated assets, but it seems to work fine enough for me, at least to ensure things will not break by accident.
     
    FlightOfOne likes this.
  38. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    If that is all true then what is the purpose of the 'Release' methods? I would assume you'd need to call Release explicitly, otherwise they would be retained forever.

    Also did the UI that counts references go away? I can't seem to find it.
     
  39. magmagma

    magmagma

    Joined:
    Oct 27, 2017
    Posts:
    41
    If you still have references, they will not be unloaded even if you call Resources.UnloadUnusedAssets, because they are technically still being used.
    Calling the "Release" methods will reduce the reference counts and if they reach 0 they are ready to be freed from the memory, either by the automatic garbage collector of manually if you call Resources.UnloadUnusedAssets.

    If they are part of a scene and you close the scene with no remaining references, then that part is automatic. But sometimes you load/unload stuff without changing scenes.
     
  40. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,464
    So what is conclusion? Should include SO to asset bundle or not? When include SO to bundle, are there no flaws?
     
  41. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    339
    One year later, in Unity 2021.3.0f1, Addressables version 1.20.3

    The lost Scriptable Objects is still present :/
    Any hope for a clean fix ?
     
    Last edited: Jul 14, 2022
    DungDajHjep likes this.
  42. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,088
    There's not going to be a fix, only workarounds. Even with workarounds there are a few issues left.

    What I ended up doing for a project was to put everything into addressables.
    The build was just a bootstrap scene to load up an addressable bootstrap scene.
    All scenes were addressable, except the initial bootstrap scene. So all references to scriptable objects had to come from addressables, nothing of it was built-in anymore.
     
  43. fakegood

    fakegood

    Joined:
    Oct 11, 2013
    Posts:
    31
    For my situation, I made scriptable objects addressables, and the scenes referencing those scriptable objects are addressables too. No problem since version 1.5.x
     
  44. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    339
    Huum...

    I've already put everything into addressables (scenes and scriptable objects). I'm also loading everything from a bootstrap scene... And it is still not working :/

    One more precision : the problem occurs only when loading bundles from built files.

    When I test it locally from the editor using the "Use Asset Database" or "Simulate Groups" options, everything is fine.
    It is breaking when I try "Use existing Build", or from the built project.
     
  45. serpin

    serpin

    Joined:
    Nov 13, 2011
    Posts:
    54
    Did you mark your ScriptableObjects as addressables? Because it should work: watch this (see 4:24):


    In case you don't want to (or can't) do this, you can implement glitchers' approach with a FixedAddressID wrapper for GUID of ScriptableObjects. It actually works like a charm when SOs are treated as dependencies and as such get duplicated in asset bundles. I'd like to chime in and personally thank glitchers for coming up with such a cool workaround and an approach of wrapping asset GUIDs in such a way in general. It might come useful in other areas as well, because generic classes are awesome.

    For those who are confused or intimidated by the approach as glitchers explained it, allow me to present you with a how-to:

    1. Download FixedAddressTest.zip (see above) and create a new Unity project from its contents (for testing). Or just copy the scripts from the ScriptableObjectUtils folder and make sure your project has Addressables bundle imported.

    2. Inherit your ScriptableObjects from FixedAddressObject (I renamed this base class to FixedAddressScriptableObject for clarity). It should become your new, one and only base class for all SOs from now on if you want to continue following the "SO Architecture" approach whilst using the awesome Addressables system.

    3. For all live objects in the scene that reference any of the derived FixedAddressScriptableObjects add the following line to their Awake() or Start() scripts:
      Code (csharp):
      1. fixedSOReference = FixedAddressCollection.FixReference(fixedSOReference);
      Where fixedSOReference is a type of derived class reference. Say, "ArmorScriptableObject" with all of your armors, that extends FixedAddressObject. So in such case you would use:
      Code (csharp):
      1. ArmorScriptableObject myArmorSOReference = FixedAddressCollection.FixReference(myArmorSOReference);
    4. The trick is that any object that makes the very first call to FixReference() upon app initialization will add its own reference to the ArmorScriptableObject SO to the shared static dictionary of FixedAddressObjects. And any other that follows with the same call with receive a new reference to the same shared object in memory. So even if you have, say, a build-embedded asset bundle with armors and decided to create a separate DLC bundle which will come with its own copy of your ArmorScriptableObject, all of new armors from the DLC will replace their references to a shared ScriptableObject that's being used by non-DLC armors since most likely those get initialized before the DLC ones.

    5. Done! To verify, build your asset bundles, switch Play Mode Script mode in your Addressables Manager window to "Use Existing Build" and hit Play. Then, when you click your original ArmorScriptableObject asset file among project files, you will see a button that warns you that "Hey, you're not editing the SO that is used by objects in run-time. Click Select to jump to that object" — and sure enough, as you click you will see that you're taken to a clone of your SO that resides in memory and is shared by all objects that need to reference it (as long as you didn't change the FixedAddress of your SO by using the "New" button in the custom inspector after building your asset bundles, otherwise the button will take you nowhere). You can double check by changing values to see how those values change for all objects from all asset bundles, or double click a reference to this SO from any of those, which will take you to the same in-memory run-time shared SO. Again — only if those objects are actually supposed to use the same asset as non-DLC armors. If they are referencing another, separate derived ScriptableObject, that was specifically created for DLC armors, even then, if there are 2, 3 or more DLCs with armors using this new SO they will properly share this new SO in memory with this approach.
    Personally, I absolutely love such tricks!

    A tip and some considerations: As fakegood mentioned previously and from the aforementioned video by Unity, you now know that if you mark all of your ScriptableObjects as addressables and scenes that have any objects that might reference them as addressables as well, those SOs will not get duplicated in separate asset bundles because addressables system will know that those SOs are a part of the addressables catalog and only need to be packed once, even of they are referenced by objects from separate Addressable Groups. To verify: open Addressables -> Analyze window and run "Fixable rules" analysis to confirm that SOs aren't duplicated any more since they are now treated as named addressable assets and not common dependencies.

    In this case it's up to you to:
    1. Make sure all SOs are marked as addressables (to avoid them being treated as "ordinary" dependencies)

    2. Decide which SOs will be a part of a locally-build and distributed asset bundle and which will come from, say, a remote server. Because if any of your objects in the whole game request an SO that is a part of a remote bundle, Unity will have to download the whole bundle to be able to use that particular SO. This is getting into application architecture even further, but is another good example of how planning ahead is important.

    3. But even in case you have no duplicate SOs, while working in the Editor if you wish to directly modify some settings of SOs that came from an addressables bundle while testing in "Use existing build" addressables mode, you can make use of the glitchers' approach to be able to quickly jump to a run-time SO in the scene with a click of a button in the inspector thanks to all SOs being logged in a dictionary by their GUIDs upon the first FixReference() call. Just remember to do that in each calling script with a reference to your ScriptableObjects or put that call into initializers of base classes of your entities =)
    With SOs marked as addressables you can continue working on your game knowing that you don't have to sacrifice neither SOs, nor Addressables and will be able to both work in comfort (and without Singletons!) and future-proof your game by allowing yourself to extend it in the future with downloadable content packs using Addressables.
     
    Last edited: Sep 26, 2022
  46. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    339
    @serpin

    Thank you very much for this detailled answer :)
    It seems to be working for me.
     
  47. njellingson

    njellingson

    Joined:
    Jun 25, 2015
    Posts:
    3
    Thanks everyone for this thread! I made a repo that hopefully reproduces and demonstrates this issue as simply as possible. I really wish there were giant flashing warning signs on those unity articles promoting scriptableobject architecture since this seems to be a really tricky technical issue, especially for newbies.

    I had read this article, got swept up in the hype, worked for a couple weeks in my off time getting a prototype working with a bunch of Unity gaming services (I had a ScriptableObject tracking references to the player inventory pulled down from Unity Economy Service, etc.) only to realize it simply did not work as expected on a build. A lot of time was spent but I think an important lesson was learned.

    Here's the link to my repo with the demonstration of this issue: https://github.com/njelly/addressables-scriptableobjects-test

    EDIT: added a demonstration of how to work around this problem by marking relevant ScriptableObjects and Scenes as addressable.
     
    Last edited: Jun 1, 2023
    DungDajHjep likes this.
  48. kyuskoj

    kyuskoj

    Joined:
    Aug 28, 2013
    Posts:
    52
    Has the problem been alleviated now that we can use AssetReferences synchronously?