Unity activate something only first collision - c#

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){
...
}

Related

I want to finish the level with 2 players at the door. How do I do it?

here is my code for the trigger
`
private AudioSource finishSound;
private bool levelCompleted = false; //play the audio once
void Start()
{
finishSound = GetComponent<AudioSource>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.name == "Player1" && !levelCompleted)
{
CompleteLevel();
levelCompleted = true;
Invoke("CompleteLevel", 1.5f); //delay finish
}
}
private void CompleteLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}`
I know this code is only for one Player. How do I do it requiring 2 players at the door to finish the level?
An approach for this would be to cache the players and check against the total count. To see if it's a player, I'd advice to use tags instead of using the name of the object for a lot of reasons. I assume, the tag "Player" is used to identify the player.
List<GameObject> players = new List<GameObject>();
private void OnTriggerEnter2D(Collider2D collision)
{
// Add a new player if they enter the trigger
if (collision.gameObject.CompareTag("Player") && !players.Contains(collision.gameObject))
{
players.Add(collision.gameObject);
}
// Both players are in the trigger
if (players.Count == 2 && !levelCompleted)
{
CompleteLevel();
levelCompleted = true;
Invoke("CompleteLevel", 1.5f); //delay finish
}
}
private void OnTriggerExit2D(Collider2D collision)
{
// Remove a player if they leave the trigger again
if (collision.gameObject.CompareTag("Player") && players.Contains(collision.gameObject))
{
players.Remove(collision.gameObject);
}
}
If you have a varying count of players, you probably don't want to hard code the amount of players in a trigger. You should have a total count somewhere to check against. A game manager of sorts, that tracks the expected amount of players.
One way would be to create flags for each player, not one. When player one enters the trigger, you set playerOneCompleted = true. Then, you check if second player also completed. If that's the case finish the game if not, do nothing.
What you would need to add is the method OnTriggerExit2D to set flags of respective players to false when they exit the area.
Since the code you presented is inside the player, I would advise moving it to the door's trigger, so you can reference both players more easily.

Message not being disabled when not in collider

I am trying to display a message saying 'Press E to talk to NPC' when the player is collided with the NPC collider and when the player is not collided with the NPC the message is disabled. The message does appear upon collision but it does not disabled when there are no collisions I have tried so many things but nothing seem to work. Can anyone help? HERE IS MY CODE AND SOME THINGS I HAVE TRIED:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Task_7 : MonoBehaviour
{
public GameObject PressEmsg;
//public bool isNearNPC = true;
// Start is called before the first frame update
void Start()
{
PressEmsg.gameObject.SetActive(false);
}
// Update is called once per frame
void Update()
{
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
//bool isNearNPC = false;
//we are looping in the array hitColliders
foreach(Collider collider in nearbyColliders)
{
if(collider.gameObject.tag == "NPC")
{
PressEmsg.gameObject.SetActive(true);
print("NPC DETECTED");
//isNearNPC = true;
}
else
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
/*
else if(collider.gameObject.tag != "NPC")
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
*/
}
/*foreach(Collider collider1 in notnearbyColliders)
{
if(collider1.gameObject.tag != "NPC")
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
}
*/
}
}
If you have no collisions at all you will not have any iteration of your loop. So the deactivate will not happen, except you have a nearby object without the tag NPC.
Further you are iterating through all the nearby objects and check for each of them if it has the tag NPC. So the loop fully depends on the order in which the colliders are iterated. It might e.g. happen that you first have a hit that has the tag, then you have a second hit that doesn't => you falsely deactivate the object again.
You should rather use Linq Any like e.g.
using System.Linq;
...
void Update()
{
var nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
// This will be true if any of the nearbyColliders has the tag "NPC"
// If there are no colliders this will automatically be false accordingly
var detected = nearbyColliders.Any(collider => collider.CompareTag("NPC"));
// Basically this equals somewhat doing
//var detected = false;
//foreach(var collider in nearbyColliders)
//{
// if(collider.CompareTag("NPC"))
// {
// detected = true;
// break;
// }
//}
PressEmsg.gameObject.SetActive(detected);
print(detected ? "NPC detected" : "NPC not detected");
}
In general for performance reasons avoid logging in Update! Even though your users don't see the log it is still done and is quite expensive.
Note: Typed on smartphone but I hope the idea gets clear
It seems like if you don't have any collisions, you won't get into your for loop.
Before looping over the found colliders I would default the message to be not active, but I'd use a variable so I only actually call the message state method once:
bool isNearNpc = false;
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
foreach(Collider collider in nearbyColliders)
{
if(collider.gameObject.tag == "NPC")
{
print("NPC DETECTED");
isNearNpc = true;
}
}
PressEmsg.gameObject.SetActive(isMessageActive);
print($"NPC DETECTED: { isNearNpc }");

Defeat all targets

I have this script, but doesn't work, when i kill one enemy the door opens, i need to know how active the door when i kill all enemies. I need to destroy all enemies to open the door, but doesn't work good, if i kill one enemy the door opens for no reason.
public class DoorScript : MonoBehaviour
{
private GameObject[] enemyToKill;
public Transform pos1, pos2;
public float speed;
public Transform startPos;
Vector3 nextPos;
public bool EnemyDead = false;
// Start is called before the first frame update
void Start()
{
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
// Update is called once per frame
void Update()
{
foreach (GameObject enemy in enemyToKill)
{
if (enemy == null)
{
EnemyDead = true;
if (transform.position == pos1.position && EnemyDead)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
}
}
private void OnDrawGizmos()
{
Gizmos.DrawLine(pos1.position, pos2.position);
}
}
Well you are setting your bool to true for the first enemy that is killed.
You should rather check if all enemies are non existent. In general do never compare anything derived from UnityEngine.Object for null! Rather use the implicit bool operator.
Then you could use Linq Any (or All) like e.g.
using System.Linq;
...
void Update()
{
// In general use a bool check!
// A destroyed object reference is not necessarily null
// This returns true if there is still an existing enemy
if(!EnemyDead && enemyToKill.Anyy(enemy => enemy)) return;
// Still keep the flag so the check can be skipped
// As soon as the flag is already set
EnemyDead = true;
if (transform.position == pos1.position)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
If I assume correctly (it is not clear from above code), You have set to all enemies some tag "EnemyR1" and now You want to check, if those enemies exists or not. If there are no enemies, you want to open the doors (like exit point for player).
It also depends, if you kill the enemy immediately (and destroy the GameObject), or you only mark enemy as something (set some property/set is non-active/set different tag).
In the code you want to:
void Start()
{
// There you will get ALL objects that have the tag (that was ok before)
// This function always returns Array. Sometimes empty.
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
void Update()
{
// Now it depends how your logic is implemented:
// If you destroy automatically all objects, array will be empty, check length:
if (enemyToKill.Length == 0)
{
//There are no enemies to be killed, You can open the door
}
// Else if objects still exist, you must check if all have the condition.
// They can be disabled, have some property, etc.. In that case You need to:
bool allKilled = true;
foreach (GameObject enemy in enemyToKill)
{
if (enemy.active) //update this condition as you need
{
allKilled = false; // Set to false, when enemy is not killed
break; // and exit the loop
}
}
if (allKilled)
{
//There are no enemies to be killed, You can open the door
}
}

How can I run StartCoroutine once?

The code in my script works fine. But sometimes it does not drop after I contact the rigid body collides. When it touches another corner, it restarts the StartCoroutine. I want to run it once to get ahead of it. How can I get it? (More descriptive: In my game, a ball is falling from above, and it stops for 3 seconds when hit by obstacles. I do not want it to stop in that obstacle again after it has hit once.
public void OnCollisionEnter2D(Collision2D col)
{
if (col.collider.CompareTag("Player"))
{
hitEffect.transform.position = col.contacts[0].point;
hitEffect.gameObject.SetActive(true);
GameManager.Instance.playerController.anim.Squeeze();
col.gameObject.GetComponent<Rigidbody2D>().simulated = false;
StartCoroutine (SetKinematic_Coroutine(col));
}
}
public IEnumerator SetKinematic_Coroutine(Collision2D col)
{
yield return new WaitForSeconds(1f);
col.gameObject.GetComponent<Rigidbody2D>().simulated = true;
}
Best way for preventing multi call of a coroutine is to use a reference:
private IEnumerator coroutine = null;
private void Method()
{
if(condition == true && this coroutine == null)
{
this.coroutine = MyCoroutine();
StartCoroutine(this.coroutine);
}
}
private IEnumerator MyCoroutine()
{
yield return null;
this.coroutine = null; // Set it back to null when leaving the coroutine.
}
When the condition is met and the coroutine is null (you are not running it already), it will assign to the reference and call the starting of the coroutine. While the coroutine is running, this.coroutine is not null and the double condition cannot be met anymore. When the coroutine is done, this.coroutine is set to null so next time the double condition is run, they will be both true.
You can also use a basic boolean for flag, but the usage of the IEnumerator reference can also be used to cancel.
You could turn the collider of the ball off whilst it is stopped so it can't receive any more hits during that time.
public IEnumerator SetKinematic_Coroutine(Collision2D col)
{
//turn the collider off
col.gameObject.GetComponent<Collider2D>().enabled = false;
yield return new WaitForSeconds(1f);
col.gameObject.GetComponent<Rigidbody2D>().simulated = true;
//turn the collider back on after we have waited
col.gameObject.GetComponent<Collider2D>().enabled = true;
}
Add a list of objects already hit by it and, for every hit, check if the object it is colliding with is in the list, if not add it and run the code.
private List<Collider2D> collided = new List<Collider2D>();
public void OnCollisionEnter2D(Collision2D col) {
if (col.collider.CompareTag("Player") && !collided.Contains(col.collider)) {
collided.Add(col.collider);
// ...
The Contains call might give you trouble if to many colliders are added to the list frequently, but otherwise this should do it.

Detect collision/colliding only once

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.

Categories

Resources