Unity Addressable Image Control
Hi all,
The Unity Addressables system simplifies the lifecycle management of unmanaged resources (textures, models, GameObjects, etc.) in Unity. It also has the benefit of allowing you to store these assets outside the built application, including hosting them remotely. While Addressables has it's issues/quirks, we've found it to be an invaluable addition to our game.
Addressable Image Control
I've created an AddressableImage control which allows you to use Addressable Texture2D assets within Noesis. This control will handle the lifecycle of the Texture2D asset, using the Addressable API to load and release it as necessary.
How It Works
This AddressableImage control inherits from the Image class, and adds a new string AssetPath dependency property. This AssetPath string can be set to the name, or the Unity GUID, of the Addressable Texture2D asset. When AssetPath is set it will release any previously loaded/loading asset, it will then try to load the new Texture2d using Addressables.LoadAssetAsync<Texture2D>(AssetPath). The AddressableImage control takes full control of the Source dependency property, setting Source directly is not supported.
Usage
Here is an example of using the AddressableImage:
"Image" in this binding is an AssetReferenceTexture2d:
Example Project
I've created an example Unity project which takes the Noesis QuestLog sample and makes it's quest image Addressable, you can find this project in a zip archive here:
For the Addressable sample to work you'll need to install Noesis 3.1.4 and set your license details (as normal).
Here is a screenshot of the sample, including the Addressables Event Viewer showing where one image has been released, and the new image loaded (I've added a red arrow pointing to this in the timeline).
AddressableImage Class
Here is the full AddressableImage class. If you don't want to download the example project, this is all you need for integration into your own project.
To allow the use of AddressableImage controls in Blend/WPF, and for use with design time resources, AddressableImage in WPF implements AssetPath as an ImageSource.
The Unity Addressables system simplifies the lifecycle management of unmanaged resources (textures, models, GameObjects, etc.) in Unity. It also has the benefit of allowing you to store these assets outside the built application, including hosting them remotely. While Addressables has it's issues/quirks, we've found it to be an invaluable addition to our game.
Addressable Image Control
I've created an AddressableImage control which allows you to use Addressable Texture2D assets within Noesis. This control will handle the lifecycle of the Texture2D asset, using the Addressable API to load and release it as necessary.
How It Works
This AddressableImage control inherits from the Image class, and adds a new string AssetPath dependency property. This AssetPath string can be set to the name, or the Unity GUID, of the Addressable Texture2D asset. When AssetPath is set it will release any previously loaded/loading asset, it will then try to load the new Texture2d using Addressables.LoadAssetAsync<Texture2D>(AssetPath). The AddressableImage control takes full control of the Source dependency property, setting Source directly is not supported.
Usage
Here is an example of using the AddressableImage:
Code: Select all
<local:AddressableImage AssetPath="{Binding SelectedQuest.Image.RuntimeKey}"/>
Code: Select all
public AssetReferenceTexture2D _image;
public AssetReferenceTexture2D Image { get => _image; }
I've created an example Unity project which takes the Noesis QuestLog sample and makes it's quest image Addressable, you can find this project in a zip archive here:
For the Addressable sample to work you'll need to install Noesis 3.1.4 and set your license details (as normal).
Here is a screenshot of the sample, including the Addressables Event Viewer showing where one image has been released, and the new image loaded (I've added a red arrow pointing to this in the timeline).
AddressableImage Class
Here is the full AddressableImage class. If you don't want to download the example project, this is all you need for integration into your own project.
Code: Select all
#if UNITY_5_3_OR_NEWER
#define NOESIS
using Noesis;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
#endif
using System;
namespace Addressable
{
public class AddressableImage : Image
{
#if NOESIS
private AsyncOperationHandle<Texture2D> _asyncOperationHandle;
public string AssetPath
{
get { return (string)GetValue(AssetPathProperty); }
set { SetValue(AssetPathProperty, value); }
}
public static readonly DependencyProperty AssetPathProperty =
DependencyProperty.Register(nameof(AssetPath), typeof(string), typeof(AddressableImage),
new PropertyMetadata(null, OnAssetKeyChanged));
public AddressableImage()
{
WeakReference weak = new WeakReference(this);
this.Unloaded += (s, e) => { ((AddressableImage)weak.Target)?.OnUnloaded(s, e); };
this.Loaded += (s, e) => { ((AddressableImage)weak.Target)?.OnLoaded(s, e); };
}
public void LoadAsset()
{
UnloadAsset();
if (string.IsNullOrEmpty(AssetPath))
{
return;
}
_asyncOperationHandle = Addressables.LoadAssetAsync<Texture2D>(AssetPath);
_asyncOperationHandle.Completed += OnAssetLoadCompleted;
}
public void UnloadAsset()
{
if (_asyncOperationHandle.IsValid())
{
Addressables.Release(_asyncOperationHandle);
_asyncOperationHandle = new AsyncOperationHandle<Texture2D>();
Source = null;
}
}
private void OnAssetLoadCompleted(AsyncOperationHandle<Texture2D> handle)
{
if (handle.IsValid())
{
if (handle.IsDone
&& handle.Status == AsyncOperationStatus.Succeeded
&& handle.Result != null)
{
Source = new TextureSource(handle.Result);
}
else
{
Addressables.Release(handle);
}
}
}
private void OnLoaded(object sender, Noesis.EventArgs args)
{
LoadAsset();
}
private void OnUnloaded(object sender, Noesis.EventArgs args)
{
UnloadAsset();
}
private static void OnAssetKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AddressableImage assetImage)
{
if (!string.IsNullOrEmpty((string)e.NewValue))
{
assetImage.LoadAsset();
}
else
{
assetImage.UnloadAsset();
}
}
}
#else
public AddressableImage()
{
WeakReference weak = new WeakReference(this);
this.Loaded += (s, e) => { ((AddressableImage)weak.Target)?.OnLoaded(s, e); };
}
private void OnLoaded(object sender, EventArgs args)
{
if (Source != null && AssetPath == null)
{
throw new Exception("Source cannot be used directly, use AssetPath instead.");
}
Source = AssetPath;
}
public ImageSource AssetPath
{
get { return (ImageSource)GetValue(AssetPathProperty); }
set { SetValue(AssetPathProperty, value); }
}
public static readonly DependencyProperty AssetPathProperty =
DependencyProperty.Register(nameof(AssetPath), typeof(ImageSource), typeof(AddressableImage),
new PropertyMetadata(null, OnAssetKeyChanged));
private static void OnAssetKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AddressableImage assetImage)
{
assetImage.Source = (ImageSource)e.NewValue;
}
}
#endif
}
}
Re: Unity Addressable Image Control
This is an awesome contribution and we should work to have this example officially available on GitHub.
Thank you!
Thank you!
Re: Unity Addressable Image Control
Great! I want to do a cleaner implementation which doesn't inherit from Image, but instead inherits from FrameworkElement and uses an Image in it's ControlTemplate. This will remove the weirdness of the AddressableImage control having a publicly writable Source dependency property, which should never be set directly.
- SingingBard
- Posts: 5
- Joined:
Re: Unity Addressable Image Control
I really like this solution, as I wanted to use Addressables for my icons. However, I am having some issues I was hoping I could recruit your aid in resolving. I have an InventoryView which contains a bunch of item Icons. When the user receives an inventory packet from the server, the items in the Inventory refresh the binding. With your solution, all the icons flash. It isn't a huge delay, but it is very noticeable. When using the default Image control, I don't have this issue. I would prefer to use Addressables, but is there any way to eliminate the flashing while the icon is loaded? Is there an easy way to cache the loaded addressable icon so that it doesn't need to asynchronously load it each time? Thanks for your time
Re: Unity Addressable Image Control
Addressables loads the asset on the next frame, which causes this flashing issue. Unless Unity improve this aspect of Addressables, the best thing to do is to load and keep a reference handle for any images/assets before using them in the UI. You don't need to do anything with this handle, it will keep the Addressable asset reference count greater than zero, keeping it in memory, until it and all other references to that asset are released.
One way to achieve this is to store a list of Addressable AssetReferences used for each scene or state in your game, and then load and release these when the scene/state is loaded and unloaded.
One way to achieve this is to store a list of Addressable AssetReferences used for each scene or state in your game, and then load and release these when the scene/state is loaded and unloaded.
Who is online
Users browsing this forum: No registered users and 6 guests