Popcorning Guinea Pig
Search Results for

    Nine-Slice Textures

    Nine-slice textures are a powerful technique for creating scalable UI elements that maintain their visual quality at any size. The Popcorning Guinea Pig engine provides comprehensive support for nine-slice textures in UI components.

    Overview

    Nine-slice textures allow you to:

    • Scale UI elements without distorting corners and edges
    • Create buttons and panels that look good at any size
    • Reduce the number of texture assets needed
    • Maintain consistent visual style across different screen sizes
    • Support complex border designs with minimal memory usage

    How Nine-Slice Works

    A nine-slice texture divides an image into nine sections:

    ┌─────┬─────────────┬─────┐
    │  1  │      2      │  3  │  Top row
    ├─────┼─────────────┼─────┤
    │  4  │      5      │  6  │  Middle row
    ├─────┼─────────────┼─────┤
    │  7  │      8      │  9  │  Bottom row
    └─────┴─────────────┴─────┘
    
    • Corners (1, 3, 7, 9): Never scaled, maintain original size
    • Edges (2, 4, 6, 8): Scaled in one dimension only
    • Center (5): Scaled in both dimensions

    Setting Up Nine-Slice Textures

    Texture Requirements

    Create textures with clear slice boundaries:

    Button texture example (64x32 pixels):
    - Left border: 8 pixels
    - Right border: 8 pixels  
    - Top border: 8 pixels
    - Bottom border: 8 pixels
    - Center: 48x16 pixels (scalable area)
    

    NineSliceSprite Class

    The engine provides a NineSliceSprite class for handling nine-slice rendering:

    public class NineSliceSprite
    {
        public Texture2D Texture { get; set; }
        public Rectangle SourceRectangle { get; set; }
        public Padding Borders { get; set; }
        public Color Tint { get; set; }
        
        public NineSliceSprite(Texture2D texture, int left, int right, int top, int bottom)
        {
            Texture = texture;
            Borders = new Padding(left, right, top, bottom);
            SourceRectangle = new Rectangle(0, 0, texture.Width, texture.Height);
            Tint = Color.White;
        }
        
        public void Draw(SpriteBatch spriteBatch, Rectangle destinationRectangle)
        {
            DrawNineSlice(spriteBatch, destinationRectangle, Tint);
        }
    }
    

    UI Components with Nine-Slice Support

    NineSliceButton

    A button component that uses nine-slice textures for scalability:

    public class NineSliceButton : Button
    {
        private NineSliceSprite _normalSprite;
        private NineSliceSprite _hoverSprite;
        private NineSliceSprite _pressedSprite;
        private NineSliceSprite _disabledSprite;
        
        public void SetNineSliceTextures(
            Texture2D normal, 
            Texture2D hover, 
            Texture2D pressed, 
            Texture2D disabled,
            int borderLeft, int borderRight, int borderTop, int borderBottom)
        {
            _normalSprite = new NineSliceSprite(normal, borderLeft, borderRight, borderTop, borderBottom);
            _hoverSprite = new NineSliceSprite(hover, borderLeft, borderRight, borderTop, borderBottom);
            _pressedSprite = new NineSliceSprite(pressed, borderLeft, borderRight, borderTop, borderBottom);
            _disabledSprite = new NineSliceSprite(disabled, borderLeft, borderRight, borderTop, borderBottom);
        }
        
        public override void Draw(SpriteBatch spriteBatch)
        {
            var sprite = State switch
            {
                ButtonState.Normal => _normalSprite,
                ButtonState.Hover => _hoverSprite,
                ButtonState.Pressed => _pressedSprite,
                ButtonState.Disabled => _disabledSprite,
                _ => _normalSprite
            };
            
            sprite?.Draw(spriteBatch, GetAbsoluteBounds());
            
            // Draw text on top
            DrawText(spriteBatch);
        }
    }
    
    // Usage
    var button = new NineSliceButton
    {
        Text = "Scalable Button",
        Position = new Vector2(100, 100),
        Size = new Vector2(200, 50) // Can be any size
    };
    
    button.SetNineSliceTextures(
        assetRegistry.LoadAsset<Texture2D>("ui/button_normal.png"),
        assetRegistry.LoadAsset<Texture2D>("ui/button_hover.png"),
        assetRegistry.LoadAsset<Texture2D>("ui/button_pressed.png"),
        assetRegistry.LoadAsset<Texture2D>("ui/button_disabled.png"),
        8, 8, 8, 8 // Border sizes
    );
    

    NineSlicePanel

    A panel component with nine-slice background:

    public class NineSlicePanel : Panel
    {
        private NineSliceSprite _backgroundSprite;
        private NineSliceSprite _borderSprite;
        
        public void SetNineSliceBackground(Texture2D texture, int borderLeft, int borderRight, int borderTop, int borderBottom)
        {
            _backgroundSprite = new NineSliceSprite(texture, borderLeft, borderRight, borderTop, borderBottom);
        }
        
        public void SetNineSliceBorder(Texture2D texture, int borderLeft, int borderRight, int borderTop, int borderBottom)
        {
            _borderSprite = new NineSliceSprite(texture, borderLeft, borderRight, borderTop, borderBottom);
        }
        
        public override void Draw(SpriteBatch spriteBatch)
        {
            var bounds = GetAbsoluteBounds();
            
            // Draw background
            _backgroundSprite?.Draw(spriteBatch, bounds);
            
            // Draw border
            _borderSprite?.Draw(spriteBatch, bounds);
            
            base.Draw(spriteBatch);
        }
    }
    
    // Usage
    var dialogPanel = new NineSlicePanel
    {
        Position = new Vector2(200, 150),
        Size = new Vector2(400, 300)
    };
    
    dialogPanel.SetNineSliceBackground(
        assetRegistry.LoadAsset<Texture2D>("ui/dialog_background.png"),
        12, 12, 12, 12
    );
    

    Advanced Nine-Slice Techniques

    Animated Nine-Slice Components

    Create animated UI elements that scale properly:

    public class AnimatedNineSliceButton : NineSliceButton
    {
        private float _pulseTimer;
        private Vector2 _originalSize;
        private bool _isPulsing;
        
        public void StartPulseAnimation(float intensity = 0.1f, float speed = 2f)
        {
            _originalSize = Size;
            _isPulsing = true;
            _pulseIntensity = intensity;
            _pulseSpeed = speed;
        }
        
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            
            if (_isPulsing)
            {
                _pulseTimer += (float)gameTime.ElapsedGameTime.TotalSeconds * _pulseSpeed;
                
                var scale = 1f + Mathf.Sin(_pulseTimer) * _pulseIntensity;
                Size = _originalSize * scale;
            }
        }
    }
    

    Multi-State Nine-Slice Components

    Handle complex UI states with multiple nine-slice textures:

    public class ProgressBarNineSlice : ProgressBar
    {
        private NineSliceSprite _backgroundSprite;
        private NineSliceSprite _fillSprite;
        private NineSliceSprite _overlaySprite;
        
        public void SetNineSliceTextures(
            Texture2D background,
            Texture2D fill,
            Texture2D overlay,
            int borderSize)
        {
            _backgroundSprite = new NineSliceSprite(background, borderSize, borderSize, borderSize, borderSize);
            _fillSprite = new NineSliceSprite(fill, borderSize, borderSize, borderSize, borderSize);
            _overlaySprite = new NineSliceSprite(overlay, borderSize, borderSize, borderSize, borderSize);
        }
        
        public override void Draw(SpriteBatch spriteBatch)
        {
            var bounds = GetAbsoluteBounds();
            
            // Draw background
            _backgroundSprite?.Draw(spriteBatch, bounds);
            
            // Draw fill based on progress
            var fillWidth = (int)(bounds.Width * GetProgressPercentage());
            var fillBounds = new Rectangle(bounds.X, bounds.Y, fillWidth, bounds.Height);
            
            if (fillWidth > 0)
            {
                _fillSprite?.Draw(spriteBatch, fillBounds);
            }
            
            // Draw overlay
            _overlaySprite?.Draw(spriteBatch, bounds);
            
            base.Draw(spriteBatch);
        }
    }
    

    Custom Nine-Slice Shapes

    Create non-rectangular nine-slice elements:

    public class RoundedNineSlicePanel : UIElement
    {
        private NineSliceSprite _sprite;
        private float _cornerRadius;
        
        public float CornerRadius
        {
            get => _cornerRadius;
            set
            {
                _cornerRadius = value;
                UpdateNineSliceBorders();
            }
        }
        
        private void UpdateNineSliceBorders()
        {
            var borderSize = (int)Math.Ceiling(_cornerRadius);
            if (_sprite != null)
            {
                _sprite.Borders = new Padding(borderSize, borderSize, borderSize, borderSize);
            }
        }
        
        public void SetRoundedTexture(Texture2D texture, float cornerRadius)
        {
            _cornerRadius = cornerRadius;
            var borderSize = (int)Math.Ceiling(cornerRadius);
            _sprite = new NineSliceSprite(texture, borderSize, borderSize, borderSize, borderSize);
        }
        
        public override void Draw(SpriteBatch spriteBatch)
        {
            _sprite?.Draw(spriteBatch, GetAbsoluteBounds());
            base.Draw(spriteBatch);
        }
    }
    

    Creating Nine-Slice Textures

    Design Guidelines

    When creating nine-slice textures, follow these guidelines:

    1. Clear Border Definition

      - Make border areas visually distinct
      - Ensure corner elements don't have scalable content
      - Keep border thickness consistent
      
    2. Scalable Center

      - Design center area to tile seamlessly
      - Avoid directional elements in the center
      - Use patterns or solid colors that scale well
      
    3. Edge Considerations

      - Top/bottom edges should tile horizontally
      - Left/right edges should tile vertically
      - Maintain visual consistency when tiled
      

    Texture Creation Tool

    Create a utility to help generate nine-slice textures:

    public static class NineSliceTextureGenerator
    {
        public static Texture2D CreateRoundedRectangle(
            GraphicsDevice graphicsDevice,
            int width, int height,
            int cornerRadius,
            Color fillColor,
            Color borderColor,
            int borderThickness = 1)
        {
            var texture = new Texture2D(graphicsDevice, width, height);
            var data = new Color[width * height];
            
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    var color = GetPixelColor(x, y, width, height, cornerRadius, fillColor, borderColor, borderThickness);
                    data[y * width + x] = color;
                }
            }
            
            texture.SetData(data);
            return texture;
        }
        
        private static Color GetPixelColor(int x, int y, int width, int height, 
            int cornerRadius, Color fillColor, Color borderColor, int borderThickness)
        {
            // Implement rounded rectangle pixel generation logic
            // Return appropriate color based on position
            
            var isInBorder = x < borderThickness || x >= width - borderThickness ||
                            y < borderThickness || y >= height - borderThickness;
            
            var distanceFromCorner = GetDistanceFromNearestCorner(x, y, width, height, cornerRadius);
            
            if (distanceFromCorner <= cornerRadius)
            {
                if (distanceFromCorner >= cornerRadius - borderThickness)
                    return borderColor;
                else
                    return fillColor;
            }
            
            return isInBorder ? borderColor : fillColor;
        }
    }
    

    Performance Optimization

    Batch Rendering

    Optimize nine-slice rendering with batching:

    public class NineSliceBatchRenderer
    {
        private readonly List<NineSliceDrawCall> _drawCalls;
        private readonly Dictionary<Texture2D, List<NineSliceDrawCall>> _batchedCalls;
        
        public void AddDrawCall(NineSliceSprite sprite, Rectangle destination)
        {
            var drawCall = new NineSliceDrawCall
            {
                Sprite = sprite,
                Destination = destination
            };
            
            _drawCalls.Add(drawCall);
        }
        
        public void FlushBatch(SpriteBatch spriteBatch)
        {
            // Group draw calls by texture
            _batchedCalls.Clear();
            
            foreach (var call in _drawCalls)
            {
                if (!_batchedCalls.ContainsKey(call.Sprite.Texture))
                    _batchedCalls[call.Sprite.Texture] = new List<NineSliceDrawCall>();
                
                _batchedCalls[call.Sprite.Texture].Add(call);
            }
            
            // Render batches
            foreach (var batch in _batchedCalls)
            {
                RenderBatch(spriteBatch, batch.Key, batch.Value);
            }
            
            _drawCalls.Clear();
        }
        
        private void RenderBatch(SpriteBatch spriteBatch, Texture2D texture, List<NineSliceDrawCall> calls)
        {
            // Optimize rendering of multiple nine-slice sprites with the same texture
            foreach (var call in calls)
            {
                call.Sprite.Draw(spriteBatch, call.Destination);
            }
        }
    }
    

    Texture Atlasing

    Combine multiple nine-slice textures into texture atlases:

    public class NineSliceAtlas
    {
        public Texture2D AtlasTexture { get; private set; }
        public Dictionary<string, NineSliceInfo> NineSlices { get; private set; }
        
        public class NineSliceInfo
        {
            public Rectangle SourceRectangle { get; set; }
            public Padding Borders { get; set; }
        }
        
        public NineSliceSprite GetNineSlice(string name)
        {
            if (NineSlices.TryGetValue(name, out var info))
            {
                return new NineSliceSprite(AtlasTexture, 
                    info.Borders.Left, info.Borders.Right, 
                    info.Borders.Top, info.Borders.Bottom)
                {
                    SourceRectangle = info.SourceRectangle
                };
            }
            
            return null;
        }
    }
    
    // Usage
    var atlas = LoadNineSliceAtlas("ui/ui_atlas.json");
    var button = new NineSliceButton();
    
    button.SetNineSliceSprite(
        atlas.GetNineSlice("button_normal"),
        atlas.GetNineSlice("button_hover"),
        atlas.GetNineSlice("button_pressed")
    );
    

    Common Use Cases

    Dialog Boxes

    Create scalable dialog boxes with nine-slice textures:

    public class DialogBox : NineSlicePanel
    {
        private Label _titleLabel;
        private Label _contentLabel;
        private List<Button> _buttons;
        
        public DialogBox(string title, string content)
        {
            SetNineSliceBackground(
                AssetRegistry.LoadAsset<Texture2D>("ui/dialog_bg.png"),
                16, 16, 16, 16
            );
            
            CreateComponents(title, content);
            LayoutComponents();
        }
        
        public void AddButton(string text, Action onClick)
        {
            var button = new NineSliceButton
            {
                Text = text,
                OnClick = onClick
            };
            
            button.SetNineSliceTextures(
                AssetRegistry.LoadAsset<Texture2D>("ui/button_normal.png"),
                AssetRegistry.LoadAsset<Texture2D>("ui/button_hover.png"),
                AssetRegistry.LoadAsset<Texture2D>("ui/button_pressed.png"),
                null,
                8, 8, 8, 8
            );
            
            _buttons.Add(button);
            AddChild(button);
            
            LayoutComponents();
        }
    }
    

    Inventory Slots

    Create consistent inventory slot appearances:

    public class InventorySlot : NineSliceButton
    {
        private Texture2D _itemTexture;
        private int _quantity;
        
        public InventorySlot()
        {
            Size = new Vector2(50, 50);
            
            SetNineSliceTextures(
                AssetRegistry.LoadAsset<Texture2D>("ui/slot_empty.png"),
                AssetRegistry.LoadAsset<Texture2D>("ui/slot_hover.png"),
                AssetRegistry.LoadAsset<Texture2D>("ui/slot_selected.png"),
                null,
                4, 4, 4, 4
            );
        }
        
        public void SetItem(Texture2D itemTexture, int quantity)
        {
            _itemTexture = itemTexture;
            _quantity = quantity;
        }
        
        public override void Draw(SpriteBatch spriteBatch)
        {
            base.Draw(spriteBatch); // Draw nine-slice background
            
            if (_itemTexture != null)
            {
                var itemBounds = GetContentBounds();
                spriteBatch.Draw(_itemTexture, itemBounds, Color.White);
                
                if (_quantity > 1)
                {
                    DrawQuantityText(spriteBatch);
                }
            }
        }
    }
    

    Health and Mana Bars

    Create scalable status bars:

    public class StatusBar : UIElement
    {
        private NineSliceSprite _backgroundSprite;
        private NineSliceSprite _fillSprite;
        private float _currentValue;
        private float _maxValue;
        
        public StatusBar(string backgroundTexture, string fillTexture)
        {
            _backgroundSprite = new NineSliceSprite(
                AssetRegistry.LoadAsset<Texture2D>(backgroundTexture),
                4, 4, 4, 4
            );
            
            _fillSprite = new NineSliceSprite(
                AssetRegistry.LoadAsset<Texture2D>(fillTexture),
                4, 4, 4, 4
            );
        }
        
        public void SetValue(float current, float max)
        {
            _currentValue = current;
            _maxValue = max;
        }
        
        public override void Draw(SpriteBatch spriteBatch)
        {
            var bounds = GetAbsoluteBounds();
            
            // Draw background
            _backgroundSprite.Draw(spriteBatch, bounds);
            
            // Draw fill based on current value
            if (_maxValue > 0)
            {
                var fillPercentage = _currentValue / _maxValue;
                var fillWidth = (int)(bounds.Width * fillPercentage);
                var fillBounds = new Rectangle(bounds.X, bounds.Y, fillWidth, bounds.Height);
                
                if (fillWidth > 0)
                {
                    _fillSprite.Draw(spriteBatch, fillBounds);
                }
            }
        }
    }
    

    Best Practices

    Design Practices

    1. Consistent Border Sizes

      • Use the same border thickness across related UI elements
      • Document border sizes for your UI style guide
      • Consider different screen DPIs when setting border sizes
    2. Texture Optimization

      • Keep nine-slice textures as small as possible
      • Use power-of-two dimensions when possible
      • Consider compression settings for different platforms
    3. Visual Consistency

      • Maintain consistent corner radius across similar elements
      • Use the same color palette for related UI components
      • Ensure borders and fills complement each other

    Performance Practices

    1. Texture Reuse

      • Share nine-slice textures between similar components
      • Use texture atlases to reduce draw calls
      • Cache NineSliceSprite instances when possible
    2. Rendering Optimization

      • Batch nine-slice draws by texture
      • Avoid unnecessary texture switches
      • Use appropriate blend states and sampler states
    3. Memory Management

      • Dispose of unused nine-slice textures
      • Monitor texture memory usage
      • Consider streaming for large UI texture sets

    Nine-slice textures provide a powerful way to create scalable, professional-looking UI elements that maintain their visual quality across different screen sizes and resolutions in your Popcorning Guinea Pig games.

    • Edit this page
    In this article
    Back to top © 2024 Popcorning Guinea Pig Game Engine. A custom C# game engine for indie developers.