I have an object that moves towards another object and physically collides with it, I want that collision/colliding event to happen only once. I tried using a bool but it didn't work as intended. It seems that I'm doing something wrong.
bool doDamage = true;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Target" && doDamage)
{
doDamage = false;
// damage code
}
}
void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Target")
{
doDamage = true;
}
}
Edit:
I want the "damage code" to run only once, even if the 2 objects are still in contact. This script is only assigned to 1 object, not both.
I don't know the easiest way to explain why your code is not working but I will try.
You have two GameObjects:
GameObject A with doDamage variable.
GameObject B with doDamage variable.
When GameObject A collides with GameObject B:
A.The OnCollisionEnter2D function is called on GameObject A.
if(other.gameObject.tag == "Target" && doDamage) executes because doDamage is true.
B.The doDamage variable from GameObject A is then set to false.
This does not affect the doDamage variable from GameObject B.
Then
C.The OnCollisionEnter2D function is called on GameObject B.
if(other.gameObject.tag == "Target" && doDamage) executes because doDamage is true.
D.The doDamage variable from GameObject B is then set to false.
Both your damage code will run because doDamage is always true in each OnCollisionEnter2D call. What you are currently doing is only affecting doDamage variable in each individual script.
What you are currently doing:
Setting doDamage in the local/this script to false while also checking if local/this doDamage is set or not.
What you need to do:
Set doDamage in the other script to false but read the local/this doDamage to check if it is set or not.
This is what it should look like:
public class DamageStatus : MonoBehaviour
{
bool detectedBefore = false;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Target"))
{
//Exit if we have already done some damage
if (detectedBefore)
{
return;
}
//Set the other detectedBefore variable to true
DamageStatus dmStat = other.gameObject.GetComponent<DamageStatus>();
if (dmStat)
{
dmStat.detectedBefore = true;
}
// Put damage/or code to run once below
}
}
void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Target")
{
//Reset on exit?
detectedBefore = false;
}
}
}
If you want to run a snippet of code once, run the code directly in the callback event that you want to trigger your code.
void OnCollisionEnter2D(Collision2D other)
{
DoSomeDamageTo(other.gameObject);
}
This should only trigger once upon collision.
If you want this to only ever happen once (e.g. if the object holding the script were to hit something again), you'll need a flag to say that the damage has already been done:
bool hasHitSomething = false;
void OnCollisionEnter2D(Collision2D other)
{
if (!hasHitSomething)
{
DoSomeDamageTo(other.gameObject);
hasHitSomething = true;
}
}
I suspect given the code you've shared, that either the objects are colliding multiple times in engine (very possible if they're solid and physics-controlled), leading to OnCollisionEnter being called multiple times. OnCollisionEnter should only be called once per collision. If you are observing it being called multiple times, either you are actually observing multiple collisions, or you've found an obscure Unity bug. The former is much, much more likely.
Related
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.layer == layer)
{
StopAllCoroutines();
Destroy(gameObject, 1f);
}
}
//Coroutine is called from another script
public IEnumerator MoveRb(Vector3 destination)
{
yield return new WaitUntil(() => rb.velocity == Vector3.zero);
destination.y = rb.position.y;
rb.velocity = transform.right;
//this is where i get an error
yield return new WaitUntil(() => Vector3.Distance(rb.position, destination) < 0.1f);
rb.velocity = Vector3.zero;
}
Basically, getting "MissingReferenceException" when trying to destroy an object while running coroutine. Delay of 1 second, nor replacing "WaitUntil" with while loop and "yield return null" doesn't fix this issue. The only place where object gets destroyed is inside of "OnCollisionEnter" inside of the same gameObject script. What am I missing here?
Full exception message:
MissingReferenceException: The object of type 'Rigidbody' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
As you posted on the other answer you actually run this routine from another script
public class ConveyourBelt : MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.TryGetComponent(out MovingPart movingPart))
{
// runs this Coroutine on THIS component
StartCoroutine(movingPart.MoveRb(transform.position + transform.right));
}
}
}
The issue here is that this ConveyourBelt component is running the Coroutine, not the MovingPart component attached to the other object
=> The call to
StopAllCoroutines();
in the MovingPart component has no effect at all since it is never running that routine!
So when you destroy the object after 1 second the routine could still be running on the ConveyourBelt component.
As a solution you should rather make the routine running on the other component like
public class ConveyourBelt : MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.TryGetComponent<MovingPart>(out var movingPart))
{
// rather runs the Coroutine on the movingPart component
movingPart.StartCoroutine(movingPart.MoveRb(transform.position + transform.right));
}
}
}
The code should work in theory. I think the problem is you are trying to access the coroutine again during this 1 second destroy delay. This results in the coroutine running while the gameobject is destroyed.
If you want this 1s delay then either make sure that the coroutine can't get called again after you have collided with something or the easier way is to make the coroutine error proof. Means you check if the rigidbody is null before you use it.
My aim is activate an event inside the OnTriggerEnter function only for once.
void OnTriggerEnter(Collider coll)
{
GameObject obj = GameObject.Find("Player");
Player player = obj.GetComponent<Player>();
bool isCollide = false;
if (coll.GetComponent<Collider>().CompareTag("Player"))
{
if (isCollide == false)
{
isCollide = true;
Instantiate(instantiatedObj, new Vector3(this.transform.position.x, 3, this.transform.position.z), Quaternion.identity);
}
else
isCollide = false;
}
}
However, when I press play, it instantiates the instantiatedObj in every collision. I just want to make this happen only first collision.
Currently you are resetting your IsCollide to false each time you enter the OnTriggerEnter for the second time.
To really ensure that you only Instantiate it on the first Collision, you would need to remove that reset.
Example:
// Check if isCollide is set to false
if (!isCollide) {
// If it is then set it to true, so it won't be called again
isCollide = true;
// and execute any code we don't want to be called again
...
}
isCollide is being set to false for every collision because OnTriggerEnter is called for every collision.
Move isCollide to outside of method, make it class property so it will be kept.
public bool isCollide {get;set;}
void OnTriggerEnter(Collider col){
...
}
I'm trying to program a maze in Unity where you have to pick up a key in order to open door somewhere else. I put colliders on both the key and the door and have set the script to do different things when the player collides with them. here's the script. The problem is that the key allows the player to pass through it, but it doesn't mark it as the player has collected the key nor does it destroy the key either. Both the door and key have "is Trigger" checked.
public class DetectCollide : MonoBehaviour
{
public static string hasKey = "n";
void Start()
{
}
void Update()
{
}
private void onTriggerEnter(Collider other)
{
if(other.name == "Main Camera")
{
if(gameObject.name == "Key")
{
hasKey = "y";
Destroy(gameObject);
}
if((gameObject.name == "ArchDoor") && (hasKey == "y"))
{
Destroy(gameObject);
}
}
}
}
Both the key and the player must contain a Collider component: https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html
Both GameObjects must contain a Collider component. One must have Collider.isTrigger enabled, and contain a Rigidbody. If both GameObjects have Collider.isTrigger enabled, no collision happens. The same applies when both GameObjects do not have a Rigidbody component.
Are all the prerequisites in place?
Please also change the code and see if it enters at all in the function
private void onTriggerEnter(Collider other)
{
Debug.Log("OtherName: " + other.name + "gameObject: " + gameObject.name);
if(other.name == "Main Camera")
{
if(gameObject.name == "Key")
{
hasKey = "y";
Destroy(gameObject);
}
if((gameObject.name == "ArchDoor") && (hasKey == "y"))
{
Destroy(gameObject);
}
}
}
It appears to not even enter the function seeing as nothing happened
after the change
The problem lies withing your function name, Unitys custom functions (eg. Update, Start and OnTriggerEnter) are all case sensitive and will not be called if they don't have the exact same name.
Example:
private void OnTriggerEnter(Collider other) {}
Instead of onTriggerEnter(), because you need to make sure that the O on OnTriggerEnter() is higher case like it should be. Or your function won't be called and therefore not trigger your collision like you wanted.
I have an NPC that my player can talk to when the players collider is colliding with the NPC, I do that using this piece of code:
private void OnTriggerStay2D(Collider2D other)
{
if (other.gameObject.tag == "InteractiveArea")
{
if (Input.GetKeyDown(KeyCode.E))
{
Debug.Log("PRESSED NPC");
CreateAndShowDialog();
}
}
}
However, this gets called really randomly, sometimes the first time I press "E", sometimes the second or third time, etc.
My rigidbodies:
The colliders I use are standard BoxCollider2D, my players collider is a trigger, the NPCs is not.
Why are some key press not detected in the OnTriggerStay function?
OnTriggerStay2D is called randomly. This is why you should never check for Input inside of it.
Set to a flag to true and false in the OnTriggerEnter2D and OnTriggerExit2D functions then check for that flag and input in the Update function which is called every frame. Also, always use CompareTag instead of other.gameObject.tag to compare tags.
private void Update()
{
if (Input.GetKeyDown(KeyCode.E) && triggerStay)
{
//
}
}
bool triggerStay = false;
void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("Entered");
if (collision.gameObject.CompareTag("InteractiveArea"))
{
triggerStay = true;
}
}
void OnTriggerExit2D(Collider2D collision)
{
Debug.Log("Exited");
if (collision.gameObject.CompareTag("InteractiveArea"))
{
triggerStay = false;
}
}
My guess would be because the value returned by Input.GetKeyDown is only true for the single (Update()) frame that the key was pressed, whereas physics (including OnTriggerStay) are called during physics frames, i.e. FixedUpdate()
Doing a bit of research I pull up this question which suggests:
I've been struggling with this issue all afternoon!
My OnTriggerStay and Update/FixedUpdate methods were not in sync resulting is split-second undesired effects.
I finally found the solution when I read in the OnTriggerStay docs that this function can be a co-routine. I simply added a WaitForFixedUpdate in the correct location in my OnTriggerStay and it worked. They now both run in sync with each other. This even corrects the issue in Update.
Another question has this as a solution (Programmer's answer):
OnTriggerStay will not call on every frame. One way to get around this is to have OnTriggerEnter, and OnTriggerExit set a bool. Then execute your code in the FixedUpdate().
While the documentation no longer says what this post says,
OnTriggerStay gets called every FixedUpdate.
"Note: OnTriggerStay function is on the physics timer so it wont necessary run every frame. "
That confirms my guess and that the documentation was changed to no longer include this note, for some unexplained reason.
You can do this :
bool eIsOnclick;
float wait = 0.03f;
float nextFire = 0.0f;
void Update() {
if(Input.GetKeyDown(keyKode.E))
eIsOnclick = true;
else if(nextFire < Time.time)
nextFire = Time.time + wait;
ebas = false;
}
void OnTriggerStay2D(Collider2D collision) {
if(eIsOnclick == true) {
// your cods
}
// not work in alwaays aquestions
I am using unity 5 c# and I have a gameobject with 2 trigger colliders one of them is in a different location.
I need to be able to use OnTriggerStay2D and OnTriggerEnter2D for them but I need to find what trigger is being entered. Right now if I enter the 1st(polygon) trigger the OnTriggerEnter activates for the 2nd(box).
How can I Tell the two colliders apart???
public void OnTriggerEnter2D(Collider2D other) //2nd collider trigger
{
if (other.tag == "Player") {
Found = true; //if the player is in shooting range
Idle = false;
}
}
public void OnTriggerStay2D(Collider2D other) //1st collider trigger
{
if (Found != true) {
if (other.tag == "Player") {
Shield = true;
Idle = false;
}
}
}
public void OnTriggerExit2D(Collider2D other) //2nd collider trigger
{
if (other.tag == "Player") {
Found = false;
Shield = false;
Shooting = false;
Idle = true;
}
}
I have tried making the 1st trigger public void OnTriggerStay2D(PolygonCollider2D other) but it says "This message parameter has to be of type: Collider2D
The message will be ignored."
What I am trying to do is have a polygon trigger in front of the gameobject and a different box trigger closer to the gameobject so when you go near the gameobject you enter the 1st trigger and it puts its shield up but when you get close to it (within shooting range of it) it will put its shield down and start shooting you.
Well collider2d detects all types of 2d colliders. It doesn't matter if it's polygon or just a box. As the documentation suggestions it doesn't need to be public or private. It only takes a collider2d as it's argument however.
For debugging purposes why not use print?
Print("you've entered the trigger function");
Also I wouldn't use 2 different trigger colliders on the same GameObject. Why not just make 2 separate gameobjects so you can have more thorough detection. Each GameObject with its own trigger collider can have different tags.
If you have to use 2 trigger colliders on one object. Which isn't the best idea. You could use shapeCount to determine which one it's hitting. Although like I said I would warrant against doing 2 trigger colliders on the same object when whatever you're trying to do can be easier on two separate objects.
However links aren't usually prohibited I think. I would watch and study these videos. They're very useful for explaining the engine and they really aren't even that long.
https://unity3d.com/learn/tutorials/modules/beginner/2d
They even have a video explaining 2d colliders.
This is my fix. In one of my games I have a boulder, I have a trigger which will delete a block below it so it falls, I then have another trigger which tells the boulder to start moving left or right I then also have another trigger which will delete the boulder once the boulder comes in contact.
So what you can do is create 2 new game objects, create a new CS file and name them appropriately, then with those two new classes allow them to take in the gameobject you are referring to in your question.
Then when they are triggered you can use code from their class.
So your first class would become something like this
public void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player") {
Enemy.Found = true; //if the player is in shooting range
Enemy.Idle = false;
}
}
public void OnTriggerExit2D(Collider2D other)
{
if (other.tag == "Player") {
Enemy.Exit2DTrigger();
}
}
Then the other class would be something like this
public void OnTriggerStay2D(Collider2D other)
{
if (Enemy.Found != true) {
if (other.tag == "Player") {
Enemy.Shield = true;
IEnemy.dle = false;
}
}
}
public void OnTriggerExit2D(Collider2D other)
{
if (other.tag == "Player") {
Enemy.Exit2DTrigger();
}
}
Then in your Enemy class you would have
public void Exit2DTrigger()
{
Found = false;
Shield = false;
Shooting = false;
Idle = true;
}
P.S. also don't you need to use other.gameObject.tag == "Player" ?