So, c# noob here, with a bit of an issue:
I'm trying to script a boss battle for my Unity game, and I'm making it's A.I.
Every 10 seconds, I want the boss to check a random number. If it makes it, it will perform a teleport animation, and teleport. I haven't coded the teleportation itself, just trying to get the animation to trigger. I want this to be keep going throughout the boss fight, until the boss is defeated.
Unfortunately, it's an infinite loop that crashes my Unity every time I run it. I know having it in Update() is a dumb idea, but I've tried a lot of stuff and got nothing. I'm losing my mind here! Am I missing something obvious?!
Anyway, here's the code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SmellseerAI : MonoBehaviour
{
public Animator animator;
public SmellseerHealth smellseerhealth;
public GameObject tele1;
public GameObject tele2;
public GameObject tele3;
public DateTime TimeOfLastTeleport;
private bool teleporting = false;
void Start()
{
TimeOfLastTeleport = System.DateTime.Now;
}
void Update()
{
Debug.Log("Starting teleportcheck!");
int TeleportMin = 1;
int TeleportMax = 10;
int RandomTeleport = UnityEngine.Random.Range(TeleportMin, TeleportMax);
var diffInSeconds = (System.DateTime.Now - TimeOfLastTeleport).TotalSeconds;
Debug.Log("diffInSeconds is " + diffInSeconds);
if ((RandomTeleport > 5) && (diffInSeconds > 3))
{
Debug.Log("Teleporting!");
teleporting = true;
animator.SetBool("teleporting", true);
while (animator.GetCurrentAnimatorStateInfo(0).normalizedTime <= 1)
{ //If normalizedTime is 0 to 1 means animation is playing, if greater than 1 means finished
Debug.Log("anim playing");
}
animator.SetBool("teleporting", false);
TimeOfLastTeleport = System.DateTime.Now;
}
else
{
Debug.Log("Failed to teleport");
}
Debug.Log("Gaming!");
}
}
You're running a while loop inside Update():
while (animator.GetCurrentAnimatorStateInfo(0).normalizedTime <= 1)
but the animator's time isn't changing because it updates each frame, and you're causing the main thread to hang with your while loop. You won't release Update() (and thus won't allow the next frame to be drawn) until your while loop breaks, but your while loop's condition requires more frames. So you just wait forever.
Try moving your teleport values to the class, initialize them in Start(), then re-set them again after you've done all your checks. Then you can return each frame your condition isn't met. Consider the following:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SmellseerAI : MonoBehaviour
{
public Animator animator;
public SmellseerHealth smellseerhealth;
public GameObject tele1;
public GameObject tele2;
public GameObject tele3;
public DateTime TimeOfLastTeleport;
private bool teleporting = false;
// NEW
int TeleportMin = 1;
int TeleportMax = 10;
int RandomTeleport;
void Start()
{
TimeOfLastTeleport = System.DateTime.Now;
//NEW
RandomTeleport = UnityEngine.Random.Range(TeleportMin, TeleportMax);
}
void Update()
{
var diffInSeconds = (System.DateTime.Now - TimeOfLastTeleport).TotalSeconds;
Debug.Log("diffInSeconds is " + diffInSeconds);
if ((RandomTeleport > 5) && (diffInSeconds > 3))
{
Debug.Log("Teleporting!");
teleporting = true;
animator.SetBool("teleporting", true);
// NEW
if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime <= 1)
{ //If normalizedTime is 0 to 1 means animation is playing, if greater than 1 means finished
Debug.Log("anim playing");
// NEW
return;
}
animator.SetBool("teleporting", false);
TimeOfLastTeleport = System.DateTime.Now;
}
else
{
Debug.Log("Failed to teleport");
}
Debug.Log("Gaming!");
// NEW
RandomTeleport = UnityEngine.Random.Range(TeleportMin, TeleportMax);
}
}
When you start the animation your code enters the while loop and never finishes, unity waits until all scripts are done before rendering the next frame, meaning the animation never finishes
Removing the while loop and just making it an if statement to check the animation state should work.
Related
Hey Guys I'm having some Problems with my Highscore in a Unity game. Its a 2d runner in which I save the Player Distance in a Variable and want to check when the Player is dead if distance is higher than Highscore. But I have a Error and can't figure out what the problem is. Can somebody help me, I'm pretty new to c#.
Heres my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class UIController : MonoBehaviour
{
Player player;
Text distanceText;
GameObject results;
Text finalDistanceText;
private void Awake()
{
player = GameObject.Find("Player").GetComponent<Player>();
distanceText = GameObject.Find("DistanceText").GetComponent<Text>();
results = GameObject.Find("Results");
finalDistanceText = GameObject.Find("FinalDistanceText").GetComponent<Text>();
HighscoreNumber = GameObject.Find("HighscoreNumber").GetComponent<Text>();
results.SetActive(false);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
int distance = Mathf.FloorToInt(player.distance);
distanceText.text = distance + " m";
if (player.isDead)
{
results.SetActive(true);
finalDistanceText.text = distance + " m";
if (distance > Highscore)
{
int Highscore = distance
HighscoreNumber.text = Highscore + " m";
}
}
}
public void Quit()
{
SceneManager.LoadScene("Menu");
}
public void Retry()
{
SceneManager.LoadScene("SampleScene");
}
}
In your second if statment inside update method you use HighScore in comparison then declare variable with the same name on the next line.
Couple issues that I can see. You'll be getting an error for HighscoreNumber being assigned to before being declared.
class UIController : MonoBehaviour {
Text highScore
//...
void Awake() {
highScore = GameObject.Find...
}
}
The next issue is that you're declaring a Highscore locally after you try to use it in the if statement:
// Highscore doesn't exist yet so you can't compare distance against it.
if (distance > Highscore)
{
// The int here means you're making a new variable that only lives inside the braces and setting it to distance.
int Highscore = distance
HighscoreNumber.text = Highscore + " m";
}
If this UIComponent stays alive between scene loads, you could just move the declaration to the class level.
public class UIController : MonoBehavior {
int highScore = 0 //I get set to 0 when the UIController component is created.
//...
void Update() {
//...
// Since highScore is declared at the scope of the class, the if statement and assignment inside both have access.
if(distance > highScore) {
highScore = distance;
}
}
}
The other issue is that it looks like the UIComponent may be getting disposed and re-created when you restart the scene so will likely be reset to 0 between restarts. You'll want to look at something like the Singleton method described here:
https://www.sitepoint.com/saving-data-between-scenes-in-unity/
This is just what came up when I Googled, there's probably a bunch of examples but that should help you know what to search.
However, this will only survive as long as the game is running. If you want to retain the high score between game launches, you'll want to look at something like this:
https://blog.unity.com/technology/persistent-data-how-to-save-your-game-states-and-settings
I am trying to make an endless 2D game. I want to use 3-4 spawners for my game. My problem is that I can't set a specific time for my spawners.
After game started
First spawner will start spawning after 5 seconds and stops after 15 seconds, and it never turns on again.
Second spawner will start spawning after 15 seconds and stop after 25 seconds, and it never turns on again.
Third spawner will start spawning after 25 seconds and stop after 40 seconds, and it never turns on again.
I would like to use the same spawner script for all of them. I think I need some public values.
Here is my spawner code :
using UnityEngine;
using System.Collections;
public class RandomSpawner : MonoBehaviour
{
bool isSpawning = false;
public float minTime = 5.0f;
public float maxTime = 15.0f;
public Transform[] spawnPoints;
public GameObject[] enemies;
IEnumerator SpawnObject(int index, float seconds)
{
Debug.Log("Waiting for " + seconds + " seconds");
int spawnPointIndex = Random.Range(0, spawnPoints.Length);
yield return new WaitForSeconds(seconds);
Instantiate(enemies[index], spawnPoints[spawnPointIndex].position, transform.rotation);
isSpawning = false;
}
void Update()
{
if (!isSpawning)
{
isSpawning = true;
int konumIndex = Random.Range(0, enemies.Length);
StartCoroutine(SpawnObject(konumIndex, Random.Range(minTime, maxTime)));
}
}
}
Use In-Built InvokeRepeating Method,You need to edit according to your Game.
https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
One option I have used for this kind of thing is to test against Time.realtimeSinceStartup which gives you a the number of seconds since the game started:
if (Time.realtimeSinceStartup >= minTime && Time.realtimeSinceStartup <= maxTime) {
//spawn stuff
}
That might be enough, but one extra thing to mention is if the start of your game is not exactly the correct reference point (for example your scene with the spawner created might start long after the game starts if you have a main menu first), then you might need to store a reference time for when the spawner is...uh...spawned. eg:
public class RandomSpawner : MonoBehaviour {
float objectStartTime;
void Start() {
objectStartTime = Time.realtimeSinceStartup;
}
void Update() {
var timeSinceObjectStart = Time.realtimeSinceStartup - objectStartTime;
if (timeSinceObjectStart > minTime && timeSinceObjectStart < maxTime) {
//spawn stuff
}
}
}
Hope this helps!
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?
I'm trying to implement a damage over time system, but Unity keeps saying "Trying to Invoke method...Couldn't be Called." The method I want to call uses the parameters "Collider coll", but from my research you can't invoke if the method has said paremters.
Here is my code:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class DamageOverTime : MonoBehaviour
{
public int PHP; //PHP = Player Health from PlayerHealth.cs script.
public int Damage; //Amount of damage.
public int DamageOverTime; //Damage over time.
public float DamageInterval_DOT = .25f; //Damage interval for damage over time.
public string Level;
PlayerHealth player;
void Start()
{
player = GameObject.Find("Player").GetComponent<PlayerHealth>();
InvokeRepeating("OnTriggerEnter", DamageInterval_DOT, DamageInterval_DOT);
}
void Update()
{
PHP = GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP;
if (PHP <= 0)
{
SceneManager.LoadScene(Level);
}
}
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP = PHP - Damage;
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
}
My goal is to get the OnTriggerEnter function to loop ever 1/4 of a second (or lower possibly). Current upon entering a collider my health is drained by 60% in about a second which is far too fast. How should I work around this?
You can't use InvokeRepeating with OnTriggerEnter, because it's a trigger, which means it will trigger once when entrance of its holder occured.
Also InvokeRepeating means that you want to keep repeating an action continously which is not the case here. You want your trigger to occur once and then remove health points over time.
Solution - Coroutine
Unity3D makes custom usage of IEnumerable and yield keyword called Coroutine that always returns an IEnumerator. How it works? It will return control on every yield there is in our Coroutine and then will go back to exact point where it gave back control instead of starting function execution from scratch.
Code:
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
StartCoroutine("DamageOverTimeCoroutine");
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
while (dotHits < 4)
{
//Will remove 1/4 of Damage per tick
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP -= Damage / 4;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
There's of course room for improvement in this Coroutine. You can pass parameters to it, same as you can to any other method in C#. Also you can implement other invervals that are not hardcoded. Code above is just a simple example on how to deal with such scenarios.
For continous damage over time
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
var player = GameObject.Find("Player").GetComponent<PlayerHealth>();
while (true)
{
//Stop removing damage, player is dead already
if (player.PlayerHP <= 0)
yield break;
//Will remove 5 Damage per tick
player.PlayerHP -= 5;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
To stop Coroutine somewhere else from code use StopCoroutine("DamageOverTimeCoroutine") to stop certain coroutine type or StopAllCoroutines() to stop all coroutines that are active now.
I'm trying to make a GameObject appear and disappear for a finite amount of time (Lets put the time function aside for now).
Here's what I came out with:
using UnityEngine;
using System.Collections;
public class Enemy1Behavior : MonoBehaviour
{
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
this.gameObject.SetActive(false); // Making enemy 1 invisible
Debug.Log("Update called");
DisappearanceLogic(gameObject);
}
private static void DisappearanceLogic(GameObject gameObject)
{
int num = 0;
while (num >= 0)
{
if (num % 2 == 0)
{
gameObject.SetActive(false);
}
else
{
gameObject.SetActive(true);
}
num++;
}
}
}
Now when I click the play button in Unity the program just don't respond, and I can only quit it from the task manager using End Task.
(And yes I know there a infinite loop in the method).
So I guess Im doing something wrong. What is the best way for making a Gameobject Blink/Flash/appear-disappear in Unity?
Thanks guys.
You are using an infinite loop which locks your Update() completely, because num will always be greater then 0.
So you could use InvokeRepeating (http://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html)
public GameObject gameobj;
void Start()
{
InvokeRepeating("DisappearanceLogic", 0, interval);
}
void DisappearanceLogic()
{
if(gameobj.activeSelf)
{
gameobj.SetActive(false);
}
else
{
gameobj.SetActive(true);
}
}
interval is a float - something like 1f 0.5f etc.
You can make animation for blinking etc - Animations in Mecanim. Appearing and disappearing you can achieve using gameObject.SetActive(true/false);. If you want to make something with time its better to use Coroutines or just Invoke with delay parameter - Invoke Unity Docs.