- Home /
OnTriggerEnter is called too late (after Start)
I am creating a simple room-based level generator. On adding a room to the level, I check if the new room is touching any colliders (new room has trigger collider and kinematic-rigidbody, old rooms just have normal colliders).
The problem is that immediately after adding a room, when I check whether the room is overlapping another room, the new room's OnTriggerEnter has not yet been called so it always returns as not triggered. How do I set up my code so that OnTriggerEnter has a chance to test the new room before I check whether OnTriggerEnter has been called?
Trigger code (on the new Room)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Room : MonoBehaviour {
[HideInInspector] public bool isColliding = false;
private void OnCollisionEnter(Collision collision) {
//Debug.Log("Colliding");
isColliding = true;
}
private void OnCollisionExit(Collision collision) {
//Debug.Log("Not Colliding");
isColliding = false;
}
private void OnTriggerEnter(Collider collision) {
Debug.Log("Triggered");
isColliding = true;
}
private void OnTriggerExit(Collider collision) {
Debug.Log("Not triggered");
isColliding = false;
}
// Use this for initialization
void Start () {
Debug.Log("ontrigger is: " + isColliding);
}
// Update is called once per frame
void Update () {
}
}
Check code (see line 39, it always logs "False", even when it should be true. After it logs false, OnTriggerEnter is called and logs "Triggered") using System.Collections; using System.Collections.Generic; using UnityEngine;
public class levelGen : MonoBehaviour {
public Room[] rooms;
public int roomCount = 10;
int currentRoomCount = 0;
// Add a Room
void AddRoom() {
bool success = false;
Room newRoom;
GameObject newRoomObject;
Door[] endDoors;
Door endDoor;
Door[] newDoors;
Door newDoor;
endDoors = ShuffleDoors(GetComponentsInChildren<Door>());
while (!success) {
newRoomObject = Instantiate(rooms[Random.Range(0, rooms.Length)].gameObject,new Vector3(100,100,100),transform.rotation) as GameObject;
newRoom = newRoomObject.GetComponent<Room>();
newDoors = ShuffleDoors(newRoom.GetComponentsInChildren<Door>());
for (int i = 0; i < endDoors.Length && !success; i++) {
endDoor = endDoors[i];
if (!endDoor.isConnected) {
for (int ii = 0; ii < newDoors.Length && !success; ii++) {
newDoor = newDoors[ii];
// place room at endDoor
newRoom.transform.SetPositionAndRotation(endDoor.transform.position - newDoor.transform.localPosition, endDoor.transform.rotation);
// rotate room to fit on endDoor
newRoom.transform.RotateAround(endDoor.transform.position, Vector3.up, 180 -Quaternion.Angle(Quaternion.Euler(Vector3.zero), newDoor.transform.localRotation));
// try to rotate in case the door is backkwards
Debug.Log(newRoom.isColliding);
if (newRoom.isColliding) {
Debug.Log("Tried it the other way around (rotate 180d)");
newRoom.transform.RotateAround(endDoor.transform.position, Vector3.up, 180);
}
if (!newRoom.isColliding) {
success = true;
currentRoomCount += 1;
//newRoomObject.transform.parent = gameObject.transform;
//newRoomObject.GetComponent<Collider>().isTrigger = false;
//Destroy(newRoom.gameObject.GetComponent<Rigidbody>());
// ?
//endDoor.deadEnd = false;
//endDoor.isConnected = true;
//newDoor.deadEnd = false;
}
}
}
}
}
}
// Use this for initialization
void Start () {
for (int i = 0; i < roomCount; i++) {
AddRoom();
}
}
// Update is called once per frame
void Update () {
}
//stuff
Door[] ShuffleDoors(Door[] arr) {
for (int i = 0; i < arr.Length; i++) {
int rnd = Random.Range(0, arr.Length);
Door tempGO = arr[rnd];
arr[rnd] = arr[i];
arr[i] = tempGO;
}
return arr;
}
Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Vector3 angles) {
return Quaternion.Euler(angles) * (point - pivot) + pivot;
}
}
You could use a coroutine and waitforendofframe() so your object has time to do awake, start, collision checks in its first frame, etc.
https://docs.unity3d.com/ScriptReference/WaitForEndOfFrame.html
Its not the best solution but you can at least see if that "fixes" the issue.
@Deathdefy I set up AddRoom as a coroutine and did waitforfixedupdate before every colission check (after every transform change). Sometimes the script recognizes colission and sometimes it doesn't. I've even added 5 second delays and still have issues. There is something more going wrong here, impossible to tell with what I have shared so far.
Answer by Teh_Bucket · Jan 20, 2018 at 09:57 PM
The alternate solution is to avoid using Unity's physics entirely, and just test whether the bounding boxes of each room overlap with bounds.Intersects()
. This is much faster and works (so far as I've tested, so long as you're using box colliders).
// bounds check
bool checkForOverlap(Collider[] newBoxes, Collider[] boxes) {
foreach (Collider newBox in newBoxes) {
foreach (Collider box in boxes) {
Debug.Log(box.transform.name);
if (newBox.bounds.Intersects(box.bounds)) {
return true;
}
}
}
return false;
}
// Function call looks like
if (!checkForOverlap(newRoom.GetComponents<Collider>(), transform.GetComponentsInChildren<Collider>())) {
success = true;
}
Answer by pako · Jan 20, 2018 at 07:59 PM
Set up AddRoom as a coroutine as you have done, but use WaitUntil
instead of waitforfixedupdate. Additionally add a public variable bool HasEnteredTrigger
in Room, and this is what the WaitUntil
should check before proceeding.
e.g.
public class Room : MonoBehaviour {
[HideInInspector] public bool isColliding = false;
[HideInInspector] public bool HasEnteredTrigger;
// other code
private void OnTriggerEnter(Collider collision) {
Debug.Log("Triggered");
isColliding = true;
HasEnteredTrigger = true; //levelGen should check this. before proceeding with checking isColliding
}
private void OnTriggerExit(Collider collision) {
Debug.Log("Not triggered");
isColliding = false;
HasEnteredTrigger = false; //reset here, or wherever else you think is more appropriate
}
}
Ienumerator AddRoom() {
//existing code
while (!success) {
newRoomObject = Instantiate(rooms[Random.Range(0, rooms.Length)].gameObject,new Vector3(100,100,100),transform.rotation) as GameObject;
newRoom = newRoomObject.GetComponent<Room>();
newDoors = ShuffleDoors(newRoom.GetComponentsInChildren<Door>());
for (int i = 0; i < endDoors.Length && !success; i++) {
endDoor = endDoors[i];
if (!endDoor.isConnected) {
for (int ii = 0; ii < newDoors.Length && !success; ii++) {
newDoor = newDoors[ii];
// place room at endDoor
newRoom.transform.SetPositionAndRotation(endDoor.transform.position - newDoor.transform.localPosition, endDoor.transform.rotation);
// rotate room to fit on endDoor
newRoom.transform.RotateAround(endDoor.transform.position, Vector3.up, 180 -Quaternion.Angle(Quaternion.Euler(Vector3.zero), newDoor.transform.localRotation));
yield return new WaitUntil(() => newRoom.HasEneteredTrigger);
// try to rotate in case the door is backkwards
Debug.Log(newRoom.isColliding);
if (newRoom.isColliding) {
Debug.Log("Tried it the other way around (rotate 180d)");
newRoom.transform.RotateAround(endDoor.transform.position, Vector3.up, 180);
// existing code
}
}
}
}
}
}
The problem here is that HasEnteredTrigger
will never become true if the trigger isn't colliding with anything, so the script just hangs. $$anonymous$$aybe there's a way to set HasEnteredTrigger
to true after collision is finished simulating, whether OnTriggerEnter is called or not (which should be what WaitForFixedUpdate
does, but it's not working)
Your answer
Follow this Question
Related Questions
Initialising List array for use in a custom Editor 1 Answer
Multiple Cars not working 1 Answer
OnTriggerEnter is called too late (after Start) 0 Answers
Distribute terrain in zones 3 Answers
[c#] Texture-based Trigger? 1 Answer