Way to evenly spawn X sprites within a 2D area so they scale to fit depending on screen size.

Hello,

So basically in my 2D game, I have an area on my screen where I want to spawn a bunch of ball sprites evenly, say 8 rows with each row containing 6 balls with a gap between each of them of X.

The idea is that I can change the number of rows, or balls per row, or the pixel gap between the balls to anything I want and it’ll just resize the balls to make them fit perfectly in the area. The reason for this is so that the balls will resize themselves properly depending on the size/aspect ratio of the screen.

Currently it looks like this (the darker area where the balls are, should be the area they are resized to):

I understand the conflict with the width and height in that with the orbs being circles, it likely wont math out evenly however I will settle for just them scaling properly for the width as I can try and adjust the UI to clip any extra room at the top.

In the code, what I basically do is calculate the width and height of the game area (taken from the transform.position.x & y’s of two empty objects anchord to the bottom left and top right of the game area).

With this width I minus the number of gaps between balls * the size of the gaps (6 balls = 7 gaps). This gives me the amount of space I’m able to use out of the width of the screen for the sake of ball size.

So I then take this number and divide it by the number of balls in the row.

This final number should give me the sprite size of the balls (it actually gives me 56% of the sprite size as the transform positions don’t 1:1 the sprite size for some reason) so that if each ball in the row was that size + the size of all the gaps, it would equal the width of the screen. I’m doing the same thing with the height but I’ve been focusing mostly on just trying to get the width to work.

This is the code that spawns the balls (orbs in the script):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class OrbPositions : MonoBehaviour
{
    public GameObject orbParent;
    public GameObject orbPrefab;

    public Transform screenTopRight; // An empty object anchored to the top right of the game area
    public Transform screenBottomLeft; // An empty object anchored to the bottom left of the game area

    public int minX; // The minumum transform.position.x of the game area.
    public int maxX; // The maximum transform.position.x of the game area.
    public int minY; // The minumum transform.position.y of the game area.
    public int maxY; // The maximum transform.position.y of the game area.

    public float screenWidth; // The gap between the min and max transform.position.x's of the game area.
    public float screenHeight; // The gap between the min and max transform.position.y's of the game area.

    public Transform orbSpawnPoint; // An empty object used as transform.position spawn point.
    private float startingX; // The starting transform.positon.x of the orb spawn point.
    private float startingY; // The starting transform.positon.y of the orb spawn point.
    //private float currentX; 
    //private float currentY; 

    public int rowsToSpawn;
    public int orbsPerRow;

    public float pixelGap = 31; // The number of pixels to put between each orb.

    public float orbXSpacing; // The transform.position.x spacing to leave between each orb.
    public float orbYSpacing; // The transform.position.y spacing to leave between each orb.


    public int spriteSize = 300; // Size of the orb sprite.

    void Start()
    {
        screenTopRight.GetComponent<Image>().enabled = false;
        screenBottomLeft.GetComponent<Image>().enabled = false;

        // Fetches the boundries of the game area in transform.position.
        minX = (int)screenBottomLeft.transform.position.x;
        maxX = (int)screenTopRight.transform.position.x;
        minY = (int)screenBottomLeft.transform.position.y;
        maxY = (int)screenTopRight.transform.position.y;

        // Calculates the width and height of the game area based on the boundries.
        screenWidth = Mathf.Abs(minX - maxX);
        screenHeight = Mathf.Abs(minY - maxY);

        // Some Drawlines to make sure the width and height are working properly.
        Debug.DrawLine(new Vector3(minX, minY, 0), new Vector3(minX, maxY, 0), Color.red, 1000f, false);
        Debug.DrawLine(new Vector3(minX, minY, 0), new Vector3(maxX, maxY, 0), Color.green, 1000f, false);
        Debug.DrawLine(new Vector3(minX, minY, 0), new Vector3(maxX, minY, 0), Color.blue, 1000f, false);




        print("The game width is: " + (int)screenWidth);

        // Now I take the number of gaps * the size of the gaps, and minus it from the screen width.
        int widthMinusGaps = (int)screenWidth - ((int)pixelGap * (orbsPerRow + 1));
        print("Screen width minus the total pixel gaps (" + ((int)pixelGap * (orbsPerRow + 1)) + ") is: " + widthMinusGaps);

        // Now I take what is left over, and divide it by the number of orbs in the row.
        int remainderDevided = widthMinusGaps / orbsPerRow;
        print("The reaminder divided by the number of orbs in the row is: " + remainderDevided);

        // The remainder divided should be 56% of what the sprite size of the orb should be set as. 
        print("Sprite size should be: " + (int)(remainderDevided / .56f));
        spriteSize = (int)(remainderDevided / .56f);





        // Ignore this //orbXSpacing = ((screenWidth - ((orbsPerRow + 1) * pixelGap)) / orbsPerRow) + pixelGap + (spriteSize / 2);



        orbXSpacing = pixelGap + spriteSize;
        orbYSpacing = pixelGap + spriteSize;

        startingX = screenBottomLeft.transform.localPosition.x + ((spriteSize / 2)) + pixelGap;
        startingY = screenBottomLeft.transform.localPosition.y + (spriteSize / 2) + pixelGap;

        orbSpawnPoint.localPosition = new Vector3(startingX, startingY, 0);

        SpawnOrbs();

    }

    private void SpawnOrbs()
    {
        ResetPositions();

        for (int rts = 1; rts < rowsToSpawn + 1; rts++) // For rows to spawn (rts)
        {
            for (int opr = 1; opr < orbsPerRow + 1; opr++) // For orbs per row (opr)
            {
                GameObject newOrb = Instantiate(orbPrefab, orbParent.transform);
                newOrb.name = "Orb Point. Row: " + rts + ". Column: " + opr + ".";
                //newOrb.transform.localPosition = new Vector3(currentX, currentY, 0);
                newOrb.transform.position = orbSpawnPoint.position;

                newOrb.GetComponent<RectTransform>().sizeDelta = new Vector2(spriteSize, spriteSize);

                Orb newOrbScript = newOrb.GetComponent<Orb>();
                newOrbScript.colourManager = GetComponent<ColourManager>();
                newOrbScript.FakeStart();

                orbSpawnPoint.localPosition = new Vector3(orbSpawnPoint.localPosition.x + orbXSpacing, orbSpawnPoint.localPosition.y, orbSpawnPoint.localPosition.z);
            }

            orbSpawnPoint.localPosition = new Vector3(startingX, orbSpawnPoint.localPosition.y, orbSpawnPoint.localPosition.z);
            orbSpawnPoint.localPosition = new Vector3(orbSpawnPoint.localPosition.x, orbSpawnPoint.localPosition.y + orbYSpacing, orbSpawnPoint.localPosition.z);
            //currentY += orbYSpacing;

        }

    }

    private void ResetPositions()
    {
        orbSpawnPoint.localPosition = new Vector3(startingX, startingY, 0);
    }
}

I hope there’s a horizontal scroll to that code because the way it’s thrown half the comments to second lines makes it rather unreadable.

Anyway I’ve tried a good while to fix this and just can’t seem to get it to work. If anyone has any ideas I would appreciate it. My ability to translate between transform.positions and sprite sizes seems to have failed. My maths is probably down there with it though.

Thanks!

I managed to fix this on my own.

Basically instead of calculating things from the transform.position of the boundry defining objects that are anchored to parts of the UI, I reparented them both (the objects I anchor to get the bottom left and top right of the game area) to the orb parent in the Start function.

This meant that everything to do with calculating the sprite size is now using the same object’s X & Y localPosition coordinates - so I don’t need to do silly math to calculate the sprite size based on the divided remainder of the X or Y axis.

I also just 9 sliced the sprites and calculated the width and height of them seperately (which I was always going to do after solving the evenly spreading width issue).

Of course it still looks a little wonky but that’s because the sprites I’m currently using are perfect circles:

I can now give any number for rows to spawn, orbs per row, or pixel gap and they’ll all fit within the boundries regardless of what resolution or aspect ratio I’m using. Here’s the code:

`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class OrbPositions : MonoBehaviour
{
[Header(“Customisable Fields”)]
public int rowsToSpawn;
public int orbsPerRow;
public float pixelGap = 31; // The number of pixels to put between each orb. Game border size is 31.
[Space]

[Header("Setup")]
public GameObject orbParent;
public GameObject orbPrefab;
public Transform orbSpawnPoint; // An empty object used as transform.position spawn point.

public Transform screenTopRight; // An empty object anchored to the top right of the game area
public Transform screenBottomLeft; // An empty object anchored to the bottom left of the game area

// Private Setup
private int minX; // The minumum transform.position.x of the game area.
private int maxX; // The maximum transform.position.x of the game area.
private int minY; // The minumum transform.position.y of the game area.
private int maxY; // The maximum transform.position.y of the game area.

private float screenWidth; // The gap between the min and max transform.position.x's of the game area.
private float screenHeight; // The gap between the min and max transform.position.y's of the game area.

private float startingX; // The starting transform.positon.x of the orb spawn point.
private float startingY; // The starting transform.positon.y of the orb spawn point.

private float orbXSpacing; // The transform.position.x spacing to leave between each orb.
private float orbYSpacing; // The transform.position.y spacing to leave between each orb.

private int spriteWidth; // Width of the orb sprite.
private int spriteHeight; // Height of the orb sprite.

void Start()
{
    CalculateScreenSize();

    CalculateSpriteSize();

    CalculateAxisSpacing();

    SetStartingOrbPosition();

    SpawnOrbs();
}

private void CalculateScreenSize()
{
    // Disables the editor help images and reparents both boundry defining objects to the orb parent.
    screenTopRight.GetComponent<Image>().enabled = false;
    screenBottomLeft.GetComponent<Image>().enabled = false;
    screenTopRight.transform.parent = orbParent.transform;
    screenBottomLeft.transform.parent = orbParent.transform;

    // Finds the boundries of the game area from the two boundry defining objects.
    minX = (int)screenBottomLeft.transform.localPosition.x;
    maxX = (int)screenTopRight.transform.localPosition.x;
    minY = (int)screenBottomLeft.transform.localPosition.y;
    maxY = (int)screenTopRight.transform.localPosition.y;

    // Calculates the width and height of the game area based on the boundries.
    screenWidth = Mathf.Abs(minX - maxX);
    screenHeight = Mathf.Abs(minY - maxY);
}

private void CalculateSpriteSize()
{
    // Calculating width and height of the orb sprites according to rowsToSpawn and number of orbsPerRow. Factors in total pixel gap.
    int widthMinusGaps = (int)screenWidth - ((int)pixelGap * (orbsPerRow + 1));
    int widthDivided = widthMinusGaps / orbsPerRow;
    spriteWidth = widthDivided;

    int heightMinusGaps = (int)screenHeight - ((int)pixelGap * (rowsToSpawn + 1));
    int heightDivided = heightMinusGaps / rowsToSpawn;
    spriteHeight = heightDivided;
}

private void CalculateAxisSpacing()
{
    // Sets the spacing to leave between orb spawns for both the X and Y axis.
    orbXSpacing = pixelGap + spriteWidth;
    orbYSpacing = pixelGap + spriteHeight;
}

private void SetStartingOrbPosition()
{
    // Defines the starting X and Y position of the first orb.
    startingX = (int)screenBottomLeft.transform.localPosition.x + ((spriteWidth / 2) + pixelGap);
    startingY = (int)screenBottomLeft.transform.localPosition.y + ((spriteHeight / 2) + pixelGap);

    orbSpawnPoint.localPosition = new Vector3(startingX, startingY, 0);
}

private void SpawnOrbs()
{
    for (int rts = 1; rts < rowsToSpawn + 1; rts++) // For rows to spawn (rts)
    {
        for (int opr = 1; opr < orbsPerRow + 1; opr++) // For orbs per row (opr)
        {
            SpawnSingleOrb(rts, opr);

            // Moves the spawn point forward one position on the X axis.
            orbSpawnPoint.localPosition = new Vector3(orbSpawnPoint.localPosition.x + orbXSpacing, orbSpawnPoint.localPosition.y, orbSpawnPoint.localPosition.z);
        }
        // Resets the X axis position to the first spot and moves the spawn point one point up in the Y axis.
        orbSpawnPoint.localPosition = new Vector3(startingX, orbSpawnPoint.localPosition.y + orbYSpacing, orbSpawnPoint.localPosition.z);
    }
}

private void SpawnSingleOrb(int row, int column)
{
    GameObject newOrb = Instantiate(orbPrefab, orbParent.transform);
    newOrb.name = "Orb Point. Row: " + row + ". Column: " + column + ".";
    newOrb.transform.localPosition = orbSpawnPoint.localPosition;

    newOrb.GetComponent<RectTransform>().sizeDelta = new Vector2(spriteWidth, spriteHeight);

    Orb newOrbScript = newOrb.GetComponent<Orb>();
    newOrbScript.colourManager = GetComponent<ColourManager>();
    newOrbScript.FakeStart();
}

}
`