- Home /
How to Detect if a Collider is in the Area, Move Away, Rinse and Repeat?
Hello, everyone. In my 2D game, since I couldn't figure out how to prevent them from originally spawning on top of each other, a few random platforms are generated within a certain area by an empty gameobject. Once they spawn in, I want them to detect if there are other platforms within a certain distance using Physics2D.OverlapCircleAll(transform.position, radius);. The platforms have edge colliders. If they detect one or more other platforms within the circle, they will teleport a random position, rinse and repeat. However, instead of doing what I meant to happen, they just teleport all over the screen or don't do anything after spawning in, regardless to whether they're touching another platform or not. I don't understand why. Here is the code attached to the platform prefab:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlatformCfg : MonoBehaviour {
bool canStayHere = true;
float height = 2;
float radius = 2;
float width = 2;
Collider2D[] colliders;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update()
{
CheckForPlatforms();
if (!canStayHere)
{
Displace();
}
}
void CheckForPlatforms()
{
colliders = Physics2D.OverlapCircleAll(transform.position, radius);
if (colliders.Length >= 1)
{
canStayHere = false;
Array.Clear(colliders, 0, colliders.Length);
}
else canStayHere = true;
}
void Displace()
{
float randX = UnityEngine.Random.Range(transform.position.x - width, transform.position.x + width);
float randY = UnityEngine.Random.Range(transform.position.y - height, transform.position.y + height);
transform.position = new Vector2(randX, randY);
}
}
If anyone can tell me why this happens and how to fix this, I would be thankful. If you have a better solution to prevent the platforms from spawning too close to each other, please tell me! Thank you.
Answer by Ermiq · Sep 01, 2018 at 01:43 PM
You should somehow prevent the CheckFoPlatforms()
execution on the platforms which are already placed. Because, as it is now, both platforms (that one which was at the place and the new one) will jump from this place 'cause they both detect that they can't stay here.
Also, you should add some check to be sure the platform can't detect itself. I would do this like that:
colliders = Physics2D.OverlapCircleAll(transform.position, radius);
canStayHere = true; // if something will be detected in next code,
// then it will be 'false', otherwise it'll stay 'true'
foreach (Collider col in colliders) {
if (col.transform //just to be sure that collided object has an actual transform
&& col.transform != transform) // check if it's not this platform itself
canStayHere = false;
Array.Clear(colliders, 0, colliders.Length);
//forcibly exit the foreach statement as we already know what we need
break;
}
}
UPDATE:
To prevent unnessesary checks I would make the platforms instantiation code as a coroutine, so every new platform would be instantiated in new frame, and not all at once.
Don't know how exactly you instantiate the platform prefabs in the scene, so I just assume that you use some array or a number of places which determines how much platforms should be instantiated:
private IEnumerable SpawnPlatforms (int quantity) {
for (int i = 0; i < quantity; i++) {
// instantiate one of the platforms here
// and take a pause until the next 'Update()'
yield return new WaitForEndOfFrame();
}
}
private SomeMethod () {
// that's how you start the coroutine
StartCoroutine (SpawnPlatforms (5));
}
Now, the platforms could be placed in some order, one by one, frame by frame. And there's no risk of detecting each other, no mess, everything is ordered.
Lets go further and make it so platform could be placed and will not be displaced when another platform instantiated within the circle.
To do this, you should make so this another platform checks the place and doesn't take the place unless it's sure that the place is not occupied.
So, CheckForPlatforms()
should be modified, so it takes some position, checks it and returns true
if the platform can go there.
bool CheckForPlatforms (Vector2 positionToCheck)
{
colliders = Physics2D.OverlapCircleAll (positionToCheck, radius);
if (colliders.Length >= 1)
{
// if the 'positionToCheck' is already occupied by some platform
return false;
}
else return true;
}
Also, makes it so the platform checks it's current place in its Start()
so it could check if the spawn point is occupied. And if it's occupied, try to find new place to jump there:
void Start () {
if (!CheckForPlatforms (transform.position)) // if spawn point is occupied already
{
Displace (transform.position);
}
// otherwise, if the spawn point is not occupied, the platform will stay here and never go away
// because the 'Start()' is called only once when the platform is instantiated,
// so the platform won't check anything in the future.
}
void Displace(Vector2 currentPosition)
{
// some random position relative to 'currentPosition' parameter
Vector2 randomPos;
float randX = UnityEngine.Random.Range (currentPosition.x - width, currentPosition.x + width);
float randY = UnityEngine.Random.Range (currentPosition.y - height, currentPosition.y + height);
randomPos = new Vector2 (randX, randY));
// check this random position
if (CheckForPlatforms (randomPos) { // if it's not occupied
// take this place
transform.position = randomPos;
}
// otherwise, try to find another place relative to this randomPos
// since it's occupied, so the platform can't be placed here
else Displace (randomPos);
}
So, the platform will be trying to find free space, everytime the randomPos is occupied, the platform will call Dispace()
again sending new positions in this method to look for another places.
Once the randomPos is not occupied, the platform will go there and stay there.
@Ermiq Doesn't this block of code prevent the execution once it has not detected any colliders for a frame? if (!canStayHere) { Displace(); }
Once canStayHere is true, the function never runs again, meaning it can never be set back to false unless by other means. Is there a flaw I just don't notice?
How I see it:
The platform is placed at the spawn position in Update()
, another platform is placed in the same position in its own Update()
.
In the next frame CheckForPlatforms()
is called on both 1st platform and 2nd one. Both platforms detect that something else is here and both of them get canStayHere = false
, both of them call their Displace()
.
Next frame. Platforms are getting their new positions and both are calling CheckForplatforms()
. There's nothing that could prevent them from checking it once they have been placed. So, if all of a sudden they both were displaced at the same new direction or some other platforms are already placed there, they all detect something in the circle again, and all of them get canStayHere = false
again.
Rinse and repeat. :)
@Ermiq How would I do that? I tried calling CheckForPlatforms() inside the if (!canStayHere){} statement, but that did nothing. What exactly would I do to tell it not to run again?