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!
Related
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.
So I'm currently working on a unity boss fight game where lasers are meant to fire from above on a certain time interval (let's say like 10 seconds) after the player shoots the boss for the first time. What happens is when the boss is hit multiple times, it spawns multiple different laser projectiles. I'm using InvokeRepeating to do this, which I don't think is the most efficient way, so if anyone has any ideas on this, help would be greatly appreciated. Here's the code (it's probably messy I'm pretty new to game development)
using UnityEngine;
public class startboss : MonoBehaviour
{
public bool fight = false;
public Sprite awake;
public bool player_dead = false;
public float spawntime = 10f;
public float spawndelay = 10f;
public bool spawn = false;
// Start is called before the first frame update
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.tag == "Bullet")
{
spawn = true;
fight = true;
if (fight == true)
{
gameObject.GetComponent<SpriteRenderer>().sprite = awake;
if (spawn == true)
{
InvokeRepeating("Shoot", spawntime, spawndelay);
}
}
}
}
public GameObject laserprefab;
public Transform laserpoint;
public Transform laserpoint2;
public float seconds = 10;
public Rigidbody2D rblaser;
// Start is called before the first frame update
// Update is called once per frame
// Start is called before the first frame update
void Shoot()
{
Instantiate(laserprefab, laserpoint.position, laserpoint.rotation);
Instantiate(laserprefab, laserpoint2.position, laserpoint2.rotation);
}
}
It looks like you are creating a new InvokeRepeating("Shoot", spawntime, spawndelay); every time you shoot the boss so it will start another instance of Shoot repeating. So if you shoot the boss 3 times you will have 3 repeating Shoots going on.
You could move the shooting outside of the collision and use a timer to count down instead of InvokeRepeating to have more control. If you need you can set the timer to 0 or stop / start the timer on collision.
//Declair timer and time
float fireTimer = 0.0f;
float fireTime = spawnTime;
void Update(){
//Add time to the timer.
fireTimer += Time.deltaTime;
//If timer = time then fire and reset the timer.
if(fireTimer == fireTime){
Shoot();
fireTimer = 0.0f;
}
}
I am trying to create a ball hitting game in the baseball format. I create a ball as a prefab. I want to push the ball to the main scene within a certain period of time.
For example; when the first ball is in the scene, the second ball will spawn after 5-6 seconds, then the third, fourth etc. I am the beginner level of Unity and I am not good at C#. I am not sure whether I am using the true functions such as Instantiate. Here is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour {
public float RotateSpeed = 45; //The ball rotates around its own axis
public float BallSpeed = 0.2f;
public GameObject[] prefab;
public Rigidbody2D rb2D;
void Start() {
rb2D = GetComponent<Rigidbody2D>(); //Get component attached to gameobject
Spawn ();
}
void FixedUpdate() {
rb2D.MoveRotation(rb2D.rotation + RotateSpeed * Time.fixedDeltaTime); //The ball rotates around its own axis
rb2D.AddForce(Vector2.left * BallSpeed);
InvokeRepeating("Spawn", 2.0f, 2.0f);
}
public void Spawn ()
{
int prefab_num = Random.Range(0,3);
Instantiate(prefab[prefab_num]);
}
}
After I apply this script, the result is not what I want.
Add InvokeRepeating("Spawn", 2.0f, 2.0f); to the Start not the FixedUpdate.
InvokeRepeating invokes the method methodName in time seconds, then repeatedly every repeatRate seconds.
You can check the documentation here.
Use Coroutines
private IEnumerator SpawnBall() {
while(true) {
Instantiate(baseball);
yield return new WaitForSeconds(5);
}
}
Which can then be started with StartCoroutine() and terminated in one of three ways:
internally by breaking out of the while loop with break (the function would then have no more lines to execute and exit)
internally by yield break
externally by calling StopCoroutine() on a reference to the coroutine
Alternative to the other answers: Just use a countdown. This sometimes gives you more control
// Set your offset here (in seconds)
float timeoutDuration = 2;
float timeout = 2;
void Update()
{
if(timeout > 0)
{
// Reduces the timeout by the time passed since the last frame
timeout -= Time.deltaTime;
// return to not execute any code after that
return;
}
// this is reached when timeout gets <= 0
// Spawn object once
Spawn();
// Reset timer
timeout = timeoutDuration;
}
I updated my script by considering your feedbacks and it works like a charm. Thanks to all!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Threading;
public class Ball : MonoBehaviour {
public float RotateSpeed = 45; //The ball rotates around its own axis
public float BallSpeed = 0.2f;
public GameObject BaseBall;
public Transform BallLocation;
public Rigidbody2D Ball2D;
void Start() {
Ball2D = GetComponent<Rigidbody2D>(); //Get component attached to gameobject
InvokeRepeating("Spawn", 5.0f, 150f);
}
void FixedUpdate() {
Ball2D.MoveRotation(Ball2D.rotation + RotateSpeed * Time.fixedDeltaTime); //The ball rotates around its own axis
Ball2D.AddForce(Vector2.left * BallSpeed);
}
public void Spawn ()
{
Instantiate (BaseBall, BallLocation.position, BallLocation.rotation);
}
}
I'm having this problem with my enemy shooting, you see I'm using raycasting to detected where my player is and once detected I want the enemy to shoot, so far I have accomplished that but however there's not delay between each Instantiated bullet!So the bullet is being constantly spawn rather than a delay in between each spawn. I've tried a whole lot of different solutions to fix this problem but nothing worked! I've tried IEnumerator, object pooling, creating a count down timer and invoke & invokerepeating but still my bullets are still being instantiated instantly without no delay. Does any one knows how to have a delay between each instantiated bullet?? Thank you!
This is my script:
public GameObject bulletPrefab;
public Transform bulletSpawn;
public Transform sightStart, sightEnd;
public bool spotted = false;
void Update()
{
RayCasting ();
Behaviours ();
}
void RayCasting()
{
Debug.DrawLine (sightStart.position, sightEnd.position, Color.red);
spotted = Physics2D.Linecast (sightStart.position, sightEnd.position, 1 << LayerMask.NameToLayer("Player"));
}
void Behaviours()
{
if (spotted == true) {
//Invoke("Fire", cooldownTimer);
Fire ();
} else if (spotted == false) {
//CancelInvoke ();
}
}
void Fire()
{
GameObject bullet = (GameObject)Instantiate (bulletPrefab);
//GameObject bullet = objectPool.GetPooledObject ();
bullet.transform.position = transform.position;
bullet.GetComponent<Rigidbody2D> ().velocity = bullet.transform.up * 14;
Destroy (bullet, 2.0f);
}
You need to throttle the rate at which bullets are spawned. You could base it on number of frames but that's a bad approach as fire rate will vary with game performance.
A better approach is to use a variable to track how much time must elapse before we can fire again. I'll call this "Time To Next Shot" (TTNS)
Decrement TTNS by the time that has elasped.
Check if TTNS is 0 or less
If so:
Set TTNS as appropriate for our desired rate of fire.
Fire a shot
Something like..
private float fireRate = 3f; // Bullets/second
private float timeToNextShot; // How much longer we have to wait.
// [Starts at zero so our first shot is instant]
void Fire() {
// Subtract the time elapsed (since the last frame) from TTNS .
timeToNextShot -= time.deltaTime;
if(timeToNextShot <= 0) {
// Reset the timer to next shot
timeToNextShot = 1/fireRate;
//.... Your code
GameObject bullet = (GameObject)Instantiate (bulletPrefab);
//GameObject bullet = objectPool.GetPooledObject ();
bullet.transform.position = transform.position;
bullet.GetComponent<Rigidbody2D> ().velocity = bullet.transform.up * 14;
Destroy (bullet, 2.0f);
}
}
This does assume that you only call Fire() once per frame (Otherwise the elapsed time will be subtracted from nextShot multiple times).
I opted to do it this way (subtracting slices from a time span) rather than defining an absolute time (wait until time.elapsedTime > x) as the resolution of elapsedTime decreases over time and that approach will result in glitches after a few hours of gameplay.
I'm working on a script, so I could detect when player is not moving for x seconds and load another scene accordingly.
If player started moving again after x seconds, then loading another scene should't be called.
I've tried using isSleeping function and delay it by including Coroutine with WaitForSeconds but it is still checking Rigidbody2D every frame. Is there any other way to check if Rigidbody2D hasn't moved for x seconds and only then load game over level, otherwise continue moving as before?
using UnityEngine;
using System.Collections;
public class PlayerStop : MonoBehaviour {
void Update() {
if (GetComponent<Rigidbody2D>().IsSleeping()) {
Application.LoadLevel(2);
}
}
}
In addition I have a script, which enables me to draw lines (with mouse) and stop player's movement, however lines disappear after x seconds. So for example if I'd set lines to disappear after 1 second, I would like to check if Rigidbody2D stopped moving for 2 seconds and only then load game over scene. Otherwise do nothing as Rigidbody2D will continue moving again after line disappears.
Try this
using UnityEngine;
using System.Collections;
public class PlayerStop : MonoBehaviour {
float delay = 3f;
float threshold = .01f;
void Update() {
if (GetComponent<Rigidbody2D>().velocity.magnitude < threshold * threshold)
StartCoRoutine("LoadTheLevel");
}
IEnumerator LoadTheLevel()
{
float elapsed = 0f;
while (GetComponent<Rigidbody2D>().velocity.magnitude < threshold * threshold)
{
elapsed += Time.deltaTime;
if(elapsed >= delay)
{
Application.LoadLevel(2);
yield break;
}
yield return null;
}
yield break;
}
}
You could try this...I'm not able to test this right now, so may need a little tweaking...
First some private variables:
private float _loadSceneTime;
private Vector3 _lastPlayerPosition;
private float _playerIdleDelay;
And in the Update method check if the player has moved:
private void Update()
{
// Is it time to load the next scene?
if(Time.time >= _loadSceneTime)
{
// Load Scene
}
else
{
// NOTE: GET PLAYERS POSITION...THIS ASSUMES THIS
// SCRIPT IS ON THE GAME OBJECT REPRESENTING THE PLAYER
Vector3 playerPosition = this.transform.position;
// Has the player moved?
if(playerPosition != _lastPlayerPosition)
{
// If the player has moved we will attempt to load
// the scene in x-seconds
_loadSceneTime = Time.time + _playerIdleDelay;
}
_lastPlayerPosition = playerPosition;
}
}