I want to destroy a GameObject when it hits the trigger but with an input. No errors, but its not working in unity
using UnityEngine;
public class DestroyTile : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Tile"))
{
if(Input.GetKeyDown(KeyCode.Space))
{
Destroy(other.gameObject);
}
}
}
}
what should I do to make this work??
Very unlikely that you manage to hit the key in exactly the same frame the collision happens ;)
As a quick fix rather use OnTriggerStay
OnTriggerStay is called once per physics update for every Collider other that is touching the trigger.
public class DestroyTile : MonoBehaviour
{
// Called once every FixedUpdate for each trigger you are touching
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("Tile"))
{
if(Input.GetKeyDown(KeyCode.Space))
{
Destroy(other.gameObject);
}
}
}
}
With above way there is only one last issue: OnTriggerStay is not actually called every frame but rather every FixedUpdate so you might miss a key press if it happened in a frame where FixedUpdate isn't called. Therefore in general getting User input within FixedUpdate (or other physics based continuous methods) is unreliable and should always be done in Update.
So instead you could/should do something like e.g.
public class DestroyTile : MonoBehaviour
{
private readonly HashSet<GameObject> _currentlyTouching = new HashSet<GameObject>();
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Tile") return;
// For some reason we still have the other object stored
if(_currentlyTouching .Contains(other.gameObject)) return;
// Store the other object
_currentlyTouching.Add(other.gameObject);
}
private void OnTriggerExit(Collider other)
{
// We don't have this other object stored
if(!_currentlyTouching.Contains(other.gameObject) return;
// remove this other object
_currentlyTouching.Remove(other.gameObject);
}
private void Update()
{
// Are we touching anything?
// This check is cheaper than Input.GetKeyDown
if(_currentlyTouching.Count <= 0) return;
if(!Input.GetKeyDown(KeyCode.Space)) return;
// destroy (all) the touched object(s)
foreach(var obj in _currentlyTouching)
{
Destroy(obj);
}
// Don't forget to also clear the HashSet in that case
_currentlyTouching.Clear();
}
}
This way you have the User Input separated in Update (every frame) and only track the entering and exiting colliders via the Physcis based system.
Related
So what I'm doing is doing a pvz game but the objects is constantly firing so I implemented collision when the enemy is collided it will fire and stop when it is not so here is the code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class attackRange : MonoBehaviour
{
public towerAttacker towerAttacker;
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "enemyCollider")
{
towerAttacker.shootStart();
// It will detect once but once too many enemies are present it only counts as 1
}
}
void OnTriggerStay2D(Collider2D collision)
{
if (collision.tag == "enemyCollider")
{
towerAttacker.shootStart();
// It detects constantly but makes the object go into rapid fire mode
}
}
void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == "enemyCollider")
{
towerAttacker.StopAllCoroutines();
// Stop when there is no collision detected
}
}
}
It works perfectly on one object but screws on many objects
I'm think I know what's going on here. you might want to keep a list of existing enemies inside your range.
The issue I'm seeing is running "StopAllCoroutines()" when a collision ends might be messing up your logic, because it stops listening for all the other collisions.
That OnTriggerExit2D should get executed when 1 collision ends, so you are stopping everything when the first enemy dissapears.
So what I believe you want to try is something like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class attackRange : MonoBehaviour
{
public towerAttacker towerAttacker;
private List<string> activeEnemies = new List<string>();
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "enemyCollider")
{
// You might want to make sure there is no existing element with this name
activeEnemies.Add(collision.gameObject.name);
towerAttacker.shootStart();
// It will detect once but once too many enemies are present it only counts as 1
}
}
void OnTriggerStay2D(Collider2D collision)
{
if (collision.tag == "enemyCollider")
{
towerAttacker.shootStart();
// It detects constantly but makes the object go into rapid fire mode
}
}
void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == "enemyCollider")
{
// Again, you might want to have some validation to make sure that string exists.
activeEnemies.Remove(collision.gameObject.name);
if(activeEnemies.Count() == 0) {
// Stop when there is no collision detected
towerAttacker.StopAllCoroutines();
towerAttacker.shootStop();
}
// UPDATED to comment this line
//towerAttacker.StopAllCoroutines();
}
}
}
UPDATE
To make sure your attacker does not fire twice, you should update the towerAttacker class and the shootStart method.
And you'll need a new shootStop method, to reset that flag.
public class towerAttacker
{
public bool isAttacking = false;
void shootStart ()
{
if (!isAttacking)
{
// Do whatever your doing now
...
this.isAtacking = true;
}
}
void shootStop() {
this.isAttacking = false;
}
}
So, I have this potion. When the player in my game comes in contact with the potion, I want to destroy the potion. However, if the player dies, the scene will reload and the potion will still be in the level. If the player collides with the potion, I don't want them to obtain it. They should only be able to collect the potion once.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyPotionForever : MonoBehaviour
{
public bool potionCollide = false;
// Start is called before the first frame update
void Start()
{
}
void OnTriggerEnter(){
if(potionCollide == false){
Destroy(gameObject);
bool potionCollide = true;
}
}
// Update is called once per frame
void Update()
{
}
}
But...this code doesn't work. Any help is appreciated.
A simple way would be to store whether you had picked up the potion in PlayerPrefs.
Then you could do something like:
void OnTriggerEnter()
{
if(PlayerPrefs.GetInt("GotPotion", 0) == 0)
{
// You didn't get the potion yet, so get it
Destroy(gameObject);
PlayerPrefs.SetInt("GotPotion", 1); // got the potion
}
}
Then wherever you spawn your potion, you could have:
if(PlayerPrefs.GetInt("GotPotion", 0) == 1)
{
// Got the potion already, so don't spawn the potion
}
Or, you if you put the potion in the scene directly, you could do:
void Start()
{
if(PlayerPrefs.GetInt("GotPotion", 0) == 1)
{
// If the potion is already picked up, destroy it
Destroy(gameObject);
}
}
A much better way would be to write your own save system as Antnio Pedro Gonalves Ferreira suggested, but this will get you through the demo phase at least.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyPotionForever : MonoBehaviour
{
public bool potionCollide = false;
// Start is called before the first frame update
void Start()
{
}
void OnTriggerEnter(){
if(potionCollide == false){
Destroy(gameObject);
potionCollide = true;
}
}
// Update is called once per frame
void Update()
{
}
}
Just using global var. potionCollide . You created local variable instead of using variable global.
Is your potion on the scene before you start the game? Whatever happens during runtime does not permanently change the scene, if you want the potion to disappear forever it cannot be on the hierarchy of the scene before the game runs. When you reload a scene it resets to the state it was before you ran the game.
I've got OnCollisionEnter2D function, but it say's only once, when i`m landing on ground, i want function that will return object name(or smthng like this) if i'm touching it, so if i'll stay on it it will say me about it not only once but every frame.
It's as simple as using the correct method OnCollisionStay2D which is called every frame while you are colliding with another object
Sent each frame where a collider on another object is touching this object's collider (2D physics only).
To be fair: Their example in that link is bullshit since it is for OnTriggerStay2D ^^
It could look like
private void OnCollisionStay2D(Collision2D collision)
{
Debug.Log($"I am touching {collision.gameObject.name}", this);
}
If instead you want to keep track of every touching object I would rather use something like
private HashSet<GameObject> _currentlyTouching = new HashSet<GameObject>();
private void OnCollisionEnter2D(Collision2D collision)
{
if(!_currentlyTouching.Contains(collision.gameObject))
{
_currentlyTouching.Add(collision.gameObject);
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if(_currentlyTouching.Contains(collision.gameObject))
{
_currentlyTouching.Remove(collision.gameObject);
}
}
private void Update()
{
var logString = new StringBuilder("I am touching ");
foreach(var touchingObject in _currentlyTouching)
{
logString.Append(touchingObject.name).Append(" ");
}
Debug.Log(logString.ToString(), this);
}
So I have this script that affects another script just fine. It's attached to a gameobject (an attack box) that damages another gameobject (an enemy). It makes the enemy GameObject perform an animation (it getting hurt) and takes away a certain amount of health. That's all working fine.
What I'm stuck on is that I'm trying to get it to do the same for more than one type of enemy, therefore, accessing multiple scripts. The scripts are relatively the same and i've tested those out individually and both work fine. But when I try to have my attack box the script is attached to, affect more than one script, I get nothing. I figure it's just the way it's typed out and I've tried several ways already. But I've reverted it back to its most simple form to display it here. How do I get this script to work for both, so I don't have to have multiple scripts attached to one hitbox?
I should mention that in this script, it does access the first script mentioned in the OnTriggerEnter2D function. It just doesn't do it for any other scripts mentioned afterwards.
using UnityEngine;
using System.Collections;
public class slicer : MonoBehaviour {
public int damage = 5;
private foeHP foe;
private goblin gobby;
public float timer;
void Update()
{
Destroy ();
timer -= Time.deltaTime;
}
public void OnTriggerEnter2D (Collider2D other)
{
if (other.gameObject.tag == "Enemy") {
other.gameObject.GetComponent<foeHP> ().takeDamage (damage);
var foe = other.GetComponent<foeHP> ();
other.gameObject.GetComponent<goblin> ().takeDamage (damage);
var gobby = other.GetComponent<goblin> ();
}
if (foe == null) {
return;
}
if (gobby == null) {
return;
}
}
public void Destroy(){
if (timer <=0)
Destroy(gameObject);
}
}
Declare a generic Enemy class that all enemy types derive from.
public class Enemy : MonoBehaviour
{
int health;
public void TakeDamage(int amount)
{
health -= amount;
}
}
Change your enemy classes such that they all derive from Enemy
public class Goblin : Enemy
{
// Extra fields/methods
}
public class Foe : Enemy
{
// Extra fields/methods
}
Now you can simplify your checks into:
public void OnTriggerEnter2D (Collider2D other)
{
if (other.gameObject.tag == "Enemy")
{
other.GetComponent<Enemy>().TakeDamage(5);
}
}
Since both Goblin and Foe are type Enemy, GetComponent<Enemy>() will return their respective derived type and you can call TakeDamage() on them.
First of all, here's the code:
using UnityEngine;
using System.Collections;
namespace UltimateSurvival
{
public class Radiation : MonoBehaviour
{
public GameObject radiationEffect;
public EntityVitals Vitals { get { return m_Vitals; } }
private EntityVitals m_Vitals;
// Use this for initialization
void Start() {
InvokeRepeating ("OnTriggerEnter", 1.5f, 3.5f);
}
// Update is called once per frame
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
radiationEffect.SetActive(true);
//yield return new WaitForSeconds(5);
var entity = other.GetComponent<EntityEventHandler>();
if(entity)
{
var healthEventData = new HealthEventData(-Random.Range(7.0f, 23.0f));
entity.ChangeHealth.Try(healthEventData);
}
//yield return new WaitForSeconds(5);
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
radiationEffect.SetActive(false);
}
}
}
}
What I'm trying to do is that I want this script to execute OnTriggerEnter every 3.5 seconds. As you can see, I'm using InvokeRepeating but it seems like it doesnt work. I've also tried changing void OnTriggerEnter on IENumerator OntriggerEnter and then yield return new WaitForSeconds(5); - It didn't work either. I'm really confused D: Please help!
It seems you're trying to solve the problem of draining HP from the player if player is inside the area of radiation. This is a solution that will use most of your current code, but is not neccesarily the best code. I'd also like to inform you of OnTriggerStay, which
is called once per frame for every Collider other that is touching the trigger.
and can also be used to solve this problem. I'm going to use your already declared OnTriggerEnter and OnTriggerExit to damage every player inside area every 3.5 seconds.
public GameObject radiationEffect;
public EntityVitals Vitals { get { return m_Vitals; } }
private EntityVitals m_Vitals;
// Declare a list that will contain the players.
List<GameObject> playersInArea = new List<GameObject>();
// Use this for initialization
void Start() {
InvokeRepeating ("DamagePlayers", 1.5f, 3.5f);
}
void DamagePlayers() {
foreach (var player in playersInArea) {
var entity = player.GetComponent<EntityEventHandler>();
if(entity)
{
var healthEventData = new HealthEventData(-Random.Range(7.0f, 23.0f));
entity.ChangeHealth.Try(healthEventData);
}
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
playersInArea.Add(other.gameObject);
radiationEffect.SetActive(true);
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playersInArea.Remove(other.gameObject);
if (playersInArea.Count == 0) {
radiationEffect.SetActive(false);
}
}
}
Something like that should work. If you only have 1 player it should work all the same, but this supports multiple players. Let me know if you have any further issues.
You have problems calling your method using InvokeRepeating because of two reasons:
InvokeRepeating can not be used with method that have parameters: you probably have the Trying to Invoke method: [...] couldn't be called. message in your console.
OnTriggerEnter is a method that is automatically called by Unity when the gameobject's collider is set as Trigger and another collider enters it: as #Hellium said, calling such methods manually is a bad idea (same as calling Start or Update manually: this can happen but really doesn't make sense in most of the scenarios).
Hope this helps,