Search Unity

  1. Get the latest news, tutorials and offers directly to your inbox with our newsletters. Sign up now.
    Dismiss Notice

Modding with Addressables?

Discussion in 'Addressables' started by laurentlavigne, Jul 11, 2018.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    4,939
    Is there a plan for an inbuilt modding utility in Addressables or you think modding is still too specific to the games to be part of the engine?
     
    FlightOfOne and andrew-lukasik like this.
  2. PaulBurslem

    PaulBurslem

    Unity Technologies

    Joined:
    Oct 7, 2016
    Posts:
    61
    We don't have plans to specifically support mods, but you can dynamically add IResourceLocators (which are essentially the ContentCatlogs) at runtime. When you load your collection of mods, each one could have the path to load its catalog. Predefined/known labels can be used to retrieve all assets that are the entry points to the mods (prefabs or other data).
     
    TyrannicGoat and andrew-lukasik like this.
  3. Vallar

    Vallar

    Joined:
    Oct 18, 2012
    Posts:
    175
    Excellent question @laurentlavigne I was about to ask the same question.

    Could you please elaborate on this further? We are currently planning to use AssetBundles; we group all our meshes in StreamingAssets in folders based on their type (buildings, characters, items, etc...) and then we load these at run time and when we Instantiate an object at run time, we assign their mesh, sprite, data, etc... using the AssetBundles loaded data.

    How does one do the same with Addressable Assets?

    I kind of understand in a vague way what you've mentioned but not sure I follow how to implement that; like for example what is a ContentCatalog?

    Would what you said mean that instead of AssetBundles I'd use Addressable Assets, load based on group (buildings, characters, items, etc...) and then use the name perhaps of the "Addressable Asset" to assign to the object same way we're doing with Asset Bundles?

    EDIT: Crap, I just realized I necroed the post... I didn't see this is from July. Should I make a new post?
     
  4. nTronz

    nTronz

    Joined:
    Nov 16, 2015
    Posts:
    4
    @Vallar, I came to ask the same questions.

    What functions need to be called to Load said catalog from a predefined user folder? Do users need to have a pre-built Addressable AssetGroup?

    I think we just need some specific functions to understand. I'll make a new thread if we don't get a reply soon.
     
    JotaRata likes this.
  5. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    99
    Necro or not, I'd like to second the questions since they aren't answered yet @PaulBurslem . Nothing seems to be really documented and I cannot put the pieces together here. I see there's a method Addressables.LoadContentCatalog, but here are some specific questions:
    • What exactly is a content catalog (as opposed to an asset bundle)?
    • How do we use that method? What path do we pass to it, relative to what? What does it point to / what is expected to be found at that path - an asset bundle file? Something like that catalog.json?
    • How will modders prepare a content catalog for their mods?
    • How can the game get a list of available content catalogs ("installed mods") to load? Do we put them somewhere under StreamingAssets and scan for certain JSON files using System.IO?
     
  6. Sylmerria

    Sylmerria

    Joined:
    Jul 2, 2012
    Posts:
    324
  7. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    555
    I'd also like to get some clarification about how to actually implement modding with Addressables.
     
  8. Loofou

    Loofou

    Joined:
    Aug 22, 2012
    Posts:
    17
    A second this. A bit of clarification or at least pointers to documentation or even just code that can help with this would be very helpful indeed.
     
  9. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    99
    I see so much activity in this forum, yet the developers (atm it's @unity_bill ?) seem to actively avoid threads with these kinds of questions.

    Addressables are worthless to me with all these open questions. I have long built my own system since I posted my very concrete and presumably easy to answer (for the devs) questions 4 months ago. Probably a lot of other people have as well while Addressables are slumbering in this undocumented "no idea how to use it" state. And the more time progresses with this left unanswered the less likely it becomes that I'm ever interested in using them, because I will have to migrate.

    If you want people to use this feature, please answer their questions (and within less than a year...) or provide a solid documentation.
     
    Last edited: Aug 7, 2019
    bamial_grehk and Sylmerria like this.
  10. danilonishimura

    danilonishimura

    Joined:
    Jul 13, 2010
    Posts:
    69
    @pdinklag I'll try my best to help you. If you guys think I'm wrong, please point it out because I'm also learning about it.

    AFAIK, ContentCatalog is a file that describes the relationship between the key and the asset. At runtime, the catalog is loaded, parsed and becomes a Resource Locator, where it maps the addressable name to the actual asset. In the AssetBundle you reference the asset by its path, where in the addressable, you access it by a name. The advantage is that whenever a catalog is loaded (you can load multiple catalogs), you get the asset by a single call Addressables.LoadAssetAsync(string key) or Addressables.InstantiateAsync(string key), and it will figure out where is that asset from the many bundles registred in the ResourceLocators.

    Addressables.InstantiateAsync(string key), where key is the AddressableName you put in the AddressableWindow:
    upload_2019-8-7_18-15-18.png
    In this case, I'd use:
    var instantiateOp = Addressables.InstantiateAsync<GameObject>("VIEW_Test_Capsule")// it returns a AsyncOperation.
    while (!instantiateOp.IsDone)
    {
    yield return new WaitForEndOfFrame();
    }

    var myCapsule = instantiateOp.Result; // The instantiated capsule object.

    If you use the Addressables.LoadAssetAsync, it will return a non instantiated instance of the asset. You can instantiate it using Instantiate(myCapsule);

    There are more overloads and methods in the Addressables class. Please refer to https://docs.unity3d.com/Packages/c...1/manual/AddressableAssetsGettingStarted.html and https://docs.unity3d.com/Packages/c...ityEngine.AddressableAssets.Addressables.html

    Honestly, I don't know if there is a "right" way to do it. Updating content using addressables can be easy. Setting up a webservice that returns a list of catalogs containing the updated assets, load them, and you should be good to go. Updating game logic is bit trickier. Some people use reflection, some people use runtime scripting (lua, python, etc). There are many approaches, you just have to be sure they work on your target environment.


    The way I see, the easiest way is to put the catalogs in a webserver and download them from there, where you can write server scripts to filter what catalogs you want to be available. You can also put the catalogs in your project, but it makes more complicated to update because you'll have to publish a update of your game to update the assets. Loading them from another source definitively makes it more scalable.
     
  11. senfield

    senfield

    Joined:
    Apr 1, 2019
    Posts:
    31
    You can load a catalog manually using Addressables.LoadContentCatalogAsync(path);
    The path has to be the absolute path to the catalog.json.

    Modding implies that you might want to have a separate Unity project from the one used to create the original game. To do that, you have to pay very close attention to load path and build path settings you use in the Addressable Asset settings for the mod project. They must match where it will be loading from once it is installed in the game.

    Also, if you want to use a script on a modded object it has to be present in both the game and the mod project.
     
    laurentlavigne likes this.
  12. deepakguptaPR

    deepakguptaPR

    Joined:
    Aug 20, 2019
    Posts:
    12
    @danilonishimura : Could you please provide a code snippet for loading catalog from CDN?
    I am using the following code to load catalog json
    Code (CSharp):
    1. Addressables.LoadContentCatalog("[My CDN server]/Android/catalog_2019.09.24.13.06.53.json").Completed += LoadCatalogsCompleted;
    And I am getting following error:
    Code (CSharp):
    1. Exception encountered in operation Resource<ContentCatalogData>(catalog_2019.09.24.13.06.53.json): Unknown error in AsyncOperation
    2. UnityEngine.AsyncOperation:InvokeCompletionEvent()
    3.  
    Thanks in advance
     
  13. JotaRata

    JotaRata

    Joined:
    Dec 8, 2014
    Posts:
    46
    It is possible to download and store the asset bundles built with addressables in a custom folder (i.e. Documents folder) then load any bundle stored in that folder. I want to make a modding system for my game which users are allowed to create custom objects and implement them in the scene of my game.
    Is addressables suited for that or should I revert to the older AB system?
     
  14. Ramobo

    Ramobo

    Joined:
    Dec 26, 2018
    Posts:
    199
    That's pretty much what I asked about here. As far as I know:
    • Loading a content catalog inevitably loads all bundles it references.
    • There's no way to separate bundles into multiple catalogs. The only way would be multiple projects.
    • Good luck properly changing build location.
    The last two are great if want to build your game as mods, like me. Maybe that functionality is in a dark corner of the API I'm not aware of. Maybe.
     
    JotaRata likes this.
  15. JotaRata

    JotaRata

    Joined:
    Dec 8, 2014
    Posts:
    46
    Well I'm using asset bundes in the meanwhile, both of them have their pro and cons
     
  16. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    4,939
    what are the pros + cons of each?
     
  17. Mayhem-McAwesome

    Mayhem-McAwesome

    Joined:
    Oct 31, 2012
    Posts:
    3
    Just wanted to add another voice to the clamoring for more detailed documentation on how to do exactly this.

    There is scant little intel to be dug up about what precisely most of the addressable options shown on the inspector panel are for. Let alone clearly laid out instructions for coding some use out of this new and seemingly quite capable system, without resorting to CSI-style trial and error methods.

    One might suppose there ought to be a manual. Yet in a whole day's worth of searching I've encountered but a most superficial introduction to the subject. Even the few sample projects I managed to find in some unmarked basement of GitHub had little to offer in clearing up the Hows and Whys to the concepts being demonstrated.

    Am I missing something totally obvious that somehow I managed to neglect?

    Is there any detailed and comprehensive documentation, such as about how to achieve anything more than implementing example features without fully understanding them?


    Thanks in advance
     
    cjonesflorida and pdinklag like this.
  18. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    595
    Something very helpful would be build script to build single Addressable Group (mod package) together with catalog info.

    I'll make custom script for it but another very helpful addition to write custom build scripts would be to make all private fields in
    BuildScriptPackedMode.cs
    protected and all static/private functions protected virtual.

    If that's the case I could simply inherit from BuildScriptPackedMode override ErrorCheckBundleSettings (or ProcessBundledAssetSchema) and CreateCatalog, copy/paste functions and replace few lines. I can't do it as fields are private and functions are static. BuildScriptPackedMode.cs has 9! private static fields/functions - very bad extensibility practice.

    This means I have to copy/paste whole BuildScriptPackedMode and check changes every single Addressables update :/
     
    Last edited: Jan 8, 2020
    senfield and Favo-Yang like this.
  19. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    595
    Here's the code that will build single group with catalog info: https://github.com/kamyker/BeatAim-...ster/Editor/Scripts/BuildScriptSingleGroup.cs


    I've managed to get everything working. It requires some painful setup as docs are lacking. Custom scripts and shaders obviously won't work but proxies like this are enough for me:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class VisualizeToMusic_MaterialFloat  : MonoBehaviour
    7. {
    8.     public static event Action<VisualizeToMusic_MaterialFloat> Enabled;
    9.     public static event Action<VisualizeToMusic_MaterialFloat> Disabled;
    10.  
    11.     [SerializeField] public string ShaderFieldName = "_Exposure";
    12.     [SerializeField] public MinMaxSmoothFloat ReactionToMusicLoudness = new MinMaxSmoothFloat(0.05f,0.35f,3, false);
    13.  
    14.     private void OnEnable()
    15.     {
    16.         Enabled?.Invoke( this );
    17.     }
    18.     private void OnDisable()
    19.     {
    20.         Disabled?.Invoke( this );
    21.     }
    22. }
    Reading Unity's code I have to say number of parameters in functions are crazy. Ex:
    Code (CSharp):
    1. PostProcessBundles( assetGroup, buildBundles, outputBundles, results, extractData.WriteData, aaContext.runtimeData, aaContext.locations, builderInput.Registry );
    2. PostProcessCatalogEnteries( assetGroup, extractData.WriteData, aaContext.locations, builderInput.Registry );
     
    Last edited: Jan 10, 2020
  20. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    595
    Trying to make generic class out of the one above. Any idea how to write it better?

    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. public abstract class MonoBehaviourEventful<T> : MonoBehaviour
    6.     where T : MonoBehaviourEventful<T>
    7. {
    8.     public static event Action<T> Enabled;
    9.     public static event Action<T> Disabled;
    10.  
    11.     protected abstract T _this { get; }
    12.  
    13.     private void OnEnable()
    14.         => Enabled?.Invoke( _this );
    15.  
    16.     private void OnDisable()
    17.         => Disabled?.Invoke( _this );
    18. }
    19.  
    Code (CSharp):
    1. public class VisualizeToMusic_MaterialFloat : MonoBehaviourEventful<VisualizeToMusic_MaterialFloat>
    2. {
    3.     [SerializeField] public string ShaderFieldName = "_Exposure";
    4.     [SerializeField] public MinMaxSmoothFloat ReactionToMusicLoudness = new MinMaxSmoothFloat(0.05f,0.35f,3, false);
    5.  
    6.     protected override VisualizeToMusic_MaterialFloat _this => this;
    7. }
     
  21. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    595
    I was wrong, shaders work fine. (oh and animators/animations also!)

    Whole scene imported in my game:


    Tbh it is very powerful function as Unity as level editor is easy to learn for modders.
     
    cjonesflorida and laurentlavigne like this.
  22. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    595
    Updated my game to 2019.3 and I have bad news. Maps made in 2019.2 crash the game. They load fine but one class fails:
    Code (csharp):
    1. Could not produce class with ID 92.
    2. 0x00007FFEE131A54C (UnityPlayer) StackWalker::GetCurrentCallstack
    3. 0x00007FFEE131D1E9 (UnityPlayer) StackWalker::ShowCallstack
    4. 0x00007FFEE130E048 (UnityPlayer) GetStacktrace
    5. 0x00007FFEE2586ABD (UnityPlayer) DebugStringToFile
    6. 0x00007FFEE1DD2F72 (UnityPlayer) PersistentManager::produceObject
    7. 0x00007FFEE1DCE536 (UnityPlayer) PersistentManager::CreateThreadActivationQueueEntry
    8. 0x00007FFEE1DD2CD5 (UnityPlayer) PersistentManager::preallocateObjectThreaded
    9. 0x00007FFEE1402693 (UnityPlayer) ImmediatePtr<Unity::Component>::Transfer<StreamedBinaryRead>
    10. 0x00007FFEE140294D (UnityPlayer) GameObject::Transfer<StreamedBinaryRead>
    11. 0x00007FFEE140D521 (UnityPlayer) GameObject::VirtualRedirectTransfer
    12. 0x00007FFEE1DE40F8 (UnityPlayer) SerializedFile::ReadObject
    13. 0x00007FFEE1DD33DE (UnityPlayer) PersistentManager::ReadAndActivateObjectThreaded
    14. 0x00007FFEE1DD175D (UnityPlayer) PersistentManager::LoadFileCompletelyThreaded
    15. 0x00007FFEE1A5D826 (UnityPlayer) LoadSceneOperation::perform
    16. 0x00007FFEE1A5E3C0 (UnityPlayer) PreloadManager::processSingleOperation
    17. 0x00007FFEE1A5E779 (UnityPlayer) PreloadManager::Run
    18. 0x00007FFEE1C9A1CD (UnityPlayer) Thread::RunThreadWrapper
    19. 0x00007FFF79437BD4 (KERNEL32) BaseThreadInitThunk
    20. 0x00007FFF7A08CED1 (ntdll) RtlUserThreadStart
    It's wouldn't be that bad but game crashes when engine tries to unload unused assets and I guess the class that failed to load. Maps that don't use this script work fine.
    Then:
    Code (csharp):
    1. 0x00007FFEE140D764 (UnityPlayer) GameObject::WillDestroyGameObject
    2. 0x00007FFEE1A286F2 (UnityPlayer) PreDestroyRecursive
    3. 0x00007FFEE1A28747 (UnityPlayer) PreDestroyRecursive
    4. 0x00007FFEE1A26E1A (UnityPlayer) DestroyObjectHighLevel_Internal
    5. 0x00007FFEE1A29BDD (UnityPlayer) UnloadGameScene
    6. 0x00007FFEE1A5DFE1 (UnityPlayer) LoadSceneOperation::playerLoadSceneFromThread
    7. 0x00007FFEE1A5C969 (UnityPlayer) LoadSceneOperation::IntegrateMainThread
    8. 0x00007FFEE1A5F2FC (UnityPlayer) PreloadManager::UpdatePreloadingSingleStep
    9. 0x00007FFEE1A5F61D (UnityPlayer) PreloadManager::WaitForAllAsyncOperationsToComplete
    10. 0x00007FFEE1A5EF19 (UnityPlayer) PreloadManager::UpdatePreloading
    11. 0x00007FFEE1A49844 (UnityPlayer) `InitPlayerLoopCallbacks'::`2'::EarlyUpdateUpdatePreloadingRegistrator::Forward
    12. 0x00007FFEE1A38D47 (UnityPlayer) ExecutePlayerLoop
    13. 0x00007FFEE1A3C9C8 (UnityPlayer) PlayerLoop
    14. 0x00007FFEE134B6CB (UnityPlayer) PerformMainLoop
    15. 0x00007FFEE134A01A (UnityPlayer) MainMessageLoop
    16. 0x00007FFEE134E646 (UnityPlayer) UnityMainImpl
    17. 0x00007FFEE135224B (UnityPlayer) UnityMain
    18. 0x00007FF732A511F2 (BeatAim) __scrt_common_main_seh
    19. 0x00007FFF79437BD4 (KERNEL32) BaseThreadInitThunk
    20. 0x00007FFF7A08CED1 (ntdll) RtlUserThreadStart
    Crash dmp assembly:
    Code (csharp):
    1. GameObject::WillDestroyGameObject:
    2. 00007FFF3635C6C0  mov         qword ptr [rsp+8],rbx
    3. 00007FFF3635C6C5  push        rdi
    4. 00007FFF3635C6C6  sub         rsp,20h
    5. 00007FFF3635C6CA  mov         dword ptr [rcx+78h],10h
    6. 00007FFF3635C6D1  mov         rbx,qword ptr [rcx+48h]
    7. 00007FFF3635C6D5  mov         rdi,qword ptr [rcx+60h]
    8. 00007FFF3635C6D9  shl         rdi,4
    9. 00007FFF3635C6DD  add         rdi,rbx
    10. 00007FFF3635C6E0  cmp         rbx,rdi
    11. 00007FFF3635C6E3  je          GameObject::WillDestroyGameObject+46h (07FFF3635C706h)
    12. 00007FFF3635C6E5  nop         word ptr [rax+rax]
    13. 00007FFF3635C6F0  mov         rcx,qword ptr [rbx+8]
    14. 00007FFF3635C6F4  mov         rax,qword ptr [rcx] //crash here
    15. 00007FFF3635C6F7  call        qword ptr [rax+0B8h]
    16. 00007FFF3635C6FD  add         rbx,10h
    17. 00007FFF3635C701  cmp         rbx,rdi
    18. 00007FFF3635C704  jne         GameObject::WillDestroyGameObject+30h (07FFF3635C6F0h)
    19. 00007FFF3635C706  mov         rbx,qword ptr [rsp+30h]
    20. 00007FFF3635C70B  add         rsp,20h
    21. 00007FFF3635C70F  pop         rdi
    22. 00007FFF3635C710  ret
    23. 00007FFF3635C711  int         3
    24.  
    Code (CSharp):
    1. Unhandled exception at 0x00007FFF3635C6F4 (UnityPlayer.dll) in crash.dmp: 0xC0000005: Access violation reading location 0x0000000000000000.
    Next I've tried:
    Rebuild maps with 2019.3 and Addressables 1.5 (game updated to 1.6)
    Game crashes on map load
    Rebuild maps with 2019.3 and Addressables 1.6 (same version in game)
    Everything works fine again

    With this being said I'm not sure what to do. I have 2 options:
    - maps can't be managed by users as they won't keep them updated - kind of kills modding
    - never ever update unity and addressables

    @karl_jones @DavidUnity3d are there any plans of how backward compatible Addressables will be? Packages made in 2019.3 work in 2019.2 but not the opposite way.

    Edit:
    Forgot to mention that it works fine in editor and is backward compatible. Could it be an issue with il2cpp? - Tried Mono also bugged in build.

    Edit2
    Updated stack traces with pdb files
     
    Last edited: Feb 12, 2020
    laurentlavigne likes this.
  23. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    595
    It seems that
    Code (csharp):
    1. Could not produce class with ID 92.
    is the GUILayer class that got removed in 2019.3. I can update my maps and delete it from cameras but would be good in the future to make GameObject::WillDestroyGameObject less likely to crash.
     
    ImpossibleRobert likes this.
  24. senfield

    senfield

    Joined:
    Apr 1, 2019
    Posts:
    31
    OMG yes. Every time I need to make a build script that fixes just some small thing (such as this bug: https://forum.unity.com/threads/bug...ted-and-breaks-the-build.782540/#post-5207681) I have to duplicate nearly the entire BuildScriptPackedMode script and then use some reflection to hook up the parts they made internal. I have griped about this in the past to insufficient avail and am frustrated. Don't lock down code that people are going to want to override!

    I haven't wanted to mess with the build scripts very much as a result. Instead, I ended up just using a separate project to build the mod catalog. If you configure the paths just right in the addressables settings it can work when installed to a directory in Application.persistentDataPath for example.
    Although you need to do this too to make it available: UnityEngine.AddressableAssets.Initialization.AddressablesRuntimeProperties.SetPropertyValue("Application.persistentDataPath", Application.persistentDataPath);
     
  25. michelm

    michelm

    Joined:
    Jul 2, 2012
    Posts:
    15
    @Kamyker Do you happen to know anything about dependencies? For example, say I have ColorAtlas.material in my main game project, and want to provide it to modders in the modding Unity project. If I create an Addressable bundle mod and load it, the mod will use a new variant of ColorAtlas.material, not the one already included in the main game. Is there any way to avoid this?
     
  26. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    99
    Put it in a core asset bundle and make that public with your modding toolset. Asset bundles won't duplicate resources that are in other asset bundles.
     
  27. michelm

    michelm

    Joined:
    Jul 2, 2012
    Posts:
    15
    Brilliant, I got it working. Thanks! Though I switched back to assetbundles because Addressables were causing too many headaches and I'm about to launch soon. I assume the principle is the same.

    If anyone else is as clueless as me:
    1. Create a "common" bundle in the base game project with assets you want to share.
    2. Export those assets as a package and import the package into the modding project. (took me a while to figure this out because I thought just copying the meta files over with them would work)
    3. In the modding project have an identical "common" bundle and then the mod bundle. The common bundle is just for separating assets and shouldn't be moved/loaded as part of the mod, since you already have one in the base game.
     
  28. Jorhoto

    Jorhoto

    Joined:
    May 7, 2019
    Posts:
    80
    This was said in 2018, is it supporting mods yet?
    Otherwise is there an alternative solution for modding, or tool or a safe workaround? Comeon guys competence from Unreal is getting harder than ever!

    Thanks!
     
  29. michelm

    michelm

    Joined:
    Jul 2, 2012
    Posts:
    15
    There's no "create mods" menu item or whatever, but it's fairly easy to build it into your game using Paul's method or Asset Bundles.This ModTools project uses Asset Bundles, if you want to try it, or look at the code for your own implementation:

    https://github.com/Hello-Meow/ModTool
     
  30. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    99
    I would strongly recommend against using AssetBundles, because they aren't supported anymore. For example, this bug is very serious and won't be fixed for AssetBundles. I've opened a ticket about it and the reply I got is:

    I've reached out to developers regarding this issue and it appears that this was fixed when using Adressable Assets and will not be fixed for direct bundle loading as moving forward Adressables should be used instead.
    Content catalogs are the answer for mods, but they need some custom post-processing I believe. By default, building Addressable content for distribution will create one single catalog with all bundles in there. I'm fairly certain that there's a way to have a catalog per mod. In the worst case, this can be done by editing the generated catalog JSON files directly after building, using custom scripts.

    Once you have them, you can dynamically find them in the game folders and load them via the API. The concept of asset labels can be used to mark objects, e.g., for bootstrapping (containing mod name, author, etc.).

    I have no working example yet, but I've been using Addressables for a while now and once you get the hang of how distribution works with it, it becomes kinda obvious. Unfortunately there's just no step-by-step documentation yet anywhere.
     
  31. michelm

    michelm

    Joined:
    Jul 2, 2012
    Posts:
    15
    Hah, I think I submitted the same bug, more or less, where a transparent material loaded from an asset bundle won't be transparent in editor (but will in build). And yeah, they said they're not fixing it. The project is far enough along that I can deal with it. You're right, if starting from scratch Addressables is the way to go.
     
  32. Jorhoto

    Jorhoto

    Joined:
    May 7, 2019
    Posts:
    80
    After reading Unity's doc, I found this video series very helpful:
    (Found IResourceLocators are implemented in there)

     
    michelm likes this.
  33. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    219
    Does somebody here successfully use Addressable System as modding tool (Steam Workshop) in large project (and will be willing to share impressions and general advices) ?

    Also i have a questions:
    1. Does exist an easy way to build only selected groups/entries into bundle file ? When I call AddressableAssetSettings.BuildPlayerContent(),
    it build all groups, so for large project it takes some time. Here is a solution, which works but it is not elegant - I copy info about existed groups/items into dictionary,
    then I remove all groups except group to export. After build process, I revert all groups info. Something like this:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEditor;
    5. using UnityEditor.AddressableAssets;
    6. using UnityEditor.AddressableAssets.Settings;
    7. using UnityEditor.AddressableAssets.Settings.GroupSchemas;
    8. using UnityEngine;
    9.  
    10. public class AddressableEditor : EditorWindow
    11. {  
    12.     [MenuItem("Assets/Addressable Editor")]
    13.     static void ShowWindow ()
    14.     {
    15.         EditorWindow.GetWindow ( typeof(AddressableEditor));
    16.     }
    17.    
    18.     private string _ExportGroupName = "";
    19.  
    20.     void OnGUI()
    21.     {
    22.         _ExportGroupName = EditorGUILayout.TextField(_ExportGroupName);
    23.         if ( GUILayout.Button( "Build" ) )
    24.         {
    25.             Dictionary<string, List<string>> dictionary = CreateDictionary();
    26.             ReduceGroups(_ExportGroupName);
    27.             AddressableAssetSettings.BuildPlayerContent();
    28.             LoadDictionary(dictionary);
    29.         }
    30.     }
    31.  
    32.     Dictionary<string, List<string>> CreateDictionary()
    33.     {
    34.         AddressableAssetSettings settings = LoadSettings();
    35.         Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
    36.         List<AddressableAssetGroup> groups = settings.groups;
    37.         for (int i = 0; i < groups.Count; i++)
    38.         {
    39.             if (groups[i].Name == "Built In Data") continue;
    40.             List<AddressableAssetEntry> entries = groups[i].entries.ToList();
    41.             List<string> list = new List<string>();
    42.             for (int j = 0; j < entries.Count; j++) list.Add(entries[j].AssetPath.ToString());
    43.             dictionary.Add(groups[i].Name, list);
    44.         }
    45.         return dictionary;
    46.     }
    47.  
    48.     void LoadDictionary (Dictionary<string, List<string>> dictionary)
    49.     {
    50.         AddressableAssetSettings settings = LoadSettings();
    51.         foreach (KeyValuePair<string, List<string>> pair in dictionary)
    52.         {
    53.             List<string> list = pair.Value;
    54.             AddressableAssetGroup newGroup = LoadGroup(pair.Key);
    55.             if (newGroup == null) newGroup = CreateGroup(pair.Key);
    56.             for (int j=0; j<list.Count; j++)
    57.             {
    58.                 string guid = AssetDatabase.AssetPathToGUID(list[j]);
    59.                 AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, newGroup, false, true);
    60.                 entry.SetAddress(list[j], true);
    61.                 Debug.Log(pair.Key + " ### " + list[j]);
    62.             }
    63.         }
    64.     }
    65.  
    66.     void ReduceGroups(string Exception)
    67.     {
    68.         AddressableAssetSettings settings = LoadSettings();
    69.         List<AddressableAssetGroup> groups = settings.groups.ToList();
    70.         for ( int i = groups.Count - 1;  i >= 0; i--)
    71.         {
    72.             if ( groups[i].Name == Exception ) continue;
    73.             settings.RemoveGroup( groups[i] );
    74.         }
    75.     }
    76.  
    77.     AddressableAssetSettings LoadSettings()
    78.     {
    79.         string guid = AssetDatabase.FindAssets( "t:AddressableAssetSettings" ).FirstOrDefault();
    80.         string path = AssetDatabase.GUIDToAssetPath( guid );
    81.         return AssetDatabase.LoadAssetAtPath<AddressableAssetSettings>( path );
    82.     }
    83.  
    84.     AddressableAssetGroup LoadGroup(string groupName)
    85.     {
    86.         AddressableAssetSettings settings = LoadSettings();
    87.         return settings.groups.Find( c => c.name == groupName );
    88.     }
    89.  
    90.     AddressableAssetGroup CreateGroup(string groupName)
    91.     {
    92.         AddressableAssetSettings settings = LoadSettings();
    93.         BundledAssetGroupSchema bundledAssetGroupSchema = ScriptableObject.CreateInstance<BundledAssetGroupSchema>();
    94.         ContentUpdateGroupSchema contentUpdateGroupSchema = ScriptableObject.CreateInstance<ContentUpdateGroupSchema>();
    95.         var schemas = new List<AddressableAssetGroupSchema>{bundledAssetGroupSchema, contentUpdateGroupSchema};
    96.         return settings.CreateGroup(groupName, false, false, true, schemas);
    97.     }
    98. }
    Example function to install catalog.json and bundle file into game (for test purposes):

    Code (CSharp):
    1. void Install()
    2. {
    3.     string gamepath = EditorUtility.OpenFolderPanel("Select game directory", "", "");
    4.     if (!File.Exists(gamepath + "/game.exe"))
    5.     {
    6.         Debug.Log("Selected directory is not valid !");
    7.         return;
    8.     }
    9.     if (gamepath != null)
    10.     {
    11.         string sourceCatalogJSON = Application.dataPath + "/../Library/com.unity.addressables/aa/Windows/catalog.json";
    12.         string targetCatalogJSON = gamepath + "/Game_Data/StreamingAssets/catalog.json";
    13.         if (File.Exists(targetCatalogJSON))
    14.         {
    15.             File.Delete(targetCatalogJSON);
    16.             File.Copy(sourceCatalogJSON, targetCatalogJSON);
    17.         }
    18.         else
    19.         {
    20.             File.Copy(sourceCatalogJSON, targetCatalogJSON);
    21.         }
    22.         string[] filePaths = Directory.GetFiles(Application.dataPath + "/../Library/com.unity.addressables/aa/Windows/StandaloneWindows64");
    23.         for (int i=0; i<filePaths.Length; i++)
    24.         {              
    25.             string filename = Path.GetFileName(filePaths[i]);
    26.             string targetBundles = gamepath + "/Game_Data/StreamingAssets/aa/StandaloneWindows64/" + filename;
    27.             if (File.Exists(targetBundles))
    28.             {
    29.                 File.Delete(targetBundles);
    30.                 File.Copy(filePaths[i], targetBundles);
    31.             }
    32.             else
    33.             {
    34.                 File.Copy(filePaths[i], targetBundles);
    35.             }
    36.         }
    37.     }
    38.     Debug.Log("Mod installed");
    39.     EditorUtility.RevealInFinder(gamepath + "/game.exe");
    40. }
    Load catalog.json into game:

    Code (CSharp):
    1.     // example: LoadConfigCmd ("Modding//catalog.json");
    2.     void LoadConfigCmd (string filepath)
    3.     {
    4.         string path = Path.Combine(Application.streamingAssetsPath, filepath);
    5.         if (!File.Exists(path)) return;
    6.         var locator = Addressables.LoadContentCatalog(path);
    7.     }
    Load asset:

    Code (CSharp):
    1.     async Task<GameObject> LoadAssetInstance (string path)
    2.     {
    3.         IList<UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation> locations = await Addressables.LoadResourceLocationsAsync(path).Task;
    4.         if (locations.Count == 0) return null;
    5.         AsyncOperationHandle<GameObject> handle = Addressables.InstantiateAsync(locations[0]);
    6.         await handle.Task;
    7.         return handle.Result;
    8.     }
    2. Rebuild Sprite Atlas Cache - sometimes can take a long time. Does this process can be omitted ?
     
  34. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    99
    Korindian likes this.
  35. Ramobo

    Ramobo

    Joined:
    Dec 26, 2018
    Posts:
    199
    Right now, only project assets appear in the `AssetReference` dropdown. There should also be a way to add assets in external catalogs to that list. This would make it a lot easier for modders to build scenes with your built bundles without you handing out the source.
     
    Last edited: Mar 10, 2021
  36. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    99
    That'd indeed be nice, but my guess is that Addressables alone probably can't solve this. You'd need heavy integration of Addressables into the Editor, as asset bundles would have to be unpacked on the fly. I can't see that happen anytime soon.

    If by "sources" you mean assets such as models, textures etc., you'll probably have to include them in your modding and mapping package. Concerning source code for behaviours, I solved this by using stub behaviours.

    Let's say you want mappers to be able to place weapons in a scene. In my project, I'd have two classes: WeaponPrefab and Weapon. WeaponPrefab is a stub that only contains the fields I want to configure in the inspector (e.g. an enum of possible weapons, asset addresses, etc.), while Weapon implements the actual behaviour. Now, both are in different assemblies, I called them Common and Game. For modders, I plan on only giving out the Common assembly source code containing the prefab stubs, the "Game" assembly containing the actual logic is exclusive to the game. After scene load, I scan for all prefab stub objects and add the corresponding behaviours. All this requires is a mapping from prefab stub classes to logic classes that you need to define somehow.

    EDIT: In case I misunderstood - if you just want addresses to appear in a popup, that may be easy to do already using custom inspectors / editors, but it'd indeed also be easy for the Addressables team to accomplish.
     
    Last edited: Mar 10, 2021
  37. Ramobo

    Ramobo

    Joined:
    Dec 26, 2018
    Posts:
    199
    Asset Bundle Browser exists, so it's definitely possible. It all depends on Unity's willingness and prioritization.

    The point is that if imported bundles' assets were usable from the `AssetReference` dropdown, distributing them in bundled form only would still make modding practical.

    My solution would be to have modders copy certain game assemblies into their projects and link the scenes in their bundles against those.

    Maybe I should go back to that one abandoned project to see whether I'm talking out of my ass.
     
unityunity