I am trying to do when i destroy all boxes something happen.
My code is;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class destroy : MonoBehaviour
{
private string BALL_TAG = "ball";
public AudioClip coin;
public AudioSource src;
public float numBox = 120f;
public bool isDestroyed;
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag(BALL_TAG))
{
src.clip = coin;
src.Play();
Destroy(gameObject);
isDestroyed = true;
}
}
private void Update()
{
boxes();
}
public void boxes()
{
if(isDestroyed == true)
numBox -= 1f;
if(numBox == 119)
SceneManager.LoadScene("mainManu");
}
private IEnumerator Two()
{
yield return new WaitForSeconds(1f);
Destroy(gameObject);
}
}
But it doesn't work.
It is suppose to do when I broke 1 box it sends me to menu.
I think its problem in "numBox -= 1f;" because I don't know hot to make this.
I don't understand your code completely. So, I need to make some assumptions.
I think the Script is attached to the box and every box has this Script. I also think, that your player Shoots Ball. Those Balls have a collider with an ball tag.
There are multiple problems with your code.
The first one is, that your count variable, numBox, is saved in your destroy Script, which is placed on each box.
this means, that every Box is counting for itself.
You have to centralize this. There are multiple ways for doing this.
One way is to declare this variable as static(https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/static)
This is not best practice, but works.
A Better way is to have a Script on Your Player, which holds this number and every Box searches for this Script and change this number if it is destroyed.
The second big Problem is, that your are doing some really weird thing in your Update and the collision handling
First of all, you are setting isDestroyed to true. Then in your boxes method, which is called in every Frame, you are decrementing your numBox variable by one, if this is Destroyed is true.
So if your Box gets hit, you are decrementing every frame.
After that you are checking every frame if your numBox is 119
If so, you change the Scene.
This is the reason, why you are getting to your MainMenu after only one boy
This behaviour is very weird, because it is totally unnecessary. You can reduce your variable directly in in your OnCollisionEnter2D Method.
There are some little things, which can be improved.
When you are trying to play a Sound, you don't have to specify the AudioClip in code. You can assign this directly in Unity on the AudioSource Component via drag and drop. This makes your code simpler.
You are not calling the Two Coroutine. You've specified this Coroutine but don't call it.
//Script on Player
public class PlayerBoxDestroyManager:MonoBehaviour
{
public int StartBoxes = 120;
private int Boxes;
private void Start()
{
Boxes = StartBoxes;
}
public void DestroyBox()
{
//Reduce our Boxes count
//This is equal to Boxes -= 1
// Boxes = Boxes -1
Boxes--;
// If we have less or zero Boxes left, we End call our EndGame methode
if(Boxes <= 0)
{
EndGame();
}
}
private void EndGame()
{
// We change the Scene to the mainMenu
SceneManager.LoadScene("mainManu");
}
}
```
//Script on all Boxes
public class Box : MonoBehaviour
{
public string Balltag = "ball";
//Audio Source the Audio Clip has to be assigned in the Unity editor
public AudioSource Coin;
private void OnCollisionEnter2D(Collision2D collision)
{
//Check it colliding Object has the right Tag
if(collision.transform.tag == Balltag)
{
//Get the reference to the Player Script
PlayerBoxDestroyManager PBDM = FindObjectOfType<PlayerBoxDestroyManager>();
//We can now access the Destroy Box Methode
PBDM.DestroyBox();
//Play the sound
Coin.Play();
//If we destroy our Object now, the Sound would also be deletet.
//We want to hear the sound, so we have to wait, till the sound is finished.
StartCoroutine(WaitTillAudioIsFinished());
}
}
IEnumerator WaitTillAudioIsFinished()
{
//we wait till the sound is finished
while (Coin.isPlaying)
{
yield return null;
}
//if finished, we destroy the Gameobject
Destroy(gameObject);
}
}
I hope I helped you. If you have questions, feel free to ask.
And sorry for my English:)
Related
I am making a gunshot code for a zombie shooter, but for some reason whenever I fire more than once in the same time period as the other shot, they don't overlap? Is there any way I can make it so that they do? Also, it only plays occasionally when I wait
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class audioplay : MonoBehaviour
{
public AudioSource source;
public AudioClip clip;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButton(0))
{
source.PlayOneShot(clip);
}
}
}
You wouldn't want a shoot function without any cooldown, especially in update. Also check your audio file to see if has blank space in it, like the one I used:
I used Audacity to trim the audio file. (it's free and open source :D )
Code stuff:
using UnityEngine;
public class MyPlayAudio : MonoBehaviour {
public AudioSource audioSource;
public AudioClip audioClip;
public float shootCooldown; //change this according to how fast you want to shoot
private float currentShootCooldown;
void Update()
{
//if you are holding mouse button down and we are ready to shoot
if(Input.GetMouseButton(0) && currentShootCooldown <= 0)
{
Pewpew();
}
//make sure you are not changing the public value
currentShootCooldown -= Time.deltaTime;
}
public void Pewpew()
{
//code whatever gun is supposed to do, like dealing damage
//resets cooldown for pewpew
currentShootCooldown = shootCooldown;
//plays the audio
audioSource.PlayOneShot(audioClip);
}
}
I think .Play might be better if we are thinking about a gun. I don't know, try both and see which one is better for your needs
audioSource.clip = audioClip; //this is not necessary if your gun is only going to do 1 sound.
audioSource.Play();
`
If you want it to overlap, you could for example add that component to two different gameobjects in the scene, that way there will be one frame where the function is called twice. Since Update would run on two objects.
so I'm wanting to pause the game once the amount of enemies hits 0. So I'm using GameObject.FindGameObjectsWithTag("Enemy").Length to find the number of enemies. I put this in a function that's called right when the enemies are instantiated so I can see the length go to 4, as there's 4 enemies spawning. When an enemy is killed the function is called again where the length is printed to console again. For some reason, on the first enemy killed the count repeats with a 4 again despite there only being 3 enemies. Once another enemy is killed it reports 3 when there's actually 2 and so on until I get to 1 when there's 0 enemies.
Here's the first snippet of code:
public class EnemyList : MonoBehaviour
{
public List<GameObject> weakMobs = new List<GameObject>();
public List<GameObject> mediumMobs = new List<GameObject>();
public List<GameObject> bossMobs = new List<GameObject>();
public List<Transform> spawningChildren = new List<Transform>();
public static int mobCount;
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0], spawningChildren[Random.Range(0, 4)]) as GameObject;
}
CheckMobCount();
}
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Length;
print(mobCount);
}
The next piece of code is where the enemy is killed and the CheckMobCount() is called again.
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
enemyList.CheckMobCount();
//needs death animations
}
}
Here's the console messages:
Console of printed lengths
I'm self taught so I apologize if this is elementary. I've tried doing this several different ways and this is the closest I've been but I'm open to new ideas as well.
Thank you!!
As noted in this answer, the object is not actually destroyed in the current frame.
From the documentation:
The object obj is destroyed immediately after the current Update loop… Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
I also agree that using DestroyImmediate() is a bad idea.
Ultimately, your question seems to really be about pausing the game when the enemy count reaches 0, which unfortunately hasn't actually been answered yet.
In fact, you don't really need to do anything different except move the check for the enemy count to the beginning of the Update() method, and pause the game there if it's 0. Then you'll find that the component for the enemy has been destroyed at that point.
Presumably enemies are spawned before the update loop starts (i.e. before the first frame), but if not then you can use whatever logic you're already using to decide that new enemies need to be spawned, to detect the fact that you haven't spawned any yet and avoid pausing before the enemies have spawned.
Here you have attached your script to your enemy instances. And they are still alive when you are querying for the number of enemies left.
You should do the following:
public class Enemy: MonoBehaviour
{
public static int EnemyCount = 0;
private void Start()
{
EnemyCount++;
}
private void OnDestroy()
{
EnemyCount--;
}
}
And then you can query the enemy count from anywhere but just excessing the EnemyCount by Enemy.EnemyCount.
If you want to get a more difficult example then you can check out this Game Dev tutorial: https://www.youtube.com/watch?v=LPBRLg4c5F8&t=134s
Destroy is actually executed at the end of the frame. There is DestroyImmediate that is executed immidiatelly but it's not recommended to be used. What I would do is to add a field or a property to identify whether the enemy is still alive and then to check against it. Something like:
class Enemy : MonoBehaviour
{
public bool IsAlive { get; set; } = true;
}
public class EnemyList : MonoBehaviour
{
//...
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Select(x => x.GetComponent<Enemy>()).Count(x => x.IsAlive);
print(mobCount);
}
}
And then:
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
this.GetComponent<Enemy>().IsAlive = false;
enemyList.CheckMobCount();
//needs death animations
}
}
This can be further optimized to store the Enemy somewhere and not use GetComponent every time but you get the idea.
As already mentioned by others the issue is that Destroy is executed delayed.
Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
You could simply count only the GameObjects that are still alive, those for which the bool operator is true.
Does the object exist?
It will be false for objects destroyed in that same frame.
E.g. using Linq Count
using System.Linq;
....
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Count(e => e);
which basically equals doing
mobCount = 0;
foreach(e in GameObject.FindGameObjectsWithTag("Enemy"))
{
if(e) mobCount++;
}
There is no need for an additional property or Component.
I am suggesting you to use “DestroyImmediate” instead of “Destroy”,Then look at the result.
I have a better idea, why not just use static variables when spawning enemies?
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0],
spawningChildren[Random.Range(0, 4)]) as GameObject;
mobCount++;
}
}
Do not use Linq
Do not use DestroyImmediate (it will freeze and bug your game, probably)
Avoid FindGameObjectsWithTag in loops, only in initialization.
Track your enemies in an array or list
When you destroy an enemy, remove it's reference from the list
Use the list count/length to get the real actual number.
I'm making a 2D game where you're in the middle of the screen and you move round an endless green (screen) world and white cubes spawn randomly around you, and I have finished the game mechanics and a main menu and game over screens. The one thing I'm trying to add now is a high score. I did a bit of research and found PlayerPrefs is probably the way to do it. I have a seperate scene for my main menu and my gameplay level (which includes the game over screen). I have no error messages. I have created a HSSetter (High Score Setter) script on the high score text in the main menu screen:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Start is called before the first frame update
void Start()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
// Update is called once per frame
void Update()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
}
and in my score script which is in my actual game level, here's the bit where I try to create the high score:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class score : MonoBehaviour
{
public int scoreCount = 0;
public int highScoreIFA;
void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
void Update()
{
if (scoreCount >= highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
}
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
}
}
}
In AddToScore(), I increment scoreCount.
Through some debugging, I have found that everything in the HSSetter script works - when I change the highScoreText.text, the text on screen changes, which led me to believe the issue might be with the change of scenes? Thanks!
Multiple things you should do here
The first you already updated in your question afterwards: You had the condition wrong and always updated only
if(highScoreIFA > scoreCount)
which would almost always be the case.
Now you have changed it to
if(scoreCount >= highScoreIFA)
which still is not good since if the score is equal there is no reason to update it, yet.
I would rather use
if(scoreCount > highScoreIFA)
so only really update it when needed.
Secondly in both scripts do not use Update at all! That is extremely inefficient.
I would rather use event driven approach and only change and set stuff in the one single moment it actually happens.
You should only one single class (e.g. the score) be responsible and allowed to read and write the PlayerPrefs for this. I know lot of people tent to use the PlayerPrefs for quick and dirty cross access to variables. But it is exactly this: Quick but very dirty and error prone.
If you change the keyname in the future you'll have to do it in multiple scripts.
Instead rather let only the score do it but then let other scripts reference it and retrieve the values directly from that script instead
And finally you should use
PlayerPrefs.Save();
to create checkpoints. It is automatically done in OnApplicationQuit, bit in case your app is force closed or crashes the User would lose progress ;)
Might look like
public class score : MonoBehaviour
{
public int scoreCount = 0;
// Use an event so every other script that is interested
// can just register callbacks to this
public event Action<int> onHighScoreChanged;
// Use a property that simply always invoked the event whenever
// the value of the backing field is changed
public int highScoreIFA
{
get => _highScoreIFA;
set
{
_highScoreIFA = value;
onHighScoreChanged?.Invoke(value);
}
}
// backing field for the public property
private int _highScoreIFA;
private void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
// Only update the Highscore if it is greater
// not greater or equal
if (scoreCount > highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
// Save is called automatically in OnApplicationQuit
// On certain checkpoints you should call it anyway to avoid data loss
// in case the app is force closed or crashes for some reason
PlayerPrefs.Save();
}
}
}
}
Then your other script only listens to the event and updates its display accordingly. It is even questionable if both scripts should not rather simply be one ;)
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Reference your Score script here
[SerializeField] private score _score;
private void Awake ()
{
// Find it on runtime as fallback
if(!_score) _score = FindObjectOfType<score>();
// Register a callback to be invoked everytime there is a new Highscore
// Including the loaded one from Start
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnDestroy()
{
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnHighScoreChanged(int newHighScore)
{
highScoreText.text = $"High Score: {newHighScore}";
}
}
I am coding a tower defense game in Unity, and I've ran into a snag while trying to figure out a way to place towers. My idea is to be able to click an art asset in the game when the player has a certain amount of points, and it replaces that art asset with a tower. Unfortunately, even when the player has the right amount of points, the object does not instantiate. I have made sure to link the prefab to the script, but it doesn't work. I'm stumped, the logic of the code seems right but maybe someone can help me figure out what's wrong here.
public class PointManager : MonoBehaviour
{
public int pointCount;
public Text pointDisplay;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
pointDisplay.text = "Points: " + pointCount;
}
}
public class PlaceTower: MonoBehaviour
{
public GameObject Tower;
private GameObject firstTower;
int placeCost = 25;
private PointManager pointsGained;
// Start is called before the first frame update
void Start()
{
pointsGained = GameObject.FindGameObjectWithTag("Point").GetComponent<PointManager>();
}
// Update is called once per frame
void Update()
{
}
private void OnMouseDown()
{
if (pointsGained.pointCount >= placeCost)
{
firstTower = Instantiate(Tower, transform.position, Quaternion.identity);
//Destroy(this.gameObject);
}
}
}
I got it. The problem was not with my code, but with my prefabs. In the documentation, I misread that OnMouseDown works on objects with colliders. While I had set up a circle collider on the object I was trying to instantiate, I had failed to put one on the object I was trying to instantiate from. Doing so fixed the problem immediately. A simple mistake that I would have completely glanced over had it not been for a second opinion. Thank you, Pac0!
I wrote a simple timer script, after the timer reaches 0 it loads a new scene. But it keeps continuously loading the scene instead of once not allowing the scene it loads to be played. I just need it to load the scene once.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
public float timelimit;
public Text text;
public void ChangeScene(int changeTheScene)
{
SceneManager.LoadScene(changeTheScene);
}
void Update()
{
timelimit -= Time.deltaTime;
text.text = "TimerText:" + Mathf.Round(timelimit);
if (timelimit < 0)
{
timelimit = 0;
SceneManager.LoadScene(1);
}
}
}
NOTE: your code as described won't cause the issue you describe. I see two possibilities:
your newly loaded scene has the same component in it, which repeatedly loads the scene
you are actually loading the scene additively so this component continues to run after the load.
Assuming the latter, if you follow your code through, you'll see that your if statement has a condition that will always be true once the timelimit is reached:
Every frame you subtract a number from timelimit. Then, if timelimit is now less than zero, set timelimit to zero and load the scene.
If you've set timelimit to zero in the previous frame, and then subtract a number, it will always be less than zero: you'll always load the scene again on each subsequent frame.
Try instead using a boolean variable to track whether you've loaded the scene or not. Or alternatively, destroy the component as soon as you load the scene, so that your code stops running.
If the problem is actually that you have this component in your new scene, too... consider removing it! :-)
EDIT:
Try instead using a boolean variable to track whether you've loaded
the scene or not.
public class Timer : MonoBehaviour
{
public float timelimit;
public Text text;
static bool loadedScene = false;
public void ChangeScene(int changeTheScene)
{
//SceneManager.LoadScene(changeTheScene);
}
void Update()
{
//Exit if we have already loaded scene
if (loadedScene)
{
//Destroy Timer Text
Destroy(text.gameObject);
//Destroy this Timer GameObject and Script
Destroy(gameObject);
return;
}
timelimit -= Time.deltaTime;
text.text = "TimerText:" + Mathf.Round(timelimit);
if (timelimit < 0)
{
timelimit = 0;
loadedScene = true; //We have loaded Scene so mark it true
SceneManager.LoadScene(1);
}
}
}
You're timelimit might not be initialized. Did you make sure it isn't set to 0 in the Unity Editor?