Unity: Possible to call Game Event from OnTriggerEnter? - c#

I'm attempting to call Game Event from an OnTriggerEvent method, but it's not working. I'm not seeing documentation on this, or else it's not clear to me from the Order of Execution chart, but it seems that OnTriggerEvent hides event passing.
I have a player object and a checkpoint object that have collider triggers. When the player collides with the checkpoint, the Checkpoint.cs script should fire a game event when the player enters.
Checkpoint.cs:
private void OnTriggerEnter(Collider hitObject) {
GameEvents.current.CheckpointReached(this);
}
The GameEvent.cs script:
public class GameEvents : MonoBehaviour
{
public event Action<Checkpoint> OnCheckpointReached;
public void CheckpointReached(Checkpoint checkpoint)
{
if (OnCheckpointReached != null)
{
Debug.Log("Checkpoint reached!");
OnCheckpointReached(checkpoint);
}
}
}
Other methods called from OnTriggerEnter are working fine, so it doesn't seem to be a collider issue, and Game Events do work for other objects in the scene.

Related

Unable to activate a previously deactivated gameObject (canvas) in Unity

For some reason Unity won't activate my Death Screen GUI-canvas after the player has died (or the GameObject of the player has been destroyed, or the GameObject == null). I'm able to deactivate it, but activating it after doesn't work for some reason. I get no error, so I have no idea what's wrong.
Here's the code in question:
public class DeathScreenMaster : MonoBehaviour
{
public GameObject player;
// Start is called before the first frame update
void Start()
{
gameObject.SetActive(false);
}
// Update is called once per frame
void Update()
{
if (player == null)
{
gameObject.SetActive(true);
}
}
}
You can't activate the game object by itself because it's already deactivated and Update() method is not executing anymore.
You have to have some other object that controls this object (DeathScreenMaster) activation and deactivation.

I don't know how to communicate with other scripts

I have a Player script. I have the variables on there
public class Player : MonoBehaviour
{
float horimove;
Rigidbody rb;
public float speed;
public GameObject thrown;
public bool win;
On my other script (I'm making a game where you instantiate spheres into a hole and win.)
public class spheresciprt : MonoBehaviour
{
public Player pscript;
public GameObject player;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("Player");
pscript = player.GetComponent<Player>();
}
private void OnTriggerEnter(Collider other)
{
if (gameObject.CompareTag("win"))
{
pscript.win = true;
}
}
However pscript.win doesn't change. The bool always stays false but I want the player to win after the sphere hits the trigger in the hole I want it to go to. I tried different computers. The same thing happened in different games I think so I'm probably just missing something.
First of all, make sure that the collision is detected. You can easily do something like
void OnTriggerEnter()
{
Debug.Log("Pass1");
if (gameObject.CompareTag("win"))
{
Debug.Log("Hey, it worked!");
}
}
in the OnTriggerEnter to check that.
If the collision is NOT detected, make sure there are colliders on all the affected GameObjects, as well as a RigidBody component on the sphere. Also, make sure that the collider is marked as Trigger. If you still have an issue with that, we will need to see your inspector to figure it out.
If the collision IS detected... Well, start by making sure that you have assigned the proper tag on the hole. Then, make sure that the player GameObject is actually called "Player" (since you are trying to find it by name) and that it has the script you are trying to get on the object you actually find (and not on one of its children). I hope that works, because I can't see anything else seriously flawed at your code.
Have you tried to change
if (gameObject.CompareTag("win"))
to
if (collider.gameObject.CompareTag("win"))
or just add the rigidbody to both player and other object.
does the other object have "win" tag?
Here is your script:
public class spheresciprt : MonoBehaviour
{
public Player pscript;
public GameObject player;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("Player");
pscript = player.GetComponent<Player>();
}
private void OnTriggerEnter(Collider other)
{
if (gameObject.CompareTag("win"))
{
pscript.win = true;
}
}
}
It sets pscript to the current values in Player script. This is the problem, because it is setting a variable to the current values, and you want access to the component. You could change this by, just replace all of the pscript variables with player.GetComponent<Player>(). There are other ways of doing this. You could, at the end of OnTriggerEnter, add this:
void OnTriggerEnter(Collider other)
{
...
player.GetComponent<Player>() = pscript;
}
In both of these solutions you fix it. In one, you re-apply the variable. In the other, you are changing the component directly. Which is what you want, because you have pscript as an instance of player script, not the player script that is attached to player.

How to have a button run while it is being pressed

Im trying to have a method continuously run while a button is pressed. At the moment the method only runs once when the UI button is pressed. I've tried implementing a coroutine but I couldn't figure out a way a condition for StopCoroutine() to run. I've also tried using the event triggers PointerUp and PointerDown that set a boolean to false and true that determine if a coroutine runs or not but that seemed to work.
Would anyone know how to implement a continuous on button hold? Any help is appreciated.
The method I want to continuously run while the button is held. This method is called in a PointerDown event trigger in the UI button.
public void spawnLaser()
{
GameObject laser = Instantiate(laserprefab, transform.position, Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(laserSpeed, 0);
}
Input.GetKeyDown returns true during the frame the user starts pressing down the key identified by name, while Input.GetKey returns true while the user holds down the key identified by name.
Seems you need to give Input.GetKey a try.
To keep it calling while pressed, call it from the Update().
public void spawnLaser()
{
GameObject laser = Instantiate(laserprefab, transform.position,
Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(laserSpeed, 0);
}
void Update()
{
if (Input.GetKey("up"))
{
print("up arrow key is held down");
spawnLaser();
}
}
In general you can just implement the IPointerDownHandler, IPointerUpHandler, IPointerExitHandler and IPointerEnterHandler interfaces like e.g.
public class WhilePressedButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler
{
public UnityEvent whilePressed;
// adjust according to your needs via the Inspector
[Min(0f)] public float delay = 0.3f;
public void OnPointerDown(PointerEventData pointerEventData)
{
StartCoroutine(ContinuousButton());
}
public void OnPointerUp(PointerEventData pointerEventData)
{
StopAllCoroutines();
}
public void OnPointerEnter(PointerEventData pointerEventData)
{
// Actually not really needed
// but not sure if maybe required for having OnPointerExit work
}
public void OnPointerExit(PointerEventData pointerEventData)
{
StopAllCoroutines();
}
private IEnumerator ContinuousButton()
{
// Whut? o.O
// No worries, this is fine in a Coroutine as long as you yield somewhere inside
while (true)
{
whilePressed.Invoke();
// Very important for not freezing the editor completely!
// This tells unity to "pause" the routine here
// render the current frame and continue
// from this point on the next frame
// If you want some delay between calls
if(delay > 0)
{
yield return new WaitForSeconds (delay);
}
else
{
// Otherwise it directly contineus in the next frame
yield return null;
}
}
}
}
And reference your spawnLaser method in whilePressed like usually in a Button onClick via the Inspector or in code.
Note: This is either on an UI element such as a UI.Button or on a 3D object with a collider but then you additionally require a PhysicsRaycaster component on your Camera.
You do NOT need an EventTrigger component for this! The interface methods OnPointerXY will simply be called by the UI itself!
However, are you really going to Instantiate a new object every frame while the button is pressed?
You should probably checkout Object Pooling or in general only activate and deactivate your object instead of Instantiate and delete it over and over again.
According to Unity Documentation:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;// Required when using Event data.
public class ExampleClass : MonoBehaviour, IPointerDownHandler// required interface when using the OnPointerDown method.
{
//Do this when the mouse is clicked over the selectable object this script is attached to.
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log(this.gameObject.name + " Was Clicked.");
}
}
Add the IPointerDownHandler interface to your class and implement the OnPointerDown(PointerEventData) method.
I recommend using a bool to track the state of the button (e.g true if pressed) and then in your Update() method, if the bool is true, make your lazer move.

OnTriggerEnter() is called right on the start

I'm making some sort of a Evolution simualtor game. I have a script that is supposed to destroy the GameObject it's attached to when a creature's CapsuleCollider Triggers the OnTriggerEnter().
I have a problem that even tho the Creature's collider isn't even close to the Food, it still destroys the GameObject.
My script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FoodEat : MonoBehaviour
{
public GameObject FoodGO;
public Rigidbody FoodRB;
private void OnTriggerEnter(Collider Creature)
{
Destroy(FoodGO);
}
void Start()
{
FoodRB = GetComponent<Rigidbody>();
FoodGO = FoodRB.gameObject;
}
void Update()
{
Rigidbody[] allRigidBodies = (Rigidbody[])FindObjectsOfType(typeof(Rigidbody));
foreach (Rigidbody body in allRigidBodies)
{
if (body.gameObject.layer == 10)
{
OnTriggerEnter(body.gameObject.GetComponent<CapsuleCollider>());
}
}
}
}
OnTriggerEnter is a monobehaviour lifecycle method. You should not call it from your own code; it will automatically be called when it detects collisions.
Furthermore, the logic in your code right now seems to be incorrect, it is...
"Every frame, loop through all rigidbodies in the scene and if 1 is found on layer 10, destroy the FoodGO"
Simply remove your entire Update method and put an if in your Collision method, and it should work:
[RequireComponent(typeof(Rigidbody), typeof(Collider))]
public class FoodEat : MonoBehaviour
{
private void OnTriggerEnter(collider other)
{
Debug.Log(other.gameObject.name + " on layer " + other.gameObject.layer);
if (other.gameObject.layer == 10)
Destroy(this.gameObject);
}
}
A few noteworthy edits of your code:
I removed FoodGO, since it's the GameObject this script is attached to, you can access it by just writing gameObject or this.gameObject.
I remove the Rigidbody reference since it is not used anymore, and thus the entire Start() method.
Since this code requires a Rigidbody and a Collider to work, I added a [RequireComponent] attribute in the top, which will make Unity tell you if you forgot to add those components on the object you attach this script to.
I added a Debug.Log that prints the name & the layer on the creature that collides with the food, so you can debug and make sure it is working as expected

Unity 2D - How to play death animation prefab

I have created a prefab with animation from sprite sheet, which I want to be played when the Player dies. I checked if the prefab is working by dragging it in the Scene, and it is correctly playing every frame of the sprite sheet in a loop endlessly.
Now I want to play this prefab when the Player dies, and after it ends to destroy it, but so far I am only able to place it where the player dies, and it stays there forever. Also there are some errors when that happens.
Here is the death script:
public class DmgByCollisionEnemy : MonoBehaviour {
public GameObject deathAnimation;
void Die() {
deathAnimation = (GameObject) Instantiate(deathAnimation, transform.position, transform.rotation);
//Destroy(deathAnimation);
Destroy(gameObject);
}
}
I set the deathAnimation by dragging a prefab in the Unity interface.
The error I am getting when the Die() method fires is
UnassignedReferenceException: The variable deathAnimation of DmgByCollisionEnemy has not been assigned.
You probably need to assign the deathAnimation variable of the DmgByCollisionEnemy script in the inspector.
So how can I do that properly?
You can try to add simple destroy script to Your death animation object that destroys object after time or trigger it in animation (Unity Manual: Using Animation Events). When you instantiate object it will appear on desired position and it will be destroied regardless to "main" object.
Destroy Script like this:
void DestroyMyObject()
{
Destroy(gameObject);
}
Script to run after time:
void Start()
{
Invoke ("DestroyMyObject", 1f);
}
void DestroyMyObject()
{
Destroy(gameObject);
}
Spawn script:
using UnityEngine;
using System.Collections;
public class SpawnExtra : MonoBehaviour {
public GameObject deathAnimation;
public static SpawnExtra instance;
void Start ()
{
instance = this;
}
public void SpawnDeathAnimation(Vector3 position)
{
Instantiate (deathAnimation, position, Quaternion.identity);
}
}
And you can use it when you want to spawn additional object like this:
SpawnExtra.instance.SpawnDeathAnimation (transform.position);
Now you have to add gameobject e.g ExtrasController, add script on it and you can spawn whatever you want. Remember to drag&drop animation prefab in inspector.

Categories

Resources