- Home /
Instantiate object in random position on 'donut' shaped platform
Basically I want objects to periodically instantiate in random positions on this platform. This wasn't a problem, because I just used the built-in "Random.insideUnitCircle." However, now I have a circular platform with a hole in the middle, and I do not want it to instantiate over the hole. Even worse, later on I have a platform with two holes in it.
I have a basic concept down: I have to subtract the area of that hole from the area of the entire platform, then spawn it somewhere in that area...I'm just not really sure how to go about that with the code. I could do it using loops to just constantly put a random spot until it is not a spot in that hole, but there HAS to be some way to do this using a simple line of Geometry.
Thank you in advance :)
Answer by SirCrazyNugget · Mar 04, 2014 at 11:20 PM
For one hole: Spawn at the center of the hole then pick a range angle and move a random distance from the minimum requirement to the maximum requirement forward
var angle : float = Random.Range(0f, 360f);
var dist : float = Random.Range(1f, 3f);
transform.Rotate(Vector3(0, angle, 0));
transform.Translate(Vector3(dist, 0, dist));
For two holes: I'll have to dig out an old book...
That's scary when someone says they have to 'dig out an old book' for a solution haha. Thank you very much, this makes a lot of sense.
I like the way you think, for one hole this solution is the most elegant I can think of!
Answer by robertbu · Mar 04, 2014 at 11:06 PM
Solving it with math is fairly straight forward, but since you are going to have two holes (and I assume the holes will not be in the center), why not figure out a solution that works for that situation now. One common approach is to just keep generating random values until you get one that works.
So here is an example:
#pragma strict
var holeLocalPos = Vector3(0,0);
var holeRadius = 1.0;
var platformRadius = 3.0;
function FindPos() {
var pos : Vector3;
for (var i = 0; i < 1000; i++) {
pos = Random.insideUnitCircle * platformRadius;
if (Vector3.Distance(pos, holeLocalPos) > holeRadius) {
return pos;
}
}
return pos;
}
FindPos() will return a valid local position. You will need to either add transform.position to the point (non-rotated or scaled platform), or use Transform.TransformPoint() to find the world position to spawn your object.
When you get two holes, just add a bit more data and change the check to:
if ((Vector3.Distance(pos, holeLocalPos1) > holeRadius1) && (Vector3.Distance(pos, holeLocalPos2) > holeRadius2)) {
Note I use a for() loop that limits the tries. This prevents the code from hanging if I happen to code an impossible criteria. With the current code it would be difficult to hang, but imagine you add a new condition tomorrow that not only must it be on the ring, but the position must not overlap an existing object.
Thank you for the very thorough answer. I think I understand it, and I'll try to mess around with this.
This is kind of what I was already thinking though. I just feel like there's a simpler, more efficient way of doing this, through a geometric equation.
For one hole, there are a couple of different approaches. @SirCrazyNugget solution is a good one. Same thinking but different approach would be:
var localPos = Random.insideUnitCircle;
localPos = localPos.normalzied * RandomRange($$anonymous$$Radius, maxRadius);
'$$anonymous$$Radius' and 'maxRadius' would be the inside and outside of your disk.
And I can think of a couple more, but they don't solve your two hole problem.
Okay, yeah that makes a lot of sense. But yeah, I suppose for the two holes the only real way to do it is with a loop.
First off, I'm using C# ins$$anonymous$$d of JS. I've always had a little trouble understanding Return and exactly what it does. I'd want that looping thing to be in a Coroutine so it wouldn't freeze the game up (which clearly you did because of the Return in there). What would the call from the main code look like for this Coroutine? I guess what I'm trying to say, is where exactly is that Returned value of 'pos' returning to?
Here is a bit of C# code to put things in context:
using UnityEngine;
using System.Collections;
public class Bug25a : $$anonymous$$onoBehaviour
{
public GameObject prefab;
public Vector3 holeLocalPos = Vector3.zero;
public float holeRadius = 1.0f;
public float diskRadius = 2.5f;
void Start() {
InvokeRepeating("Spawner", 0.0f, 1.5f);
}
void Spawner() {
Vector3 pos = FindPos();
pos += transform.position;
Instantiate(prefab, pos, transform.rotation);
}
Vector3 FindPos() {
Vector3 pos = Vector3.zero;
for (var i = 0; i < 1000; i++) {
pos = Random.insideUnitCircle * diskRadius;
if (Vector3.Distance(pos, holeLocalPos) > holeRadius) {
return pos;
}
}
return pos;
}
}
This code uses InvokeRepeating() rather than a coroutine. I ran this code with a small sphere as a prefab and the InvokeRepeating set to 0.01. After a $$anonymous$$ute this is the result: