How does one make 3-slice scaling hexagonal sprites for UI and such?

Working on the UI art for my game, its hexagonal themed, and I’m struggling with it.

The idea is the hexagons have two parts, the core, which is the bulk of it, and an outline, a rim that will have varying thickness, they will also have a width, where we make the center wider, rotation, and also size.

Now the issues I’m facer are:
- The rim.
this rim is giving me trouble, notably adjusting its thickness, I tried using another bigger hexagon behind the first to be the rim, but when adjusting the size and with of it the rim thickness wasn’t regular. and please note I want the rim to be independent of scale, so if there’s two hexagons of different sizes with their thickness set to the same thing, the rims would appear the same thickness, not how ever many times different the scale is.
I’m thinking I’ll need like a vectorized image or something for it, but I’m not sure, any advice? (you can see what I’m looking for with rim thickness in the bottom right column of the image).
- The width.
So I want to adjust how wide it is, but not like increasing the scale along that axis, which would warp the hexagon, but just increasing how wide the middle of the hexagon is (shown in the second column), now from what I could find this is called “3-slice scaling”, but Unity doesn’t have that, but it does have “9-slice scaling”, and I tried to fake 3-slice scaling with it by putting the vertical dividers in various spots, but nothing worked.
Anyone have any advice of how to do this?

See the idea is I’d make something, a prefab or whatever, that would have a script with a collection of variables (notably so if it is like two images their variables can be adjusted together):
- Scale, how big the hexagon is (column one).
- Width, how wide it is (compare with column two).
- Rotation, the rotation of the hexagon (see column three and compare).
- Thickness, which is how thick the rim should be (column four).
- Core color, the color of the inner part (upper right).
- Rim color, the color of the outline rim thing (also upper right).
And then I’d take this prefab thing, and arrange it around the canvas, adjusting the variables to get what I want. and then the colors would also change in game (I already have code for that, it’s just the outline and width that’s giving me trouble), and it’d just work.
notably the reason I wanna make this special image instead of just making custom sprites for each UI element, is that the UI’s all have this hex theme (or simple squares and circles) so it will be very reusable for all the UI’s, and notably will allow for any changes to the design and layout to be done easily, where if I made a sprite for each bit I’d need to make new ones if I changed that bit, but with this I just adjust it.

Also in the center of the image you can see the idea for the UI, showing how these simple hexagons can make the shapes I want. compare with the top left of the image for how it will look without the overhang (yes I’m aware the angles are different from the sketch). but this does require the sprites to go off the visible part of the canvas, and I haven’t tested if the canvas allows that, so uh, does it allow you to have an image hanging way off it and only having part of the sprite be visible, or does it insist on keeping the whole thing in the canvas?

So yeah, how to get a sprite with 3-slice scaling, is that what I want, or would something else work?
And how to adjust the outline thickness, do I need a vectorized thing, and if so how? Or would something else work, and if so what, and how?
And tips, advice, or recommendations like learning resources or tools?
Please and thank you

I don’t think a hexagon is a good fit for 9-slice as you figured. Mainly because a hexagon is missing two corners, an octagon would work much better like this:

Creating your own 3-slice shouldn’t be that difficult if you can restrict scaling to one axis only. Perhaps there’s even a github library or store asset for this.

Take the bottom-left hexagon (flat top). Cut of left and right side, the middle section you can scale freely but you can’t scale vertically or else the left/right edges will be stretched or squeezed.

ok so, I found this post, and the script they posted works wonderfully for the three slice scaling I needed:

now there was slight issue of one of its methods is outdated, so that needed to be updated, but that was a quick fix, just swap them out, and a few adjustments, here’s the new script, updated from benzsuankularb’s code:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Sprites;
  
  
[ExecuteInEditMode]
public class Image3Slices : Graphic
{
    public Sprite sprite;
  
    public override Texture mainTexture
    {
        get { return sprite == null ? s_WhiteTexture : sprite.texture; }
    }
  
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        Vector2 corner1 = new Vector2(0f, 0f);
        Vector2 corner2 = new Vector2(1f, 1f);
  
        corner1.x -= rectTransform.pivot.x;
        corner1.y -= rectTransform.pivot.y;
        corner2.x -= rectTransform.pivot.x;
        corner2.y -= rectTransform.pivot.y;
  
        corner1.x *= rectTransform.rect.width;
        corner1.y *= rectTransform.rect.height;
        corner2.x *= rectTransform.rect.width;
        corner2.y *= rectTransform.rect.height;
  
        if (sprite == null)
        {
            throw new UnityException("No sprite");
        }
        else if (sprite.border == Vector4.zero)
        {
            throw new UnityException("No sprite border");
        }
  
        Vector4 uv = DataUtility.GetOuterUV(sprite);
  
        float cornerLeftSizeRatio = sprite.rect.height / sprite.border.x;
        float actualLeftCornerWidth = rectTransform.rect.height / cornerLeftSizeRatio;
        float actualLeftUVWidth = (uv.w - uv.y) / cornerLeftSizeRatio;
  
        float cornerRightSizeRatio = sprite.rect.height / sprite.border.z;
        float actualRightCornerWidth = rectTransform.rect.height / cornerRightSizeRatio;
        float actualRightUVWidth = (uv.w - uv.y) / cornerRightSizeRatio;
  
        vh.Clear(); // Clear previous mesh data
  
        vh.AddVert(new Vector3(corner1.x, corner2.y), color, new Vector2(uv.x, uv.w));
        vh.AddVert(new Vector3(corner1.x, corner1.y), color, new Vector2(uv.x, uv.y));
  
        vh.AddVert(new Vector3(corner1.x + actualLeftCornerWidth, corner2.y), color, new Vector2(uv.x + actualLeftUVWidth, uv.w));
        vh.AddVert(new Vector3(corner1.x + actualLeftCornerWidth, corner1.y), color, new Vector2(uv.x + actualLeftUVWidth, uv.y));
  
        vh.AddVert(new Vector3(corner2.x - actualRightCornerWidth, corner2.y), color, new Vector2(uv.z - actualRightUVWidth, uv.w));
        vh.AddVert(new Vector3(corner2.x - actualRightCornerWidth, corner1.y), color, new Vector2(uv.z - actualRightUVWidth, uv.y));
  
        vh.AddVert(new Vector3(corner2.x, corner2.y), color, new Vector2(uv.z, uv.w));
        vh.AddVert(new Vector3(corner2.x, corner1.y), color, new Vector2(uv.z, uv.y));
  
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(1, 2, 3);
        vh.AddTriangle(2, 3, 4);
        vh.AddTriangle(3, 4, 5);
        vh.AddTriangle(4, 5, 6);
        vh.AddTriangle(5, 6, 7);
    }
}

so that covers the 3-slice scaling, just set up the sprite like you would for 9-slice scaling in the sprite editor, but you can ignore the horizontal lines for vertically dividing the image.

now for the controlling of the images for my hexagonal image with a rim, and making the prefab.
so its quite simple, take an empty, attach this script below (not the one above) to it, and then parent two images to it, I just did create > UI > raw image, then deleted the rim image component and added that component we updated to it (the script above), and then set the image ref to the hexagon sprite we made, now you’ll need to of these kids, so duplicate it, and set the rim one as the first child, and the core as the second child, set the refs, and then you have your prefab.
so then you can take that prefab, add it to your canvas, and adjust the variables, and boom, we have size, width, thickness, rotation, and color control, complete with methods we can call to change the colors to then get tied into that system.
here’s the script that allows for the easy manipulation of the image that goes on that parent object:

using UnityEngine;
using UnityEngine.UI;
  
public class HexImageController : MonoBehaviour
{
    public RectTransform hexBase;
    public RectTransform hexRim;
  
    public Image3Slices hexBaseImage;
    public Image3Slices hexRimImage;
  
    [Header("Hex Settings")]
    [SerializeField] private float scale = 1f;       // Overall scale
    [SerializeField] private float thickness = 0.1f; // Rim size
    [SerializeField] private float width = 100f;     // Base width
    [SerializeField] private float rotation = 0f;
    public Color baseColor = Color.white;
    public Color rimColor = Color.black;
    public bool showRim = true;
    
    //there's a static so they stay in sync, so that if you wanna copy ones width you just need to paste in that one value, no need to adjust sensativity.
    //but I recommend setting sensativty to something that works before you do other things as if you change it eveerythings size changes
    public float widthSens = 1f;
    static float widthSensativity = 1f;
  
    private void Start()
    {
        ApplySettings();
    }
  
    private void OnValidate() // Updates when values change in the Inspector
    {
        ApplySettings();
    }
  
    private void ApplySettings()
    {
        if (!hexBase || !hexRim) return;
  
        // Clamp values to prevent negatives (minimum of 0.1)
        scale = Mathf.Max(scale, 0.1f);
        thickness = Mathf.Max(thickness, 0.1f);
        width = Mathf.Max(width, 0.1f);
  
        // make sure scale is normal and constant
        hexBase.localScale = Vector3.one;// * (scale);
        hexRim.localScale = Vector3.one;// * ((scale) + (thickness)); // Rim is scaled larger
  
        // Adjust width (affects both images)
        hexBase.sizeDelta = new Vector2((width * widthSensativity) + scale, scale);
        hexRim.sizeDelta = new Vector2((width * widthSensativity) + scale + thickness, scale + thickness);
  
        // Set colors
        if (hexBaseImage) hexBaseImage.color = baseColor;
        if (hexRimImage) hexRimImage.color = rimColor;
  
        hexBase.localRotation = Quaternion.Euler(0, 0, rotation);
        hexRim.localRotation = Quaternion.Euler(0, 0, rotation);
  
        widthSensativity = widthSens;
  
        hexRimImage.gameObject.SetActive(showRim);
    }
  
    public void setRimColor(Color RimColor, bool RimVisible = true)
    {
        hexRimImage.color = RimColor;
        hexRimImage.gameObject.SetActive(RimVisible);
    }
    public void setCoreColor(Color CoreColor)
    {
        hexBaseImage.color = CoreColor;
    }
}

so to anyone who is also trying to do such a thing, here ya go, 3-slice scaling, and an outline with other controls like color and rotation, all you need it your image, mines a white (so that we can assign any color) hexagon with alpha values (so that its actually a hexagon lol as its still technically square)

1 Like

ok so, that static width sensitivity thing doesn’t work, it keeps getting reset as it doesn’t persist/actually change the data, so uh, just find a value you like (I found 5 works well) and replace it with that.