I happened upon the following Reddit post asking for help regarding getting a random point in the area around a rectangle, as seen in the image below.
As getting a random point within a rectangle is simple, I wanted to treat the problem as having four rectangles bordering the zone from which no points were to be retrieved.
The side to get the random point in will be chosen randomly based on that rectangle’s area. The left and the right side will be slightly longer to handle the corners.
public Vector2 size; public float borderWidth; [NonSerialized] public List<Vector3> points = new List<Vector3>(); private Vector3 _leftBorderPosition; private Vector3 _rightBorderPosition; private Vector3 _bottomBorderPosition; private Vector3 _topBorderPosition; private float[] _sideChances = new float[4]; private Transform _transform;
The size variable is the size of the red rectangle, the area where we won’t randomize any points inside.
The points variable is a list of positions. We will collect all randomized points to visualize them in the editor.
We have four positions of the rectangles bordering the inner rectangle.
Finally, we have an array of floats. These will be the areas of the bordering rectangles used to randomize what side to retrieve a point from.
private void SetupBorderPositions() { Vector3 position = _transform.position; Vector2 halfSize = size * 0.5f; float halfBorderWidth = borderWidth * 0.5f; _leftBorderPosition = new Vector3(position.x - halfSize.x - halfBorderWidth, position.y, position.z); _rightBorderPosition = new Vector3(position.x + halfSize.x + halfBorderWidth, position.y, position.z); _bottomBorderPosition = new Vector3(position.x, position.y, position.z - halfSize.y - halfBorderWidth); _topBorderPosition = new Vector3(position.x, position.y, position.z + halfSize.y + halfBorderWidth); }
The SetupBorderPositions method will be called from the Awake unity event function calculates the positions of each rectangle. Here we need to use both the size and the border width to calculate the correct positions.
private void SetupSideChances() { float doubleBorderWidth = borderWidth * 2f; // Left _sideChances[0] = borderWidth * (size.y + doubleBorderWidth); // Right _sideChances[1] = _sideChances[0]; // Bottom _sideChances[2] = borderWidth * size.x; // Top _sideChances[3] = _sideChances[2]; }
The SetupSideChances method also called from the Awake function, calculates the area of each rectangle. This will be used in the weighted random to pick what rectangle we will randomize a point inside.
private int GetRandomSide() { float totalWeight = _sideChances[0] + _sideChances[1] + _sideChances[2] + _sideChances[3]; float randomNumber = Random.Range(0, totalWeight); for (int i = 0; i < _sideChances.Length; i++) { randomNumber -= _sideChances[i]; if (randomNumber < 0f) { return i; } } return 0; }
To randomize what rectangle to retrieve a point from, we implement a weighted random algorithm using the weights in _sideChances. This method will be called from the Update function.
private Vector3 GetRandomPoint(int side) { Vector2 halfSize = size * 0.5f; float halfBorderWidth = borderWidth * 0.5f; float x = default; float z = default; switch (GetRandomSide()) { case 0: // Left side x = Random.Range(_leftBorderPosition.x - halfBorderWidth, _leftBorderPosition.x + halfBorderWidth); z = Random.Range(_leftBorderPosition.z - halfSize.y - borderWidth, _leftBorderPosition.z + halfSize.y + borderWidth); break; case 1: // Right side x = Random.Range(_rightBorderPosition.x - halfBorderWidth, _rightBorderPosition.x + halfBorderWidth); z = Random.Range(_rightBorderPosition.z - halfSize.y - borderWidth, _rightBorderPosition.z + halfSize.y + borderWidth); break; case 2: // Bottom side x = Random.Range(_bottomBorderPosition.x - halfSize.x, _bottomBorderPosition.x + halfSize.x); z = Random.Range(_bottomBorderPosition.z - halfBorderWidth, _bottomBorderPosition.z + halfBorderWidth); break; case 3: // Top side x = Random.Range(_topBorderPosition.x - halfSize.x, _topBorderPosition.x + halfSize.x); z = Random.Range(_topBorderPosition.z + halfBorderWidth, _topBorderPosition.z - halfBorderWidth); break; } return new Vector3(x, 0f, z); }
Once we have a rectangle, we call the GetRandomPoint method.
Here we randomize an x and a z value depending on what side we have, considering the rectangle’s position.
We return the point, adding it to our list of positions.
private void Update() { if (!Input.GetKey(KeyCode.Space)) { return; } Vector3 randomPoint = GetRandomPoint(GetRandomSide()); points.Add(randomPoint); }
Finally, we have the Update function, where we put it all to use.
To visualize it all in the editor, we will write and implement a method using gizmos, make sure you turn it on in the game view to see it in play mode.
Make sure to put this script in a folder called Editor.
public class RectangleBordersSceneView { [DrawGizmo(GizmoType.NonSelected | GizmoType.Active)] static void DrawGizmoForMyScript(RectangleBorders source, GizmoType gizmoType) { Vector3 position = source.transform.position; Gizmos.color = new Color(1f, 0f, 0f, 0.25f); Gizmos.DrawCube(position, new Vector3(source.size.x, 0f, source.size.y)); Gizmos.color = new Color(0f, 1f, 0f, 0.25f);; Vector2 halfSize = source.size * 0.5f; // Draw four wire cubes for the borders float borderWidth = source.borderWidth; float halfBorderWidth = borderWidth * 0.5f; float doubleBorderWidth = borderWidth * 2f; Vector3 leftBorderPosition = new Vector3(position.x - halfSize.x - halfBorderWidth, position.y, position.z); Vector3 rightBorderPosition = new Vector3(position.x + halfSize.x + halfBorderWidth, position.y, position.z); Vector3 bottomBorderPosition = new Vector3(position.x, position.y, position.z - halfSize.y - halfBorderWidth); Vector3 topBorderPosition = new Vector3(position.x, position.y, position.z + halfSize.y + halfBorderWidth); Gizmos.DrawCube(leftBorderPosition, new Vector3(borderWidth, 0f, source.size.y + doubleBorderWidth)); Gizmos.DrawCube(rightBorderPosition, new Vector3(borderWidth, 0f, source.size.y + doubleBorderWidth)); Gizmos.DrawCube(bottomBorderPosition, new Vector3(source.size.x, 0f, borderWidth)); Gizmos.DrawCube(topBorderPosition, new Vector3(source.size.x, 0f, borderWidth)); Gizmos.color = Color.blue; foreach (Vector3 point in source.points) { Gizmos.DrawSphere(point, 0.1f); } Gizmos.color = Color.white; } }