Hey so I'm building a baseball game and I'm using OnTriggerEnter to detect whether or not the baseball enters the strike zone. The strike zone is big enough so it can be detected and the ball isn't moving too fast. I want to display ("STRIKE!) once it hits the trigger, but nothing is happening. pls
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Strike : MonoBehaviour {
public AudioSource src;
public ParticleSystem stars;
public Text StrikeText;
private void Start()
{
StrikeText.text = " ";
}
public void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "ball")
{
src.Play();
Instantiate(stars, transform.position, transform.rotation);
StrikeText.text = "STRIKE!";
Destroy(other.gameObject);
SecondsWait();
StrikeText.text = " ";
}
}
IEnumerator SecondsWait()
{
yield return new WaitForSeconds(4);
}
That's not how coroutines work.
This is what your code is doing, line by line:
src.Play();
Instantiate(stars, transform.position, transform.rotation);
StrikeText.text = "STRIKE!";
Destroy(other.gameObject);
All of that runs as normal, just as you expect/want
SecondsWait();
This invokes a function that then invokes yield return new WaitForSeconds(4); As your code doesn't do anything with this return value, nothing happens, and the WaitForSeconds object gets garbage collected.
StrikeText.text = " ";
Then this line runs immediately, clearing out the text you'd previously set.
This is what you probably actually want:
src.Play();
Instantiate(stars, transform.position, transform.rotation);
Destroy(other.gameObject);
StartCoroutine(SecondsWait());
//...
IEnumerator SecondsWait()
{
StrikeText.text = "STRIKE!";
yield return new WaitForSeconds(4);
StrikeText.text = "";
}
By calling StartCoroutine() and passing the return of SecondsWait() to it, the SecondsWait() method itself (not OnTriggerEnter!) pauses when the yield occurs.
By the way this kind of code architecture is very poor. UI should never be related directly on your domain. You should consider using event-driven approach, even if you are just a very beginner.
Related
THIS POST IS NOW IRRELEVANT PLEASE CHECK MY NEWER ONE
I am making a horror game with next bots on VR (Quest/Quest2)
the player has 1 "Mixed" light on it and the bots use pathfinding AI with navmeshes and stuff but it seems that when the next bot gets within I'll say like 10 feet of the player and the player can see it and all the game just decides it wants to freeze and stop working!
It seems to happen before the player "Dies" but I'm not fully sure.
public class Kill : MonoBehaviour
{
// Start is called before the first frame update
public GameObject tes;
public GameObject LocoToOff;
public Vector3 SPOT;
public GameObject SprintLoco;
public GameObject Dead;
public AudioSource audioSource;
private bool Using;
public GameObject Safe;
public LocomotionControllery Loco;
private bool ChillOutItsGoing;
IEnumerator TurnOff()
{
ChillOutItsGoing = true;
LocoToOff.SetActive(false);
SprintLoco.SetActive(false);
Dead.SetActive(true);
Loco.Disabled = true;
audioSource.Play();
yield return new WaitForSeconds(3);
//foreach (var pls in WallsNStuff)
//{
// pls.SetActive(false);
//}
tes.transform.position = SPOT;
//yield return new WaitForSeconds(0.05f);
Loco.Disabled = false;
Dead.SetActive(false);
//foreach (var pls in WallsNStuff)
//{
// pls.SetActive(true);
//}
Safe.SetActive(true);
ChillOutItsGoing = false;
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "BOT")
{
if (ChillOutItsGoing == false)
{
Debug.Log(other.gameObject.name);
Using = LocoToOff.active;
StartCoroutine(TurnOff());
}
}
}
}
I believe it ended up just being because the thing was set to "Cutout" instead of opaque and Cutout I think is in alpha only 2 things I changed that fixed it for sure was setting it to opaque and adding a lil bit of smoothness (just did that idk if it was part of the fix or not)
Using unity 2020.3 and the XR Plug-in (currently only oculus but will be moving to openxr i hope) and trying to start the microphone when secondary button is pushed. It works but starting the microphone causes lag. Coroutine doens't help, I tried threading which stops the lag but then can't do anything with the audioclip. This has been asked a few times over the years but no answer yet. Here's the code:
void Update()
{
foreach(var d in devices){
if (d.TryGetFeatureValue(CommonUsages.secondaryButton, out isPressed)){
if (isPressed && !wasTalking)
{
wasTalking = true;
asource.PlayOneShot(walkietalkie);
//start_recording = new Thread(startRecording);
//start_recording.Start();
startRecording();
}
else if (wasTalking && !isPressed){
finishRecording();
wasTalking = false;
}
}
private void startRecording(){
recording = Microphone.Start(null, false, 30, freq);
startRecordingTime = Time.time;
yield return null;
}
Edit: I've removed the useless coroutine. Why the -1 to my question?
The primary way I reckon that you could alleviate the stutter is moving this work to another thread, which is notoriously hard with Unity.
A solution to this could be using or adapting another method of recording audio like NAudio.
Use coroutines.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class TestMic : MonoBehaviour
{
[SerializeField] AudioSource self;
public int samplerate = 44100;
public int time = 10;
// Start is called before the first frame update
void Start()
{
//Debug.Log(Microphone.devices);
StartCoroutine("test");
}
IEnumerator test()
{
self.clip = Microphone.Start(null, true, time, samplerate);
self.loop = true;
self.Play();
yield return null;
}
// Update is called once per frame
void Update()
{
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirePistol : MonoBehaviour {
public GameObject TheGun;
public GameObject MuzzleFlash;
public AudioSource GunFire;
public bool IsFiring = false;
void Update () {
if (Input.GetButtonDown("Fire1"))
{
if (IsFiring == false)
{
StartCoroutine(FiringPistol());
}
}
}
IEnumerator FiringPistol ()
{
IsFiring = true;
TheGun.GetComponent<Animation>().Play("PistolShot");
MuzzleFlash.SetActive(true);
MuzzleFlash.GetComponent<Animation>().Play("MuzzleAnim");
GunFire.Play();
yield return new WaitForSeconds(0.5f);
IsFiring = false;
}
}
I am writing a gun mechanic And
I wonder why we need
yield return new WaitForSeconds(0.5f); .What is the difference without this command .It's really unnecessary to write this code coz the time is short .Afterall , will it be an error like scene crash after i deleting this code ?Any help is greatly appreciated !
In general: Every method returning IEnumerator has to contain at least one yield statement. In Unity you have to use StartCoroutine to run an IEnumerator as Coroutine.
In your specific case: So you can delay your code by 0.5 seconds!
It is short but 0.5 is about 30 frames!
Someone using e.g. something like AutoClicker could jam the fire key each frame so he would cause significantly more damage then someone playing "normal" (due to physical limitations of your keyboard and finger ;) )
You are just avoiding that and limit down firing to a maximum of 2x per second.
In general - as usual - there are multiple ways to achieve that and you could go without Coroutines entirely but it makes coding so much cleaner and easier to maintain then doing everything in Update!
As some alternative examples for simple delays as here you could also either do a simple timer in Update
private float timer;
void Update ()
{
if(timer > 0)
{
// reduce the timer by time passed since last frame
timer -= Time.deltaTime;
}
else
{
if(Input.GetButtonDown("Fire1"))
{
FiringPistol();
timer = 0.5f;
}
}
}
void FiringPistol()
{
TheGun.GetComponent<Animation>().Play("PistolShot");
MuzzleFlash.SetActive(true);
MuzzleFlash.GetComponent<Animation>().Play("MuzzleAnim");
GunFire.Play();
}
or you can also use Invoke with a given delay.
bool canFire;
void Update ()
{
if (Input.GetButtonDown("Fire1") && canFire)
{
FiringPistol();
}
}
void FiringPistol()
{
TheGun.GetComponent<Animation>().Play("PistolShot");
MuzzleFlash.SetActive(true);
MuzzleFlash.GetComponent<Animation>().Play("MuzzleAnim");
GunFire.Play();
Invoke(nameof(AfterCooldown), 0.5f);
}
void AfterCooldown()
{
canFire = true;
}
In general btw you should store the Animation references to not use GetComponent over and over again:
// if possible already reference these via the Inspector
[SerializeField] private Animation theGunAnimation;
[SerializeField] private Animation muzzleFlashAnimation;
private void Awake()
{
// as fallback get them on runtime
// since this is a fallback and in best case you already referenced these via the Inspector
// we can save a bit of resources and use GetComponent
// only in the case the fields are not already set
// otherwise we can skip using GetComponent as we already have a reference
if(!theGunAnimation) theGunAnimation = TheGun.GetComponent<Animation>();
if(!muzzleFlashAnimation) muzzleFlashAnimation = MuzzleFlash.GetComponent<Animation>();
}
then later you reuse them
theGunAnimation.Play("PistolShot");
MuzzleFlash.SetActive(true);
muzzleFlashAnimation.Play("MuzzleAnim");
GunFire.Play();
I currently have some objects spawning from a prefab and am attempting to destroy only one of the spawned items of that prefab. I was searching online and I found tons of different examples but I have been unable to get any of them to work. I tried setting up an instance of the Instantiate and destroying that instance but I am unable to get it to work. The spawn/collision script is attached to the main camera if that matters. Collision with other items in my game work and the prefab does have a box collider set to isTrigger. Again, I know there are plenty of examples explaining this etc, but I can't get it to work and maybe I am not understanding what I actually should be doing.
Spawner code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bloodVialSpawner : MonoBehaviour
{
public GameObject vialOfBlood;
private GameObject vialss;
private int hunger =10;
public int numOfVials;
public int minSpawnRange, maxSpawnRange;
public int minSpawnRange2, maxSpawnRange2;
// Start is called before the first frame update
float timeSpawns = 2;
List<GameObject> vialsInstantiated = new List<GameObject>();
void Start()
{
StartCoroutine(becomeHungry());
InvokeRepeating("SpawnVials", timeSpawns, timeSpawns);
}
private void Update()
{
if (hunger == -1)
{
Debug.Log("sigh");
}
}
void SpawnVials()
{
for (int i = 0; i < numOfVials; i++)
{
vialsInstantiated.Add(Instantiate(vialOfBlood, SpawnPosition(), Quaternion.identity) as GameObject);
}
}
Vector3 SpawnPosition()
{
int x, y, z;
y = 59;
x= UnityEngine.Random.Range(minSpawnRange, maxSpawnRange);
z = UnityEngine.Random.Range(minSpawnRange2, maxSpawnRange2);
return new Vector3(x, y, z);
}
IEnumerator becomeHungry()
{
while (true)
{
hunger -= 1;
yield return new WaitForSeconds(1);
Debug.Log(hunger);
}
}
}
Spawner Script is on the Main Camera. Player used is the First Person Player Unity provides.
Code for destroying spawned object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class destroyVial : MonoBehaviour
{
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "vials")
{
Destroy(col.gameObject);
Debug.Log("yell");
}
}
}
Destroy code is on prefab. Note prefab is not in hierarchy as it should not be.
Firstly,
I see that you're spawning things in a for-loop and overwriting the vialss variable every time:
for (int i = 0; i < numOfVials; i++)
{
vialss = Instantiate(vialOfBlood,
SpawnPosition(),
Quaternion.identity) as GameObject;
}
And then, on collision, you're Destroying vialss, which in this case will be the latest spawned object. And if you collide with anything after 1 collision, vialss will already be deleted and probably throw an exception. Maybe that's fine in your game, but the logic looks a bit flawed.
Also, I'm assuming you want to destroy the object you're colliding with? Does something like this not work?
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "vials")
{
Destroy(col.gameObject); // <== Remove colliding object
Debug.Log("yell");
}
}
If you, for some unrelated reason, need a list of all your spawned vials, maybe you'd like to convert that to a list instead:
List<GameObject> spawnedVials = new List<GameObject>();
void SpawnVials()
{
for (int i = 0; i < numOfVials; i++)
{
var vial = Instantiate<GameObject>(vialOfBlood,
SpawnPosition(),
Quaternion.identity)
spawnedVials.Add(vial);
}
}
Finally,
make sure that the collision detection is working. You're saying that the script is attached to your camera. please make sure you have a Collider on the camera. But you're saying that other colliders are working, so I'm guessing you have this under control.
I'd guess your issue lies in the flawed logic I initially described.
OnTriggerEnter needs to be on a script attached to the object it's colliding with, or the vial itself. It can't be on the main camera as OnTriggerEnter will never get called.
I would recommend you to keep scripts to one job, you should split that script into a Spawn script and a Collider script. And create a empty GameObject with the sole purpose of spawning prefabs.
Also there's some errors in your code:
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "vials")
{
Destroy(col.gameObject); // Destroy the gameObject you're colliding with
Debug.Log("yell");
}
}
Also the variable vialss isn't doing what you're expecting, vialss is only referencing to the last instantiated vial, so better save all vials in a List:
List<GameObject> vialsInstantiated = new List<GameObject>();
And then:
void SpawnVials()
{
for (int i = 0; i < numOfVials; i++)
{
vialsInstantiated.Add(Instantiate(vialOfBlood, SpawnPosition(), Quaternion.identity) as GameObject);
}
}
I'm a complete unity novice. I want to make a simple scene where you have three lives and you lose a live if you collide with a cube. This is my script:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Lives : MonoBehaviour {
public Transform player;
public static int lives;
public Image live1;
public Image live2;
public Image live3;
// Use this for initialization
void Start () {
lives = 3;
live1.enabled = true;
live2.enabled = true;
live3.enabled = true;
}
void Update () {
DisplayOfHearts();
}
public static void Damage() {
lives -= 1;
}
public void OnCollisionEnter(Collision col) {
if (col.gameObject.tag == "cube") {
Lives.Damage();
}
}
public void DisplayOfHearts() {
if (lives == 2) {
live3.enabled = false;
}
else if (lives == 1) {
live2.enabled = false;
}
else if (lives == 0) {
live1.enabled = false;
}
}
}
What happens is the player can't move through the cube but the amount of lives stays three. Is there something I'm missing?
The problem is you have attached the script to a wrong game object. The script and the collider must be attached to the same game object.
Unity methods inside a MonoBehaviour script (such as OnEnable, Update, FixedUpdate, Awake, Start, OnTriggerEnter, OnCollisionStay, etc..) only work for the game object which the script is attached to.
If you attach the script to another game object don't expect any of those to work. Update only works while that game object is active. OnCollisionEnter only works when a collision occurs on a collider which is attached directly to that game object. (it doesn't even work when a child has the collider instead of the actual game object where script is attached to)