OnTriggerExit not working as expected
Hello, people! I am experiencing a frustrating issue that I have not encountered before. I am using triggers in a project of mine. I have already looked up many answers.unity3d.com topics, but none of them is what I need. I have set up my triggers properly- IsTrigger is checked on whatever needs to be a trigger and also rigidbodies are added.
Enemy:
has a sphere collider ticked as a trigger
has a rigidbody
has a script Enemy.cs which contains the OnTriggerxxx() functions
has a navmesh agent to move around (wander to randomly chosen objects with colliders from an array while in wanderState)
Player:
Has a rigidbody
has its collider
has its script to move him around
has a tag "Player"
So far so good, trigger detection IS working. I am afraid my problem comes from an inability to structure my code logically.
void OnTriggerEnter(Collider col)
{
if (currentState == wanderState)
{
if (col.gameObject.tag == "Player" && m_target.gameObject.tag != "Player")
{
print(col.gameObject.tag);
wanderState.Detected(col.gameObject); //sets the target to the player
}
if (col.gameObject.tag == "WanderPoint")
{
print(col.gameObject.tag);
wanderState.changeWanderPoint();
col.gameObject.SetActive(false);
}
}
}
void OnTriggerExit(Collider col)
{
if (currentState == wanderState)
{
if (col.gameObject.tag == "Player") /* if I comment out this if-statement, this problem disappears, but the enemy follows me around the whole world without changing its target (ye, obviously m8, no code to change its target) which is obviously how I don't want the game to be. */
{
wanderState.changeWanderPoint(); /* this is where the problem occurs. The Player, even while staying inside the enemy's sphere collider, is treated as if he is exiting it, hence this function gets called repeatedly */
}
if (col.gameObject.tag == "WanderPoint")
{
col.gameObject.SetActive(true);
}
}
}
Brief explanation: I am using a State Design Pattern implementation to control the behavior of the enemy. currentState is a State variable (State being an interface), which holds concrete states that inherit from the interface(nothing to do with the problem, just FYI). Inside Enemy.cs there is a gameObject variable m_target which contains the target for the navmesh agent.
While the Player is inside the Enemy's sphere collider, the enemy just gets stuck in one place, because he doesn't get a single target to go to, instead it is being changed every frame. And while the player is within the enemy trigger the target should be the player.
My confusion probably comes from the fact that I am not fully aware of how the OnTrigger functions are called, although what I know about them is that OnTriggerEnter() and Exit() should only be called when their respective actions occur (if a collider enter the trigger, if-statement whether it is a particular tag, same with the Exit()). OnTriggerStay() is supposed to be called every frame to check whether something continues to be within the borders of the collider. Am I wrong with this information? It makes no sense for the Player to be treated as if exiting the trigger while within it.
Thank you very much in advance to anyone who will take the time to help! It will be greatly appreciated.
Answer by kris958 · Oct 27, 2016 at 05:30 PM
@JincSoft & @tanoshimi So I just managed to find a solution to my problem. I checked my Enemy's 'IsKinematic' on his rigidbody. Absolutely no other change in the code or the prefabs. So I would guess that some optimization in Unity is causing the OnTriggerEnter and OnTriggerExit to fire simultaneously and repeatedly every frame in my specific situation. Player was not changed, but Enemy is now kinematic. This has now made the transition between targets work as supposed to
Answer by tanoshimi · Oct 27, 2016 at 01:32 PM
As you point out, it's a problem in your logic rather than in OnTriggerExit itself. But it's hard for anyone else to point out your mistakes when we only have partial code. A few comments that spring to mind:
You test whether currentState == wanderState in both functions, but never seem to set currentState to any other values. What other values can it take, and what changes it?
What does changeWanderPoint() or Detected() do?
Lines 29-32 you test whether a "WanderPoint"-tagged object leaves the trigger. But that can't ever be true since, in lines 11-16, if a "WanderPoint" enters the trigger, you set it to be inactive.
When the player exits the trigger, I'm guessing you meant to set m_target.gameObject to null? (Or perhaps, to an aribtrary wander point?)
Thank you for the answer. I cannot explain the full code for the project as it would be too much to write in here, that is why I have only put this snippet of code, because this is where it comes down to in the end. I have reviewed all code and filtered everything that does not have to do with the problem.
currentState is assigned wanderState at Start(), because this will be the default state the Enemy will be in when not interacting with the player. One state is changed to another from the concrete state itself, i.e. wanderState has a function toCombatState() which will set currentState in Enemy.cs to combatState. CombatState as of now has not been implemented, because I first need to sort out this issue, so we have only one state at the moment and I am only using it, so right now the state is not being changed at all (so that if-statement is always true for now but is not related to the problem)
changeWanderPoint() sets m_target to a randomly chosen wander point from an array
public class WanderState : State { private Enemy enemy;
public WanderState(Enemy t_enemy)
{
enemy = t_enemy;
changeWanderPoint(); //set m_target to a random wander point
}
public void Detected(GameObject t_target) //player passed in as an argument in Enemy.cs
{
enemy.m_target = t_target;
}
public void run() //called every update in Enemy.cs
{
enemy.navAgent.SetDestination(enemy.m_target.transform.position);
}
public void changeWanderPoint()
{
enemy.m_target = enemy.WanderPoints[enemy.GetRand() % 10];
}
Setting WanderPoint to inactive was my attempt at letting the enemy know that the WanderPoint it was going for has just been visited. When it gets inactive, the WanderState automatically gets a new WanderPoint from the array. This one works as it is at the moment, although I will be changing it to something more reasonable, as this was just something temporary to get it working.
When the player exits the trigger, I want to generate a new wander point, so that the enemy can just continue wandering until the player goes back into his range.
I hope this makes it a little clearer, thank you for sharing your opinion. I have been away from the computer a bit and now that I am back. Seeing that the person below has also confirmed that I am not wrong about how OnTrigger functions work I will be looking into my conditions to see if I can find the fault there (it probably is there). If you have any other suggestions, please do share!
Thank you once again
Answer by JincSoft · Oct 27, 2016 at 01:40 PM
Your knowledge on how the OnTrigger functions work is correct. I am a bit curious about the wanderState.changeWanderPoint();
function code because for right now I don't see too much wrong in the OnTrigger functions so maybe you have a bad loop somewhere. What it sounds like is that you may have a small part of the player that is also tagged as "Player" (like hair, weapon, etc) that may be entering and leaving the collision bounds when the enemy moves causing a repeated function call. Another thing is that you may be inadvertently calling this function elsewhere in the code (like in the Update() function or another condition check) so you could try dropping a debug.log() with the name of the function calling the changeWanderPoint(); method.
Debug.Log("Collided with "+col.gameObject.name);
Debug.Log("Changing Wander Point from OnCollisionEnter()");
Debug.Log("Exiting collision with "+col.gameObject.name);
Debug.Log("Changing Wander Point from OnCollisionExit()");
Last thing I could suggest is changing the currentState
so it isn't the same as wanderState
in the OnCollisionExit() to see if it really is messing with it.
Thank you for replying. While writing the comment for the other, I had the time to review the code again and consider where the mistake might be. It is probably something with the conditions to deter$$anonymous$$e what to target.
I have not been using loops anywhere in the code files, so it's not a bad loop.
The player only consist of a polygon mesh and a skeleton, which move together. Also using a box collider around the whole mesh, nothing else is attached to the game object to cause the suggested problem, so it's not that one either.
The function is also not called anywhere in other functions. It is called in the constructor for the wanderState, but since it's only called once, that's not it.
I will look into debug logging anyway, because it can help me, but I never bothered (or rather never had to resort to it). If you would like, you can look at my comment to the other reply to see if there is anything else that you might notice.
Thank you once again!
Answer by RakNet · Apr 06, 2019 at 06:09 PM
Since this is a top result on Google I wanted to link to a more robust solution https://forum.unity.com/threads/fix-ontriggerexit-will-now-be-called-for-disabled-gameobjects-colliders.657205/
Answer by RohanP · Feb 01, 2021 at 08:31 PM
Similar issue with me andthe only fix that works is isKinematic flag on the enemy rigidbody.Navmaesh agents work wierd when its disabled triggers work wierd when its disabled and the object in concern has a navmesh agent even if its disabled.No other fix works.
Your answer
Follow this Question
Related Questions
Material and RenderTexture produce unexpected results when disabled/released. 0 Answers
Coloring encircled tiles 0 Answers
Using OnTriggerStay for entering, staying or exiting 2 Answers
Performance Issue While Near One Specific Object 0 Answers
Collider fires consistently, but Trigger does not? 0 Answers