- Home /
Help detecting collisions on complex objects with teleportation
I've been working on this all day and I need some help. I'm trying to spawn a bunch of buildings randomly in an area, but they keep spawning on top of and inside each other. Here's an album with my code and a screenshot of the spawning behavior:
Each building is a single color, made up of multiple box colliders with kinematic rigidbodies with IsTrigger unset. I randomly generate the building then send it to the TestCollision function. To test for collision, I create a new gameobject with a kinematic rigidbody, a trigger boxcollider, and this teleport tester script (code in the album). Then I disable the original game object, wait for a fixed update, and check the allClear variable.
The problem is, it doesn't detect collisions most of the time. When I spawn 100 buildings, it will detect maybe two collisions (triggering a retry), but it won't detect the dozen or so other collisions and buildings end up on top of one another.
Does anyone know what might be causing this? I'm guessing it's got something to do with that I'm putting the placeholder game object into other colliders which aren't moving, but I tried doing a WakeUp on the placeholder's rigidbody and that didn't work.
So it finds no collisions? Why are you using a placeholder btw, why not just test the ToSpawn object directly? On appearance, it seems like doing a lot of extra work.
As for detection, if it's not detecting, and the collision boxes are set correctly, it may (guess work) have something to do with how the backend is implemented. $$anonymous$$y guess is if you spawn inside something else, it won't detect. You can try moving the new object a bit and see if it causes a triggers, tho my guess is if it's not clear in the first place, istrigger will never fire. actually, you can try moving the new object a good distance > any possible box collider size, then move it back to the original position, that should trigger it.
note, if you move it, throw it in a coroutine so it takes a frame to move it out and a frame to move it back in. or better, spawn it say 200m away or whatever > max box collider size, then the next frame move it to the position you want and check for collision.
Thanks for your response. I tried testing the ToSpawn object directly, after looking at the Collisions table, I saw that a new object with a kinematic rigidbody, trigger collider setup should trigger vs any object type, even static collider objects. I didn't want to mess with those values and risk corrupting the original ToSpawn object.
The collision boxes (placeholder object) seem to be set up perfectly around the buildings (ToSpawn object), which I disable in order to prevent them from colliding with the collision box. The buildings aren't just spawning inside each other, they're spawning partially intersecting. I expected it to trigger then.
Would it be feasible to move the buildings around like you suggest? I feel like if I waited several frames to do that for each building, it could take forever. It seems like this should work easily and that I'm just doing something stupid.
In a coroutine you aren't waiting several frames, you are just delaying each spawn by one frame. So say, if you spawn 25 buildings per frame, and take 10 frames to spawn 250 buildings, by the 11th frame all will have been tested.
SinisterRainbow - I think I'm fundamentally misunderstanding how coroutines work. I will read up on the documentation, but if you know of a good guide, I'd love to see it.
Answer by aldonaletto · Jul 01, 2013 at 04:54 PM
I used a similar trick to detect objects inside a given volume, but had to force collision calculation with a simple trick: assign transform.position to rigidbody.position. Take a look at my answer to this question.
EDITED: Complementing @SinisterRainbow's answer: since the collision detection function is a coroutine, you should chain the main loop to it. Chaining coroutines is much like a regular function call: control is transferred to the called routine and only returns to the caller when the routine ends. But there's a problem: a coroutine can't return any useful value (coroutines always return IEnumerator). In C# you could use the out or ref keywords to get the return value in a variable passed by reference, but in JS the only way is to use a class variable (class variables are always passed by ref). The JS code could be something like this:
class AllClear { // declare the AllClear class
result: boolean; // this variable will receive the returning value
}
private var allClear = AllClear(); // create a variable of type AllClear
// SpawnBuildings is a coroutine
function SpawnBuildings(howMany: int): IEnumerator {
while (howMany > 0){
var toSpawn = SelectRandomBuilding(); // randomly select a building
do {
var pos: Vector3 = CalculateRandomPosition(); // draw a random position
yield TestCollision(toSpawn, pos, allClear); // chain to the test collision coroutine
} while (allClear.result == false); // repeat until clear position is found
SpawnBuilding(toSpawn); // spawn the building
howMany--; // decrement counter
}
}
// TestCollision is a coroutine
function TestCollision(model: GameObject, position: Vector3, isClear: AllClear): IEnumerator{
isClear.result = false; // initialize result
// do the collision test, then assign the result to isClear.result
if (model doesn\'t collide with anything) isClear.result = true;
}
NOTE: A couroutine always returns IEnumerator, but you don't need to explicitly declare its return type in JS (opposite to C#): the compiler automatically infers this when you use a yield statement inside a function. Despite this, I declared the IEnumerator type just to state that the functions above are coroutines.
So here's what I wrote based on your answer. What's weird is that it actually does detect the collisions, but only way after my TestCollision function is complete. If I go to the inspector and manually search through my objects, I can see that the ones intersecting have AllClear unset.
Debug.Log("allclear is false!") is never called.
private function TestCollision (ToSpawn : GameObject){
//get max bounds of object and all decendents
var BoundingBox = new Bounds(ToSpawn.transform.position, Vector3.zero);
var ChildColliders = ToSpawn.GetComponentsInChildren(Collider);
for (var ChildCollider : Collider in ChildColliders) {
BoundingBox.Encapsulate(ChildCollider.bounds);
}
ToSpawn.SetActive(false);
var Placeholder = new GameObject();
Placeholder.transform.position = BoundingBox.center;
Placeholder.transform.localScale = BoundingBox.size;
var ph_box : BoxCollider = Placeholder.AddComponent(BoxCollider);
ph_box.isTrigger = true;
var ph_rigid : Rigidbody = Placeholder.AddComponent(Rigidbody);
ph_rigid.is$$anonymous$$inematic = true;
ph_rigid.position = Placeholder.transform.position;
//add teleport manager
var ph_tptest = Placeholder.AddComponent(TeleportTester$$anonymous$$anager);
StartCoroutine(WaitForFUpdate());
//test
var AllClear = ph_tptest.allClear;
if (!AllClear){
Debug.Log("allclear is false!");
}
//cleanup
Destroy(Placeholder);
ToSpawn.SetActive(true);
return (!AllClear); //if we're all clear, we do not have a collision
}
You may be assu$$anonymous$$g the Coroutine pauses the calling method, it doesn't. the coroutine should be thought of as sending it to another thread that may or may not be running, or better, sends it to a queue to execute 'later', and the caller just 'queues' it and keeps executing.. So the the co should be a self sufficient unit..when it has data 'later' it will let you know by calling another function or setting a class-scope variable or the like.
Hm, so my approach of calling the TestCollison function normally, and having it delete the object was better?
So reddit suggested I use the dirty solution of checking my bounding box against all other bounds in the scene... It works, but it's slow and I don't like it.
private function TestCollisionBounds (ToSpawn : GameObject){
var BoundingBox = Bounds(ToSpawn.transform.position, Vector3.zero);
var ChildColliders = ToSpawn.GetComponentsInChildren(Collider);
for (var ChildCollider : Collider in ChildColliders){
BoundingBox.Encapsulate(ChildCollider.bounds);
}
BoundingBox.size += Vector3(1,0,1); //add a buffer of 1 on each side, so we can walk through
ToSpawn.SetActive(false);
var AllColliders = GameObject.FindObjectsOfType(Collider);
for (var ChildCollider : Collider in AllColliders){
if (BoundingBox.Intersects(ChildCollider.bounds)){
return true;
}
}
ToSpawn.SetActive(true);
return false;
}
and aldonaletto - that's a nice way to do it, and may have saved me future thinking about the (other thread's topic) :) so thumbs up.
you should select aldonaletto's answer and use his solution. there is no need to check against all bounding boxes. You're turning an 0(1) problem into an 0(n). if you use my suggestion to move the collision box out and back again it should work, aldonaletto also used anotehr technique slightly better - which he posted the link to.
Answer by SinisterRainbow · Jul 01, 2013 at 03:56 PM
I don't know a good guide, but I'll try to explain simply. I think formatting is better in the answer section so I put it in here.
Coroutine's are perfect for doing heavy work broken up into several frames.
Examples:
I use them for cross-fading music, moving objects, and even to create a tiled game board with thousands of pieces in front of the player, but I only spawn a few pieces per frame. So instead of a loading-time/game hiccup by trying to do a heavy job at once, it keeps the framerate smooth by breaking it down into simple executions spread out over many frames..
To use it:
Basically inside a coroutine you create a loop, and at the end of that loop tell it to wait for the next frame (or you can have it wait based on time). .
So code for spawning your buildings may look like this (C#):
public class Buildings {
public int m_iMaxBuildings;
public Init() {
m_iMaxBuildings = 250;
StartCoroutine(SpawnBuildings()); //<-- this is how you start it
}
public IEnumerator SpawnBuildings() {
Debug.Log("I will only print once"); //<--before the loop
for (int i=0; i < m_iMaxBuildings; ++i) { // <-- start the loop, any kind of loop.
SpawnASingleBuilding();
yield return null; // <-- here it pauses this function
Debug.Log("This is the next frame"); // <-- it will start here next frame
} //<--end of for-loop, you can put more or do nothing else-->
yield break; //<--this is NOT necessary, i put it here to show you how to if
// you need to abort early, it will terminate the the function
}
public void SpawnASingleBuilding() {
// ... your code here ..
}
}; //end class
On a side note, I also use it to delay actions once in awhile like playing particle effects for example. You can have as many yield... statements as you want as well.
btw, so psuedo code for what I was talking about with your project:
...
public IEnumerator SpawnBuildings() {
while (bSpawningBuildings) {
SpawnABuildingALittleDistanceAway();
yield return null; //pauses until next frame
$$anonymous$$oveBuildingToDesiredPositionAndCheckForCollision(); //finishes the action
}
}
...
Oh that's awesome. Thank you for explaining that - I actually noticed a big frame hiccup when it spawns the buildings.
sure thing, tell us if it worked, would be good to know for sure.
Your answer
Follow this Question
Related Questions
Respawn random objects 1 Answer
Colliders and waypoints... 2 Answers
Make a random choice on collision enter - c# 1 Answer
Why is my Rigidbody2D not moving? (C#) 1 Answer
Random Movement collision bug 3 Answers