Prevent Spawning Prefabs At The Same Position In A Row - c#

i am trying to develop a unity game which uses prefabs spawn at random locations from an array. The problem i am having is prefabs are spawning on the top of each other too many times. I have tried to prevent this from happening with the help of other topics not only here but also from google but i couldnt apply some methods to my code. So my goal is keep tracking last spawned object position and spawn next object at diffrent position from the array i have created within the obstacle script. Is there anyone who can help me?
This is my obstacle scripts which attached to prefabs.
public class obstacle : MonoBehaviour
{
private Rigidbody targetRb;
// Start is called before the first frame update
private float minSpeed = 12;
private float maxSpeed = 16;
private float ySpawnPos = 6;
private float NewPosition = -1.87f;
private List<Vector3> spawnPositions = new List<Vector3>();
private int index;
public int offset = 1;
void Start()
{
index = Random.Range(0, spawns.Length);
transform.position = spawns[index];
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
}
Vector3 RandomForce()
{
return Vector3.down * Random.Range(minSpeed, maxSpeed);
}
Vector3[] spawns = new[]
//spawns = new Vector3[]
{
new Vector3(-2.16f,7,0),
new Vector3(-0.67f,7,0),
new Vector3(0.75f,7,0),
new Vector3(2.22f,7,0)
};
}
This is my game manager script which spawn the prefabs.
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
//spawnera alternatif daha güzel
{
public List<GameObject> targets;
// Start is called before the first frame update
public int score;
public TextMeshProUGUI scoreText;
public TextMeshProUGUI gameOverText;
public Button restartButton;
public bool isGameActive;
public float spawnRate = 3.0f;
public Text highScore;
public Text highestScore;
void Start()
{
isGameActive = true;
score = 0;
UpdateScore(0);
StartCoroutine(SpawnTarget());
highScore.text = PlayerPrefs.GetInt("HighScore", 0).ToString();
}
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
Instantiate(targets[index]);
UpdateScore(5);
if(score > PlayerPrefs.GetInt("HighScore",0))
{
PlayerPrefs.SetInt("HighScore", score);
highScore.text = score.ToString();
}
}
}
// Update is called once per frame
public void UpdateScore(int scoreToAdd)
{
score += scoreToAdd;
scoreText.text = "Score: " + score;
}
public void GameOver()
{
restartButton.gameObject.SetActive(true);
gameOverText.gameObject.SetActive(true);
isGameActive = false;
Time.timeScale = 0;
highScore.gameObject.SetActive(true);
highestScore.gameObject.SetActive(true);
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
Time.timeScale = 1;
}
}

A stack or queue might be a bit overkill, so instead, I'll just keep an iterator and check if we are exceeding the bounds of your array. I am also going to move the initial placement to the GameManager so it can manage which positions are left in the spawn list locations.
// move the array of locations to the GameManager
Vector3[] spawns = new[]
{
new Vector3(-2.16f,7,0),
new Vector3(-0.67f,7,0),
new Vector3(0.75f,7,0),
new Vector3(2.22f,7,0)
};
private int currentSpawnItr = 0;
private int maxSpawnItr = 0;
private System.Random rndm;
private void Start()
{
rndm = new System.Random();
maxSpawnItr = spawns.Length;
ShufflePositions();
}
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
GameObject tmpObstacle = Instantiate(targets[index]);
tmpObstacle.transform.position = GetNextPosition();
UpdateScore(5);
if(score > PlayerPrefs.GetInt("HighScore",0))
{
PlayerPrefs.SetInt("HighScore", score);
highScore.text = score.ToString();
}
}
}
private Vector3 GetNextPosition()
{
// when we reach the bounds of our spawn array length, shuffle it again
// and reset our iterator
if(currentSpawnItr == maxSpawnItr)
{
currentSpawnItr = 0;
ShufflePositions();
}
++currentSpawnItr;
return spawns[currentSpawnItr-1];
}
private void ShufflePositions()
{
Vector3 prevLast = spawns[maxSpawnItr-1];
spawns = spawns.OrderBy(spawn => rndm.Next()).ToArray();
// when our new first spawn was our old last spawn
if(spawns[0] == prevLast)
{
// randomly pick an index to swap our first element to
int randomIdx = rndm.Next(1, maxSpawnItr);
spawns[0] = spawns[randomIdx];
spawns[randomIdx] = prevLast;
}
}
I have not tested the above snippet. It is more the direction you could take this. If you have issues implementing this let me know.

Related

How do i add multiple enemies to my unity wave system?

I have a wave script for my game and it works fine, however after a while I have realised it gets quite dull fighting against the same enemies over and over again, so I was wondering if I could make a list of some sort to store all enemies that will be in each wave.
Here is my script;
SpawnManager.cs
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
public int EnemiesPerWave;
public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
public Wave[] Waves; // class to hold information per wave
public Transform[] SpawnPoints;
public float TimeBetweenEnemies = 2f;
public GameObject HelpText;
public GameObject Shop;
public GameObject shopfx;
public Transform ShopL;
bool shopactive = false;
private int _totalEnemiesInCurrentWave;
private int _enemiesInWaveLeft;
private int _spawnedEnemies;
private int _currentWave;
private int _totalWaves;
void Start ()
{
_currentWave = -1; // avoid off by 1
_totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
StartNextWave();
}
void Update()
{
if (_enemiesInWaveLeft <= 0 && Input.GetKeyDown(KeyCode.R))
{
StartNextWave();
}
}
void StartNextWave()
{
_currentWave++;
// win
if (_currentWave > _totalWaves)
{
return;
}
_totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
_enemiesInWaveLeft = 0;
_spawnedEnemies = 0;
StartCoroutine(SpawnEnemies());
}
// Coroutine to spawn all of our enemies
IEnumerator SpawnEnemies()
{
GameObject enemy = Waves[_currentWave].Enemy;
while (_spawnedEnemies < _totalEnemiesInCurrentWave)
{
_spawnedEnemies++;
_enemiesInWaveLeft++;
int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's
// position and rotation.
Instantiate(enemy, SpawnPoints[spawnPointIndex].position,
SpawnPoints[spawnPointIndex].rotation);
yield return new WaitForSeconds(TimeBetweenEnemies);
}
yield return null;
}
// called by an enemy when they're defeated
public void EnemyDefeated()
{
_enemiesInWaveLeft--;
// We start the next wave once we have spawned and defeated them all
if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
{
HelpText.SetActive(true);
Invoke("SetFalse",5.0f);
Shop.SetActive(true);
Invoke("LateUpdate",1f);
Instantiate(shopfx, new Vector3(ShopL.transform.position.x, ShopL.transform.position.y, ShopL.transform.position.z), Quaternion.identity);
shopactive = true;
}
}
void SetFalse()
{
HelpText.SetActive(false);
}
void LateUpdate()
{
if(Input.GetKeyDown(KeyCode.R) && shopactive == true)
{
Shop.SetActive(false);
Instantiate(shopfx, new Vector3(Shop.transform.position.x, Shop.transform.position.y, Shop.transform.position.z), Quaternion.identity);
shopactive = false;
}
}
}

How can I change the moving objects speed at runtime?

The manager script
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DronesManager : MonoBehaviour
{
public GameObject target;
public float movementSpeed;
public float launchTime;
public Transform dronesUnchild;
private List<GameObject> drones = new List<GameObject>();
private float currentDroneSpeed;
private void Awake()
{
currentDroneSpeed = movementSpeed;
}
// Start is called before the first frame update
void Start()
{
target = GameObject.Find("Base");
drones = GameObject.FindGameObjectsWithTag("Drone").ToList();
StartCoroutine(MoveDrone());
}
// Update is called once per frame
void Update()
{
if(currentDroneSpeed != movementSpeed)
{
for(int i = 0; i < drones.Count; i++)
{
var droneControl = drones[i].GetComponent<DroneControl>();
droneControl.movingSpeed = movementSpeed;
}
currentDroneSpeed = movementSpeed;
}
}
private IEnumerator MoveDrone()
{
// same as you did:
drones = GameObject.FindGameObjectsWithTag("Drone").ToList();
foreach(var drone in drones)
{
drone.GetComponent<DroneControl>().target = target.transform;
}
while (drones.Count > 0)
{
// pick one at random, get it
int index = Random.Range(0, drones.Count);
var drone = drones[index];
// remove it from list
drones.RemoveAt(index);
// TODO: might want to check/guard if drone == null ... this guards against it
// being Destroy()ed and yet still lying around in our list marked as "dead"
// simplified your get-component-and-go-if-not-already-going code here
var droneControl = drone.GetComponent<DroneControl>();
if (droneControl.go == false)
{
droneControl.movingSpeed = movementSpeed;
droneControl.go = true;
drone.transform.parent = dronesUnchild;
}
// wait
yield return new WaitForSeconds(launchTime);
}
}
}
I tried to add this part in the Update
void Update()
{
if(currentDroneSpeed != movementSpeed)
{
for(int i = 0; i < drones.Count; i++)
{
var droneControl = drones[i].GetComponent<DroneControl>();
droneControl.movingSpeed = movementSpeed;
}
currentDroneSpeed = movementSpeed;
}
}
and this script is attached to each moving object
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DroneControl : MonoBehaviour
{
public Transform target;
public float turnSpeed = .01f;
Quaternion rotGoal;
Vector3 direction;
public float movingSpeed;
public bool go = false;
private bool waitBeforeRotate = false;
private bool startRotating = false;
#region AddedCode
public float targetRange = 1.0f;
private bool IsTargetReached(Vector3 dronePos, Vector3 targetPos)
{
var distance = Vector3.Distance(dronePos, targetPos);
return distance < targetRange;
}
#endregion AddedCode
// Update is called once per frame
void Update()
{
// next line is modified to incorporate the range check
if (go && !IsTargetReached(transform.position, target.position))
{
transform.position += transform.forward * movingSpeed * Time.deltaTime;
if (waitBeforeRotate == false)
{
StartCoroutine(StartRotating());
waitBeforeRotate = true;
}
if (startRotating)
{
direction = (target.position - transform.position).normalized;
rotGoal = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, rotGoal, turnSpeed);
}
}
}
IEnumerator StartRotating()
{
yield return new WaitForSeconds(3f);
startRotating = true;
}
}
but it's never change the speed of moving objects.
if the speed of each moving object in the editor start is 5 for example and in the manager script I change the speed to 100 the speed of each object is still 5.
Maybe this is happening because after picking a random drone in IEnumerator you instantly remove it from the list?
So in Update() you set a speed for all drones, except the ones that are already moving.

C# spawn manager creating waves of enemies too quickly

I'm a beginner making my first game in Unity, following Unity's Create With Code course. I'm creating a shooter game that will use hand tracking. I haven't set up hand tracking yet so i created an OnTrigger input that explodes objects when I hit space. I created the spawn manager below to spawn waves of enemy attack, but they all the waves are spawning enemies too fast. It seems like they're spawning automatically instead of when the first wave has been destroyed.
Is there an easier way to spawn at a slower rate? Or spawn only when there are no more enemies alive?
EDIT: Added Attack script below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnAttack : MonoBehaviour
{
public GameObject Trumps;
private float spawnRange = 9;
public int enemyCount;
public int waveNumber = 1;
void Start()
{
SpawnEnemyWave(waveNumber);
//InvokeRepeating("GenerateSpawnPosition", startDelay, randomInterval);
}
// Update is called once per frame
void Update()
{
enemyCount = FindObjectsOfType<Attack>().Length;
if(enemyCount == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
void SpawnEnemyWave(int enemiesToSpawn)
{
for (int i = 0; i < enemiesToSpawn; i++)
{
Instantiate(Trumps, GenerateSpawnPosition(), Trumps.transform.rotation);
}
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Attack : MonoBehaviour
{
public float speed = 0.5f;
public GameObject Player;
private Rigidbody enemyRb;
// Start is called before the first frame update
void Start()
{
enemyRb = GetComponent<Rigidbody>();
Player = GameObject.Find("Player");
this.transform.LookAt(Player.transform);
}
// Update is called once per frame
void Update()
{
Vector3 lookDirection = (Player.transform.position - transform.position).normalized;
enemyRb.AddForce(lookDirection * speed);
transform.Translate(Vector3.forward * Time.deltaTime * speed);
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
Debug.Log("Game Over");
}
}
I guess that you can use the invoke method:
Invoke("NameOfTheMethod", 1f)
What this method does is that it waits a certain amount of seconds before calling a method. You have to write the name of the method in quotation marks and then select how long you want to wait before the method is called (The "1f" represents the delay in seconds.) In your case, you can make the script wait before spawning enemies.
I don't know your Attack script but I would use something like
public class Attack : MonoBehaviour
{
public static readonly HashSet<Attack> AliveAttackInstances = new HashSet<Attack>();
private void Awake()
{
AliveAttackInstances.Add(this);
}
private void OnDestroy()
{
AliveAttackInstances.Remove(this);
}
}
So you can all the time in a cheaper way check how many and which enemies are alive like
public class SpawnAttack : MonoBehaviour
{
// I would change this type here to make sure your spawned prefab actually
// HAS an Attack attached .. otherwise your enemyCount will always be 0
public Attack Trumps;
...
void Update()
{
if(Attack.AliveAttackInstances.Count == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
Then in order to add a certain delay before spawning the next wave you could use a simple timer like
public class SpawnAttack : MonoBehaviour
{
public Attack Trumps;
[SerializeField] private float delay = 1f;
private float timer;
...
void Update()
{
if(Attack.AliveAttackInstances.Count == 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
timer = delay;
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
}
Try to use Coroutine.
Here's a video about Coroutines: https://www.youtube.com/watch?v=qolMYyq0nX0
My example:
public class Spawn : MonoBehaviour {
private float TimeToWait = 3f;
public int enemyCount = 0;
public int waveNumber = 0;
public GameObject enemy;
void Start()
{
StartCoroutine(SpawnEnemyWave(waveNumber));
}
void Update()
{
if (enemyCount == 0)
{
waveNumber++;
StartCoroutine(SpawnEnemyWave(waveNumber));
}
if (Input.GetMouseButtonDown(0))
{
enemyCount--;
}
}
IEnumerator SpawnEnemyWave(int enemiesToSpawn)
{
//"Things to do before the seconds."
for (int i = 0; i < enemiesToSpawn; i++)
{
enemyCount++;
}
yield return new WaitForSeconds(TimeToWait); // at this example we wait 3 seconds (float TimeToWait = 3f;)
//"Things to do after the seconds."
for (int i = 0; i < enemiesToSpawn; i++)
{
Debug.Log("New Enemy!");
Instantiate(enemy, transform.position, Quaternion.identity);
}
}
}

How to instantiate 3 gameobjects in 3 positions of a list of 20 random positions

I want to instantiate 3 gameobjects in 3 positions of a list of 20 random positions. When I play the game it’s instantiating 20 gameobjects in 20 positions, and not 3 gameobjects. How can I do it? This is my code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SpawnManager: MonoBehaviour {
private GameObject Player;
public List<GameObject> spawnPositions;
public List<GameObject> spawnObjects;
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
SpawnObjects ();
}
}
void SpawnObjects()
{
foreach (GameObject spawnPosition in spawnPositions)
{
int selection = Random.Range (0, spawnObjects.Count);
Instantiate (spawnObjects [selection], spawnPosition.transform.position, spawnPosition.transform.rotation);
}
}
}
UPDATE:
Sometimes 2 objects are placed in the same position and I want the objects to instantiate in different positions. I've tried to add the random positions to a list and only instantiate if it isn't already in the list, but it doesn't work. This is my code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SpawnManager : MonoBehaviour {
private GameObject Player;
public List<GameObject> spawnPositions;
public List<GameObject> spawnObjects;
private GameObject obj;
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
SpawnObjects ();
}
}
void SpawnObjects()
{
for (int i = 0; i < 3; ++i)
{
int randomObject = Random.Range(0, spawnObjects.Count);
int randomPosition = Random.Range(0, spawnPositions.Count);
List <GameObject> _spawnPositions = new List<GameObject>();
obj = spawnPositions[randomPosition];
_spawnPositions.Add(obj);
if (!_spawnPositions.Contains (obj))
{
Instantiate (spawnObjects [randomObject], _spawnPositions [randomPosition].transform.position, _spawnPositions [randomPosition].transform.rotation);
}
else
{
Debug.Log ("error");
}
}
}
}
You're looping over all of the positions and adding random objects to them, you need to randomise that as well. So this:
foreach (GameObject spawnPosition in spawnPositions)
{
int selection = Random.Range (0, spawnObjects.Count);
Instantiate (spawnObjects [selection], spawnPosition.transform.position, spawnPosition.transform.rotation);
}
Should be this:
int randomObject = Random.Range(0, spawnPositions.Count);
int randomPosition = Random.Range(0, spawnPositions.Count);
Instantiate (spawnObjects[randomObject], spawnPositions[randomPosition].transform.position, spawnPositions[randomPosition].transform.rotation);
And then just put that in a for loop like:
for (int i = 0; i < 3; ++i)
In your updated code you're creating the list on each loop, you need to use the same list (untested but should work):
List<int> randomObjects = new List<int>();
List<int> randomPositions = new List<int>();
for (int i = 0; i < 3; ++i)
{
int randomObject;
do
{
randomObject = Random.Range(0, spawnObjects.Count);
}
while (randomObjects.Contains(randomObject));
randomObjects.Add(randomObject);
int randomPosition;
do
{
randomPosition = Random.Range(0, spawnPositions.Count);
}
while (randomPositions.Contains(randomPosition));
randomPositions.Add(randomPosition);
Instantiate(spawnObjects[randomObject], spawnPositions[randomPosition].transform.position, spawnPositions[randomPosition].transform.rotation);
}

Adding Elements To An Array Using Array.Add

I am new to C# and am trying to make a basic game in Unity. I am trying to add bullet gameObject to an array. I have researched how to add elements to an array in C# and have found the Add method. However, when I try to use this MonoDevelop doesn't highlight the method as if the method doesn't exist and I get an error. Here is is the error message:
Assets/Scripts/SpaceShipController.cs(126,25): error CS0118:
SpaceShipController.gameManager' is afield' but a `type' was
expected
Here is the line of code which trips the error:
gameManager.bullets[].Add(bulletObject);
Here is the rest of my code. The class called SpaceShipController trips the error when it tries to add bullet objects to an array in a GameManager objects with the script GameManager attached. Finally the BulletBehaviour class just makes the bullet move forward. The code is labelled by class:
SpaceShipController:
using UnityEngine;
using System.Collections;
public class SpaceShipController : MonoBehaviour {
public GameObject bulletObject;
public GameManager gameManager;
//private GameObject[] bullets;
public float shipSpeed;
public float bulletSpeed = 1;
private Vector3 spaceShip;
private Quaternion spaceShipRotation;
private Vector3 bulletPosition;
private int coolDown = 10;
private bool moveRight = false;
private bool moveLeft = false;
private bool fire = false;
// Use this for initialization
void Start () {
spaceShip = transform.position;
spaceShipRotation = transform.rotation;
bulletObject.transform.position = bulletPosition;
}
// Update is called once per frame
void Update () {
coolDown--;
inputHandler();
this.transform.position = spaceShip;
}
void inputHandler() {
if (Input.GetKeyDown(KeyCode.RightArrow)) {
moveRight = true;
}
if (Input.GetKeyDown(KeyCode.LeftArrow)) {
moveLeft = true;
}
if (Input.GetKeyUp(KeyCode.RightArrow)) {
moveRight = false;
}
if (Input.GetKeyUp(KeyCode.LeftArrow)) {
moveLeft = false;
}
if (Input.GetKeyDown(KeyCode.Space)) {
fire = true;
}
if (Input.GetKeyUp(KeyCode.Space)) {
fire = false;
}
if (moveRight == true) {
spaceShip.x += shipSpeed;
}
if (moveLeft == true) {
spaceShip.x -= shipSpeed;
}
if (coolDown <= 0) {
if (fire == true) {
Fire ();
coolDown = 10;
}
}
}
void Fire () {
for (var i = 0; i < 2; i++) {
if (i == 0) {
spaceShip = new Vector3 (transform.position.x + 0.9f, transform.position.y + 0.9f, transform.position.z);
}
else if (i == 1) {
spaceShip = new Vector3 (transform.position.x - 0.9f, transform.position.y + 0.9f, transform.position.z);
}
Instantiate(bulletObject, spaceShip, spaceShipRotation);
bulletObject.AddComponent<BulletBehaviour>();
gameManager.bullets[].Add(bulletObject);
spaceShip = this.transform.position;
}
}
}
GameManager:
using UnityEngine;
using System.Collections;
public class GameManager : MonoBehaviour {
public GameObject[] bullets;
public Camera cam;
private Vector2 cameraBounds;
// Use this for initialization
void Start () {
cameraBounds = new Vector2 (cam.orthographicSize * Screen.width/Screen.height, cam.orthographicSize);
}
// Update is called once per frame
void Update () {
/*for (int i = 0; i < bullets.Length; i++) {
if (bullets[i].transform.position.y >= cameraBounds.y) {
Destroy(bullets[i]);
}
}*/
}
}
BulletBehaviour:
using UnityEngine;
using System.Collections;
public class BulletBehaviour : MonoBehaviour {
public SpaceShipController ship;
private Vector3 shipPosition;
// Use this for initialization
void Start () {
shipPosition = transform.position;
}
// Update is called once per frame
void Update () {
shipPosition.y += 1;
transform.position = shipPosition;
}
}
As always any help would be greatly appreciated. Thanks in advance for any help you can provide.
Arrays are fixed-size. This means that, once they have been initialized with a certain length (e.g., bullets = new GameObject[10]), its length can no longer change.
In order to "add" an item to a array, you have to specify in which position you'd like the item to be. By default, arrays' indexing is 0-based. For example, to insert an element in the first position:
bullets[0] = myItem;
If you don't know how many items you'll have beforehand, and want to add/remove items at will, you should use a resizable collection, such as List<T>.
public List<GameObject> Bullets {get; set;}
You can use it like so:
//initialize with 0 items
Bullets = new List<GameObject>();
//add a new item at the end of the list
Bullets.Add(item);
Read also:
Arrays Tutorial
List<T> class
C# Collections

Categories

Resources