BrandonReinhart
Topic Author
Posts: 29
Joined: 10 Jan 2025, 17:02

Custom Unity SpriteAtlas control.

30 Jan 2025, 19:18

I'm attempting to override Image to provide support for Unity sprite atlases.

The inline usage looks like this:
<local:AtlasImageExt Atlas="WheatAtlas" Region="farm_wheat_01_lvl1" Width="64" Height="64" />
The backing xaml is mostly empty:
<Image
    x:Class="ArchitectOfRuin.AtlasImageExt"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ArchitectOfRuin"
    xmlns:noesis="clr-namespace:NoesisGUI">
</Image>
And when I update the sprite the method is:
        private void UpdateSprite()
        {
            if (!_spriteAtlas)
                return;

            if (string.IsNullOrEmpty(Region))
                return;

            _sprite = _spriteAtlas.GetSprite(Region);
            if (!_sprite)
            {
                Debug.LogError($"Sprite '{Region}' not found in Atlas '{Atlas}'");
                return;
            }

            // Get the sprite texture rect (in pixels).
            var spriteRect = _sprite.textureRect;
            // var atlasWidth = _sprite.texture.width;
            // var atlasHeight = _sprite.texture.height;

            // Convert pixel-based rect to normalized UV coordinates.
            var x = spriteRect.x;
            var y = spriteRect.y;
            var width = spriteRect.width;
            var height = spriteRect.height;

            Source = new ImageBrush
            {
                ImageSource = new TextureSource(_sprite.texture),
                Viewbox = new Rect(x, y, width, height),
                ViewboxUnits = BrushMappingMode.Absolute,
                TileMode = TileMode.None,
                Stretch = Stretch.None
            }.ImageSource;
        }
No matter what I provide for the viewbox I see the entire image. I have confirmed the ImageBrush is working, just that the Viewbox isn't being respected.

What am I missing?

Ah! I figure the mistake is in the assignment of .ImageSource. Not sure how to use the whole ImageBrush.
 
BrandonReinhart
Topic Author
Posts: 29
Joined: 10 Jan 2025, 17:02

Re: Custom Unity SpriteAtlas control.

30 Jan 2025, 19:34

I figured it out. I needed to have a UserControl that contained an ImageBrush:
using Noesis;
using UnityEngine;
using UnityEngine.U2D;
using Rect = Noesis.Rect;

namespace ArchitectOfRuin
{
    public class AtlasImageExt : UserControl
    {
        private SpriteAtlas _spriteAtlas;
        private Sprite _sprite;
        private ImageBrush _imageBrush;

        public static readonly DependencyProperty AtlasProperty =
            DependencyProperty.Register(
                "Atlas",
                typeof(string),
                typeof(AtlasImageExt),
                new PropertyMetadata(null, OnAtlasChanged));

        public string Atlas
        {
            get => (string) GetValue(AtlasProperty);
            set => SetValue(AtlasProperty, value);
        }

        public static readonly DependencyProperty RegionProperty =
            DependencyProperty.Register(
                "Region",
                typeof(string),
                typeof(AtlasImageExt),
                new PropertyMetadata(null, OnRegionChanged));

        public string Region
        {
            get => (string) GetValue(RegionProperty);
            set => SetValue(RegionProperty, value);
        }

        public AtlasImageExt()
        {
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            NoesisUnity.LoadComponent(this);
            LoadAtlas();
            UpdateSprite();
        }

        private static void OnAtlasChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AtlasImageExt) d).LoadAtlas();
            ((AtlasImageExt) d).UpdateSprite();
        }

        private static void OnRegionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AtlasImageExt) d).UpdateSprite();
        }

        private void LoadAtlas()
        {
            if (string.IsNullOrEmpty(Atlas))
                return;

            _spriteAtlas = AssetStorage<SpriteAtlas>.Get(Atlas);
        }

        private void UpdateSprite()
        {
            if (!_spriteAtlas)
                return;

            if (string.IsNullOrEmpty(Region))
                return;

            _sprite = _spriteAtlas.GetSprite(Region);
            if (!_sprite)
            {
                Debug.LogError($"Sprite '{Region}' not found in Atlas '{Atlas}'");
                return;
            }

            if (_imageBrush == null)
                _imageBrush = new ImageBrush();

            var spriteRect = _sprite.textureRect;
            var atlasWidth = _sprite.texture.width;
            var atlasHeight = _sprite.texture.height;

            var x = spriteRect.x;
            var y = spriteRect.y;
            var width = spriteRect.width;
            var height = spriteRect.height;

            var flippedY = atlasHeight - y - height;

            _imageBrush.ImageSource = new TextureSource(_sprite.texture);
            _imageBrush.Viewbox = new Rect(x, flippedY, width, height);
            _imageBrush.ViewboxUnits = BrushMappingMode.Absolute;
            _imageBrush.TileMode = TileMode.None;
            _imageBrush.Stretch = Stretch.None;

            var rect = FindName("AtlasImageRect") as Rectangle;
            if (rect != null)
                rect.Fill = _imageBrush;
        }
    }
}
The XAML:
<UserControl
    x:Class="ArchitectOfRuin.AtlasImageExt"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ArchitectOfRuin"
    xmlns:noesis="clr-namespace:NoesisGUI">
    <Rectangle x:Name="AtlasImageRect">
        <Rectangle.Fill>
            <ImageBrush x:Name="AtlasBrush" />
        </Rectangle.Fill>
    </Rectangle>
</UserControl>
The usage:
                <local:AtlasImageExt Atlas="WheatAtlas" Region="farm_wheat_01_lvl1" Width="64" Height="64" />
Excellent!
 
User avatar
jsantos
Site Admin
Posts: 4823
Joined: 20 Jan 2012, 17:18
Contact:

Re: Custom Unity SpriteAtlas control.

30 Jan 2025, 21:28

Hey Brandon, sprites (and atlases) are natively supported in our plugin. Have you tried that?
 
BrandonReinhart
Topic Author
Posts: 29
Joined: 10 Jan 2025, 17:02

Re: Custom Unity SpriteAtlas control.

31 Jan 2025, 01:39

What's an example? Thanks!
 
BrandonReinhart
Topic Author
Posts: 29
Joined: 10 Jan 2025, 17:02

Re: Custom Unity SpriteAtlas control.

31 Jan 2025, 15:44

I see that sprites can be registered with the TextureProvider. How do I get an IconSource from that in code?

Ah! I see, I got this working. I was overthinking it (again). Thank you.
 
User avatar
sfernandez
Site Admin
Posts: 3363
Joined: 22 Dec 2011, 19:20

Re: Custom Unity SpriteAtlas control.

04 Feb 2025, 11:26

Our Inventory example uses an Sprite Atlas for showing the inventory icons.

Thanks for the update, I'm going to mark this as solved then.

Who is online

Users browsing this forum: Bing [Bot] and 3 guests