Edit - After testing, I discovered that only one elevator will animate. So if I walk into an elevator, the animation might not play on it, but it plays on another elevator. I noticed this by accident. I thought the animator would control the animation based upon which OnTriggerEnter2D I approached. Apparently, this caused the bug I'm experiencing.
How do I properly label the elevators so that only that elevator I'm at will run an animation?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Elevator : MonoBehaviour
{
private GameObject door;
[SerializeField]
private Text uiTxt; // Elevator UI text element
[SerializeField]
private Image uiBg; // Elevator Background image
[SerializeField]
private Image uiFg; // Elevator Foreground image
private void Start()
{
door = GameObject.FindWithTag("Elevator");
door.GetComponent<Animator>().SetBool("doorOpen", false);// Starts closed has to be TRUE to open
door.GetComponent<Animator>().SetBool("doorClose", false); // Starts closed has to be TRUE to re-close
uiBg.enabled = false;
uiFg.enabled = false;
uiTxt.enabled = false;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
door.GetComponent<Animator>().SetBool("doorOpen", true);
door.GetComponent<Animator>().SetBool("doorClose", false);
uiBg.enabled = true;
uiFg.enabled = true;
uiTxt.enabled = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
door.GetComponent<Animator>().SetBool("doorOpen", false);
door.GetComponent<Animator>().SetBool("doorClose", true);
uiBg.enabled = false;
uiFg.enabled = false;
uiTxt.enabled = false;
}
else {
door.GetComponent<Animator>().SetBool("doorOpen", false);
door.GetComponent<Animator>().SetBool("doorClose", false);
}
}
}
Here's a peek at the animator I have set up if it's helpful:
I have it set up to where the parameters are bool values. The door opens when doorOpen is true and doorClose is false. It will close when doorOpen is false and doorClose is true. However, it will do nothing if both values are false.
NOTE - I'm using Unity 2019.1 Beta
First, the issue was door = GameObject.FindWithTag("Elevator"); this will return the first GameObject it finds with that tag, this is why when you hit the 2 different triggers the same door was animating for both.
After finding out this script was already on your elevator, there is no need for the GameObject door, since every MonoBehavior has a reference to its own GameObject, you can just create an Animator variable, and call GetComponent() like this:
public class Elevator : MonoBehavior
{
Animator myAnim;
void Start()
{
myAnim = GetComponent<Animator>();
myAnim.SetBool("doorOpen", true);
myAnim.SetBool("doorClose", false);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
myAnim.SetBool("doorOpen", true);
myAnim.SetBool("doorClose", false);
uiBg.enabled = true;
uiFg.enabled = true;
uiTxt.enabled = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
myAnim.SetBool("doorOpen", false);
myAnim.SetBool("doorClose", true);
uiBg.enabled = false;
uiFg.enabled = false;
uiTxt.enabled = false;
}
else
{
myAnim.SetBool("doorOpen", false);
myAnim.SetBool("doorClose", false);
}
}
}
Related
I am new to coding and I am trying to create some code for my Oculus Quest 2 headset to be able to press a button and play a video. I tried using some code made with the help of someone else however that is just limited to using the mouse, can anyone help me in changing it so that it can be used for the VR headset or if it doesn't work help me create a new code to tackle my problem. Thank you for your time.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class TV : MonoBehaviour
{
[SerializeField] private Material defaultMat;
[SerializeField] private Material highlightMat;
private bool switchOn = false;
public GameObject targetObjectOn;
public GameObject targetObjectOff;
public Light tvlight;
///SFx references
public AudioClip buttonPress;
// Start is called before the first frame update
void Start()
{
tvlight.GetComponent<Light>().enabled = false;
targetObjectOn.gameObject.GetComponent<Renderer>().enabled = false;
targetObjectOn.gameObject.GetComponent<AudioSource>().volume = 0.0f;
targetObjectOff.gameObject.GetComponent<Renderer>().enabled = true;
}
private void OnMouseDown()
{
if (switchOn == false)
{
tvlight.GetComponent<Light>().enabled = true;
targetObjectOn.gameObject.GetComponent<Renderer>().enabled = true;
targetObjectOn.gameObject.GetComponent<AudioSource>().volume = 1.0f;
targetObjectOff.gameObject.GetComponent<Renderer>().enabled = false;
GetComponent<AudioSource>().PlayOneShot(buttonPress, 1.0F);
}
else if (switchOn == true)
{
tvlight.GetComponent<Light>().enabled = false;
targetObjectOn.gameObject.GetComponent<Renderer>().enabled = false;
targetObjectOn.gameObject.GetComponent<AudioSource>().volume = 0.0f;
targetObjectOff.gameObject.GetComponent<Renderer>().enabled = true;
GetComponent<AudioSource>().PlayOneShot(buttonPress, 1.0F);
}
switchOn = !switchOn;
}
private void OnMouseOver()
{
////highlight the button ie swap materials
gameObject.GetComponent<Renderer>().material = highlightMat;
}
private void OnMouseExit()
{
////highlight the button ie swap materials
gameObject.GetComponent<Renderer>().material = defaultMat;
}
}
The player spawns in a different location than the chest
1. So what it should do is first to go to Update Method which is obvious but it cant access the result of the first
if statement because TouchingChestBoxCollider is still false (at the beginning).
2. If the Player reaches the the trigger range of the Chest, then TouchingChestBoxCollider = true
3. Next it should go again to the Update() I know Update is called every frame but i want to make the order clear. The if Statement is now "valid" and xIsPressed = true so OnTriggerStay2D can go to DropDownItem and the rest is irelevant.
What the Problem looks like : i can still Press X outside the CircleCollider2D range of the Chest and when i already have reached the range the Chest, it drops the items even though it should do it when im in the in the range not outside (press x)
Do i have a mistake in the Code and if yes where pls help me
What the Code should do at the end: In the Range of The Chest you can press X and the items spawns
if not in range and you still press x and then go to the chest range it will not spawn BUT WHAT IT DOES XD.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChestOpener : MonoBehaviour
{
[SerializeField]
private GameObject ChestItSelf;
[SerializeField]
private GameObject[] itemsWeapon;
[SerializeField]
private GameObject[] itemsMedi;
[SerializeField]
private SpriteRenderer spriteRenderer;
[SerializeField]
private Sprite openSprite, closeSprite;
private int random;
private Vector2 destination;
private GameObject objectsWeapon;
private GameObject objectsWeapon2;
private GameObject objectsMedi;
private bool HasItDropped = false;
private bool MoveItemNow = false;
private bool xIsPressed = false;
private bool TouchingChestBoxCollider = false;
private void Update()
{
if (Input.GetKeyDown(KeyCode.X) && TouchingChestBoxCollider == true)
{
xIsPressed = true;
}
if (MoveItemNow == true ) //it should always stay at the Update function or else it will not render the move (lag)
{
objectsWeapon.transform.position = Vector2.Lerp(objectsWeapon.transform.position, new Vector2(transform.position.x, transform.position.y - 4),
Time.deltaTime * 1f);
objectsWeapon2.transform.position = Vector2.Lerp(objectsWeapon2.transform.position, new Vector2(transform.position.x-2, transform.position.y - 4),
Time.deltaTime * 1f);
}
}
private void OnTriggerStay2D(Collider2D collision)
{
TouchingChestBoxCollider = true;
if (collision.gameObject.tag == "Player" && HasItDropped == false && xIsPressed == true)
{
DropDownItem();
}
}
private void DropDownItem()
{
spriteRenderer.sprite = openSprite;
random = UnityEngine.Random.Range(0, itemsWeapon.Length);
objectsWeapon = Instantiate(itemsWeapon[random], transform.position, Quaternion.identity);
objectsWeapon2 = Instantiate(itemsWeapon[random], transform.position, Quaternion.identity);
MoveItemNow = true;
HasItDropped = true;
}
private void OnTriggerExit2D(Collider2D collision)
{
spriteRenderer.sprite = closeSprite;
Invoke("FareWellChest", 2f);
}
private void FareWellChest()
{
if (HasItDropped == true)
{
Destroy(ChestItSelf);
}
}
}
you seem to never ever reset TouchingChestBoxCollider and should probably do so in
private void OnTriggerExit2D(Collider2D other)
{
TouchingChestBoxCollider = false;
spriteRenderer.sprite = closeSprite;
Invoke("FareWellChest", 2f);
}
and also never reset xIsPressed and should do so in
private void OnTriggerStay2D(Collider2D collision)
{
TouchingChestBoxCollider = true;
if (collision.gameObject.CompareTag("Player") && !HasItDropped && xIsPressed)
{
DropDownItem();
}
xIsPressed = false;
}
Actually if instead of OnTriggerStay2D you rather used OnTriggerEnter2D in order to make relevant checks only once you could simplify the logic into
private void Update()
{
if (Input.GetKeyDown(KeyCode.X) && TouchingChestBoxCollider)
{
DropDownItem();
}
...
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Player") && !HasItDropped)
{
TouchingChestBoxCollider = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
TouchingChestBoxCollider = false;
}
}
so you wouldn't need the xIsPressed at all.
In genera when dealing with Physics and Collision detection you should avoid moving any object via Transform in Update but rather go through the Rigidbody2D in FixedUpdate.
How can I make my player die when he touches two different gameObjects with the different tags. The player should die when he touches "enemy" tag and "ground" tag. Also, I am not trying to use the same enemy tag for ground objects because I'm already using "enemy" tag for my other script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move2d : MonoBehaviour
{
public float playerSpeed; //allows us to be able to change speed in Unity
public Vector2 jumpHeight;
public bool isDead = false;
private Rigidbody2D rb2d;
private Score gm;
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
gm = GameObject.FindGameObjectWithTag("gameMaster").GetComponent<Score>();
}
void Update()
{
if (isDead) { return; }
transform.Translate(playerSpeed * Time.deltaTime, 0f, 0f); //makes player run
if (Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.Space)) //makes player jump
{
GetComponent<Rigidbody2D>().AddForce(jumpHeight, ForceMode2D.Impulse);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("ground")) // this will return true if the collision gameobject has ground tag on it.
{
isDead = true;
rb2d.velocity = Vector2.zero;
GameController.Instance.Die();
}
}
void OnTriggerEnter2D(Collider2D col)
{
if( col.CompareTag("coin"))
{
Destroy(col.gameObject);
gm.score += 1;
}
}
}
I think you may have asked the question wrong, and that you mean the player will die if he hits to ground or an enemy, rather than both at the same time. An easy fix is doing as Nikola suggested with a minor change:
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("ground") || collision.gameObject.CompareTag("enemy"))
{
isDead = true;
rb2d.velocity = Vector2.zero;
GameController.Instance.Die();
}
}
OnCollisionEnter will be individually called once for every object that starts touching your player during that frame.
You will need to create a way to track over multiple call to OnCollisionEnter the group of objects touching your player at that point. One way is to create a boolean for each type of tag you are checking:
private bool touchedGround = false;
private bool touchedEnemy = false;
private void LateUpdate() {
touchedGround = false;
touchedEnemy = false;
}
private void OnCollisionStay2D(Collision2D collision) {
if (collision.gameObject.CompareTag("ground")) {
touchedGround = true;
}
if (collision.gameObject.CompareTag("enemy")) {
touchedEnemy = true;
}
if (touchedGround && touchedEnemy) {
isDead = true;
rb2d.velocity = Vector2.zero;
GameController.Instance.Die();
}
}
At the end of each OnCollisionEnter you are verifying whether or not both tags have been encountered. In LateUpdate you reset the flags to false, so that the next frame you recheck.
As a side note, this solution is not very extensible. You may wish to use layers (LayerMask) and a bit of boolean logic allowing you to compare multiple categories in one operation. But that would be a different question.
Hello why not just do this
private void OnCollisionEnter2D(Collision2D collision)
{
switch (collision.gameobject.tag)
case "tag_here"
isDead = true;
rb2d.velocity = Vector2.zero;
GameController.Instance.Die();
break;
case "tag_here2"
isDead = true;
rb2d.velocity = Vector2.zero;
GameController.Instance.Die();
break;
case "tag_here3"
isDead = true;
rb2d.velocity = Vector2.zero;
GameController.Instance.Die();
break;
//Copy paste as long as you need more
}
switch statements are like shorter version of if statements.
You could use the following approach, it's called when Enemy || Gound Tag enters && exits.
private bool enemy, ground;
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
enemy = true;
}
if (collision.gameObject.CompareTag("Ground"))
{
ground = true;
}
if(enemy && ground)
{
Debug.Log("Enemy and Ground collision");
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
enemy= false;
}
if (collision.gameObject.CompareTag("Ground"))
{
ground = false;
}
}
I'm making a Mario replica in unity for my homework, and I'm trying to make the "Invisible" block, it starts off invisible, then when hit, it turns visible. I'm trying to use SpriteRenderer.enable to make it work, it works for turning it off in the start, but not when trying to make it visible.
I've tried to create a separate script for this particular block, but results are the same. All the tags are set correctly, I've tried using Debug.log to see if I enter the "if" where the sprite should be enabled, but the result is negative.
This is the start method turning off the sprite renderer for the particular block (it works):
private void Start()
{
//rendObject = this.gameObject.GetComponent<SpriteRenderer>();
if (gameObject.tag == "Invisible")
{
gameObject.GetComponent<SpriteRenderer>().enabled = false;
}
}
This is all the blocks script:
private void OnCollisionEnter2D(Collision2D collision)
{
if (timesToBeHit > 0)
{
if (collision.gameObject.tag == "Player" && IsPlayerBelow(collision.gameObject))
{
if (gameObject.tag == "Invisible")
{
gameObject.GetComponent<SpriteRenderer>().enabled = true;
}
collision.gameObject.GetComponent<PlayerController>().isJumping = false; //Mario can't jump higher
Instantiate(prefabToAppear, transform.parent.transform.position, Quaternion.identity); //instantiate other obj
timesToBeHit--;
anim.SetTrigger("GotHit"); //hit animation
}
}
if (timesToBeHit == 0)
{
anim.SetBool("EmptyBlock", true); //change sprite in animator
}
}
We've found a solution in chat, but for all people who may run or have run on this kind of problem will need to check for the next things:
Must have 2 Colliders of any type, 1 per gameObject.
At least 1 Rigidbogy.
Appropriately Collider setup.
Appropriate Tags.
Appropriate Layer Collision Matrix.
The code below will work.
public SpriteRenderer render;
void Start()
{
render.enabled = false;
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Player")
{
render.enabled = true;
}
}
public class InvisibleBlock : MonoBehaviour
{
public SpriteRenderer rendObject;
private void Start()
{
if (gameObject.tag == "Invisible")
{
rendObject.enabled = false;
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Player")
{
rendObject.enabled = true;
}
}
}
Separate script, sprite is attached in inspector, same results.
I'm working on a game and I have a my player with the tag "Player". I have a cyclist spawning system, and each cyclist has the tag "cyclist", they are all prefabs. I have a script on the cyclists prefab that makes it move forwards, so each time it spawns, it instantly begins to move in a fixed direction.
I want the cyclists to be able to detect 2 things, one being if the player is in front of it, and the other if another cyclist is in-front of it. If so, I want the cyclist to stop cyclist. I have a script called cyclistStoping.cs that I'm using to do this. The script has been placed onto the cyclist prefab for multiple instances to be spawned.
Bug
I've noticed that sometimes even once a cyclist(a) is no longer in front of another cyclist(b), cyclist(b) will still remain still, and it will only move off again if the player walks in front of it and then walks off again. I feel like my code is just buggy and would really appreciate some help on this.
I've tried to do 2 simple checks inside each trigger function but some cyclists still remain idle when once the cyclist is no longer in front of them.
I have also tried using a delay function so whenever the cyclist or player are no longer in range, the cyclist doesn't move off until 2-3 seconds have passed. However with this, the cyclist doesn't move off again.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cyclistStoping : MonoBehaviour
{
public VehicleMove cyclistMovement;
public bool playerInside = false;
public bool cyclistInside = false;
private bool timePassed = false;
void Start()
{
}
void Update()
{
}
// implement delay
// player or cyclist (INSIDE)
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player" ) {
playerInside = true;
cyclistInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
playerInside = false;
cyclistInside = true;
}
if (playerInside == true || cyclistInside == true) {
Debug.Log("Player inside: " + playerInside);
cyclistMovement.vehicleMovement = 0.0f;
}
}
// player or cyclist (EXIT)
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playerInside = false;
cyclistInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
playerInside = false;
cyclistInside = false;
}
if (playerInside == false || cyclistInside == false)
{
if (timePassed == true) {
Debug.Log("Player inside: " + playerInside);
cyclistMovement.vehicleMovement = 0.1f;
// delay, then move off
timePassed = false;
}
}
}
IEnumerator timeDelay()
{
// wait before moving off
yield return new WaitForSeconds(3);
timePassed = true;
}
}
I expect the cyclist to stop if a player is in front of it, and once the next cyclist spawn, I expect THAT cyclist to stop once it detects the first cyclist. Once the player moves away from the first cyclist, it should take 2 seconds before moving off, and the second cyclist will do exactly the same.
The main problem in your code is that the logic to make the bike move again should be in the IEnumerator, not the OnTriggerExit. Start the Coroutine in OnTriggerExit:
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playerInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
cyclistInside = false;
}
if (playerInside == false && cyclistInside == false)
{
StartCoroutine(timeDelay());
}
}
And check again in the IEnumerator:
IEnumerator timeDelay()
{
// wait before moving off
yield return new WaitForSeconds(3);
if (playerInside == false && cyclistInside == false) {
cyclistMovement.vehicleMovement = 0.1f;
}
}