I'm totally newbie in Unity and C#, and will apprecaite your Help.
i'm trying to make tower Defense Game,with help of Tutorial, but it seems to be a Problem with while under my Game Loop,it will freez each time i use While. and something else that i notice is EnemyIDsToSummon.Count is whole the time 0.
here you can see my GameLoop:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameLoopManager : MonoBehaviour
{
private static Queue<int> EnemyIDsToSummon;
public bool LoopShouldEnd;
private void Start()
{
EnemyIDsToSummon= new Queue<int>();
EntitySummoner.Init();
StartCoroutine(GameLoop());
InvokeRepeating("SummonTest", 0f, 1f);
}
void SummonTest(){
EnqueueEnemyIDToSummon(1);
}
IEnumerator GameLoop(){
while(LoopShouldEnd==false){
//Spawn Enemies
if(EnemyIDsToSummon.Count>0)
{
for(int i=0;i<EnemyIDsToSummon.Count;i++){
EntitySummoner.SummonEnemy(EnemyIDsToSummon.Dequeue());
}
}
//Spawn Towers
//Move Enemies
//Tick Towers
//Apply Effects
//Damge Enemies
//Remove Enemies
// remove Towers
}
yield return null;
}
public static void EnqueueEnemyIDToSummon(int ID){
EnemyIDsToSummon.Enqueue(ID);
}
}
and here is the tutorial i've used:
https://www.youtube.com/watch?v=-Pj5o5I_Wl4&t=1263s
You've written a Unity coroutine GameLoop, which has a while loop in it.
The engine will enter the while loop and never leave, because nothing sets LoopShouldEnd to true, or breaks out of the loop.
Usually, you'd fix this by yielding from the coroutine (yield return null) from inside the while loop, and setting LoopShouldEnd to false on the frame you want it to stop, or ending the while loop once you're done spawning inside that frame (you can break to end it immediately)
When the yield is outside the while loop, it will only happen after the while loop finishes, which is never, so no other code runs and the editor freezes.
Some useful documentation on coroutines:
https://docs.unity3d.com/Manual/Coroutines.html
Related
I am working on a VR speedrun game and I need a timer. The timer doesn't need to be showed in the screen for the player, just on the map I made. It needs to start when the player (VR) passes a specific point and end when it reaches a different point. If anyone has an idea of how to make this work I would really appreciate it.
On the start line you could have an empty gameobject with a trigger collider on it, and in the OnTriggerEnter event you could start a Coroutine that keeps track of the time, and on the finish line you'd have another trigger collider that sets a flag and stops the timer.
Something along the lines of this should work:
using UnityEngine;
using System;
public class Player : MonoBehaviour {
private bool _isTimerStarted = false;
private float _timeElapsed = 0;
private void OnTriggerEnter(Collider other) {
if (other.gameObject.name.Equals("Start Line")) {
_isTimerStarted = true;
StartCoroutine(StartTimer());
} else if (other.gameObject.name.Equals("Finish Line") {
_isTimerStarted = false;
}
}
IEnumerator StartTimer() {
while (_isTimerStarted) {
_elapsedTime += Time.deltaTime;
yield return null;
}
yield break;
}
}
For this to work just make sure your player has a RigidBody attached or else no collision will be detected :)
If you want a "timer" as in "show the elapsed time since some event" you might take a look at Stopwatch
var sw = Stopwatch.StartNew();
...
Console.WriteLine(sw.Elapsed.ToString());
The stopwatch is primarily intended for performance measurements. But it is easy to use, so if it fits your use case you might as well make use of it, even if the resolution and accuracy is much greater than you need.
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 first-person game and am having trouble animating my character. I have the right animations in place. I need to find a way to make the game detect when the player has just landed on the ground so that I can play the 'landing' animation. The problem is that the only way I've thought of thus far to do this is with a coroutine. But the coroutine, when initiated, completely freezes my whole application. I suspect it's because the act of being midair initiates the coroutine once per frame. Here's the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAnimationHandler : MonoBehaviour
{
Animator animator;
IEnumerator LandDetect()
{
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
}
animator.SetTrigger("Land");
yield return null;
}
PlayerMove playermove;
void Start()
{
// These are the two most important components for this
// script. I'll need PlayerMove for the mini-API
// that I have in there and I'll need the animator
// for obvious reasons.
playermove = GetComponent<PlayerMove>();
animator = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
JumpHandler();
}
void JumpHandler()
{
if (!playermove.isGrounded)
{
if (playermove.doubleJumpOccur)
{
animator.SetTrigger("DoubleJump");
StartCoroutine(LandDetect());
}
else
{
animator.SetBool("Midair", true);
StartCoroutine(LandDetect());
}
}
else if (playermove.jumpOccur)
{
animator.SetTrigger("Jump");
StartCoroutine(LandDetect());
}
}
}
I suspect that the application doesn't leave
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
}
until you land. This loop is continuously executed in the same frame (which freezes your game) because you're not skipping frames. You need to put yield return 0 (0, not null. Null won't cause the coroutine to skip any frames) somewhere in it so it can be resumed in the next frame. Also may remove yield return null at the end of the method, I doesn't do anything at this point
That's how I think it should look:
IEnumerator LandDetect()
{
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
yield return 0;
}
animator.SetTrigger("Land");
}
Also I don't know if using a coroutine is the best choice here. I usually use some trigger/collision detection for stuff like this.
Whenever I run my game it freezes, but it doesn't without this C# script.
I've tried changing around my code, and it works outside of Unity, in .NET (with some tweaks to certain functions) but when it's in Unity it crashes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Throw : MonoBehaviour
{
public Rigidbody rb;
string final = "final:";
public float force = 1;
public float accuracy = 0;
void incto(float amount)
{
while (force < amount)
{
Debug.Log(force);
force++;
}
}
void decto(float amount)
{
while (force > amount)
{
Debug.Log(force);
force--;
}
}
void fstart()
{
while (true)
{
force = 1;
incto(200);
decto(1);
if(Input.GetKey(KeyCode.E))
{
Debug.Log(final + force);
break;
}
}
}
// Start is called before the first frame update
void Start()
{
fstart();
}
// Update is called once per frame
void FixedUpdate()
{
Debug.Log(force);
}
}
It should decrease and increase the force value, then stop when you press E, but Unity just crashes.
Unity takes care of the while(true) for you. Unity's while(true) calls your FixedUpdate, you just need to fill it in.
Unity only captures keystrokes once per frame, so Input.GetKey(KeyCode.E) will always return the same value. Unity crashes because of your while(true) is an infinite loop.
More info: https://docs.unity3d.com/Manual/ExecutionOrder.html
i belive that unity starts catching key strokes after the first frame, not before, try moving fstart() behind a first run bool in the FixedUpdate function
oh and this will hang the entire program every time it executes a frame.....
The code crashes because you have an infinite loop here:
while (true)
{
}
It will never exit the loop so nothing more happens. Just put that code into Update() method, which gets called by the engine on every frame, it will do the trick
How can I create a flashing object in Unity using SetActiveRecursively (Moment = 1 second).
My example (for changes):
public GameObject flashing_Label;
private float timer;
void Update()
{
while(true)
{
flashing_Label.SetActiveRecursively(true);
timer = Time.deltaTime;
if(timer > 1)
{
flashing_Label.SetActiveRecursively(false);
timer = 0;
}
}
}
Use InvokeRepeating:
public GameObject flashing_Label;
public float interval;
void Start()
{
InvokeRepeating("FlashLabel", 0, interval);
}
void FlashLabel()
{
if(flashing_Label.activeSelf)
flashing_Label.SetActive(false);
else
flashing_Label.SetActive(true);
}
Take a look on unity WaitForSeconds function.
By passing int param. (seconds), you can toggle your gameObject.
bool fadeIn = true;
IEnumerator Toggler()
{
yield return new WaitForSeconds(1);
fadeIn = !fadeIn;
}
then call this function by StartCoroutine(Toggler()).
You can use the Coroutines and new Unity 4.6 GUI to achieve this very easily. Check this article here which falsges a Text. YOu can modify it easily for gameobject easily
Blinking Text - TGC
If you just need the code, here you go
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class FlashingTextScript : MonoBehaviour {
Text flashingText;
void Start(){
//get the Text component
flashingText = GetComponent<Text>();
//Call coroutine BlinkText on Start
StartCoroutine(BlinkText());
}
//function to blink the text
public IEnumerator BlinkText(){
//blink it forever. You can set a terminating condition depending upon your requirement
while(true){
//set the Text's text to blank
flashingText.text= "";
//display blank text for 0.5 seconds
yield return new WaitForSeconds(.5f);
//display “I AM FLASHING TEXT” for the next 0.5 seconds
flashingText.text= "I AM FLASHING TEXT!";
yield return new WaitForSeconds(.5f);
}
}
}
P.S: Even though it seems to be an infinite loop which is generally considered as a bad programming practice, in this case it works quite well as the MonoBehaviour will be destroyed once the object is destroyed. Also, if you dont need it to flash forever, you can add a terminating condition based on your requirements.
Simple way is to use InvokeRepeating() and CancelInvoke() method.
InvokeRepeating("BlinkText",0,0.3) will repeatedly call BlinkText() for every 0.03 time Interval.
CancelInvoke("BlinkText") will stop the repeating invoke.
Here's the example :
//Call this when you want to start blinking
InvokeRepeating("BlinkText", 0 , 0.03f);
void BlinkText() {
if(Title.gameObject.activeSelf)
Title.gameObject.SetActive(false);
else
Title.gameObject.SetActive(true);
}
//Call this when you want to stop blinking
CancelInvoke("BlinkText");
Unity Documentation