I have 2 scripts
one that check if an area is free
one that spawn an object if the area is free
this is to prevent overlapping
checker script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpaceCheck : MonoBehaviour
{
public bool isFree = true;
public GameObject RoomParent;
void OnCollisionEnter(Collision col)
{
isFree = false;
}
void Start()
{
if(isFree == true)
{
RoomParent.SetActive(true);
}
}
void Update()
{
}
}
spawner script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoomSpawner : MonoBehaviour
{
public List<Transform> Spawners;
public List<GameObject> Rooms;
int choosenSpawner = 0;
public GameObject SpaceCheck;
void Start()
{
choosenSpawner = Random.Range(0, Spawners.Count);
GameObject manager = GameObject.Find("RoomsManager");
RoomsManager managerProperty = manager.GetComponent<RoomsManager>();
GameObject Space = SpaceCheck;
SpaceCheck spaceProperty = Space.GetComponent<SpaceCheck>();
if (managerProperty.maxRoom > managerProperty.RoomCount && spaceProperty.isFree == true)
{
Instantiate(Rooms[Random.Range(0, Rooms.Count)], Spawners[choosenSpawner].position, Spawners[choosenSpawner].rotation);
RoomsManager.instance.AddRoomCount();
}
}
void Update()
{
}
}
Problem is that they still overlaps
here is the video of the problem: https://www.youtube.com/watch?v=mUjMUSNhDcQ
This question is really vague, but here is an answer that assumes derHugo is correct, and you are asking how to pick random positions without duplicates:
You are storing your rooms inside a list, so all you have to do is check to see if the newly generated position is the same as any of the existing game objects in the list.
So first generate random floats in a new vector x, y, and z, and then check if that position is in the list of previously generated positions. If the list does not contain a gameobject with that position, then its unique, use it and add it to the list. If it isn't unique, throw it out and generate a new one until it is unique.
You'll want to check that the newly generated position is far enough away from the previously existing ones that they don't overlap, taking the size of the object into consideration.
Related
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 1 year ago.
For some reason when I'm in the normal view in-game I am able to link the scripts that I need to call in order to make an animation like so
however, whenever I start the game it automatically removes them from the slots, and it doesn't work
I am completely clueless as to why this may be happening and unity keep giving me errors that say that I'm not setting an instance to my script I really have no clue why this may be happening.
I have 3 scripts that I'm working with that is giving me the problem
one is the main script for the enemy vision (I am referencing the other scripts in this one)
the second is the enemy animation script which makes him go from idle to attack and the third is an animation for the gun model since I had to make it follow the hands of the enemy
scripts attached bellow
1st script(enemyAI):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAi : MonoBehaviour
{
public bool detected;
GameObject target;
public Transform enemy;
public GameObject Bullet;
public Transform shootPoint;
public float shootSpeed = 10f;
public float timeToShoot = 1f;
public EnemyAni Animation;
public GunAni GunAnimation;
void Start()
{
Animation = GetComponent<EnemyAni>();
GunAnimation = GetComponent<GunAni>();
}
public void Update()
{
//makes the enemy rotate on one axis
Vector3 lookDir = target.transform.position - transform.position;
lookDir.y = 0f;
//makes enemy look at the players position
if (detected)
{
enemy.LookAt(target.transform.position, Vector3.up);
enemy.rotation = Quaternion.LookRotation(lookDir, Vector3.up);
}
if (detected == true)
{
Animation.LookPlayer = true;
GunAnimation.ShootPlayer = true;
}
if (detected == false)
{
Animation.LookPlayer = false;
GunAnimation.ShootPlayer = false;
}
}
//detects the player
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
detected = true;
target = other.gameObject;
}
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
{
detected = false;
}
}
}
2nd Script (EnemyAnimation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAni : MonoBehaviour
{
public Animator animator;
public bool LookPlayer;
public void Start()
{
animator = GetComponent<Animator>();
}
public void Update()
{
if (LookPlayer == true)
{
animator.SetBool("IsShootingStill", true);
}
else
{
animator.SetBool("IsShootingStill", false);
}
}
}
3rd script (GunAnimation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunAni : MonoBehaviour
{
public Animator animator;
public bool ShootPlayer;
public void Start()
{
animator = GetComponent<Animator>();
}
public void Update()
{
if (ShootPlayer == true)
{
animator.SetBool("IsShooting", true);
}
else
{
animator.SetBool("IsShooting", false);
}
}
}
Your code:
void Start()
{
Animation = GetComponent<EnemyAni>();
GunAnimation = GetComponent<GunAni>();
}
Searches the GameObject that holds the EnemyAI script for EnemyAni and GunAni. If the GameObject doesn't have those it will return null.
Sounds like you want to remove that code.
Note that if the scripts exist on a child of that GameObject you will need to use GetComponentInChildren
There are some things to review, good practices:
GameObject target is private for C#, but it is better to put private before.
variable names like Bullet Animation GunAnimation should always begin with lowercase, because they might be confused with a Class Name (search in internet for CamelCase and PascalCase).
Name should be clear. EnemyAni Animation is not clear enough, because Animation maybe any animation (player, enemy, cube, car, etc.).
It is better enemyAnimation to be clear, as you did with GunAnimation (only just with lower case at beginning)
As slaw said, the Animation must be in the GO attached.
about last item
Let's say you have an empty GO (GameObject) called GameObject1 and you attach EnemyAi script to it.
In Runtime (when game mode begins), it will try to find Animation = GetComponent<EnemyAni>();, but it won't find it
Why?
Because GetComponent<> searches for the Component(Class) that is in the <> (in this case EnemyAni) in its GO (in this case, GameObject1), but the unique script attached to GameObject1 is EnemyAI.
So, you have 3 options:
Attach EnemyAni to GameObject1
Create another GO (for example, GameObjectEnemyAni), attach the script EnemyAni and drag and drop GameObjectEnemyAni to GameObject1 and delete Animation = GetComponent<EnemyAni>(); in Start
Keep in mind: if you leave that line of code, instead of getting the script EnemyAni from GameObjectEnemyAni, it will run the code Animation = GetComponent<EnemyAni>(); in Start, and obviously, it won't find it
Create events. It's a really good practice for avoiding code tight coupling, but that is more advanced stuff.
Im making a terror game, and i want to spawn little collectibles in my scene, my code works fine, but they repeat the world location in every instantiate:
using UnityEngine;
public class objectivesGeneration : MonoBehaviour
{
GameObject[] objSpawn;
GameObject objActual;
public GameObject obj;
int index;
void Start()
{
objSpawn = GameObject.FindGameObjectsWithTag("spawnObj");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
generateNewObj();
}
}
public void generateNewObj()
{
index = Random.Range(0, objSpawn.Length);
objActual = objSpawn[index];
createObj();
}
public void createObj()
{
Instantiate(obj, objActual.transform.position, objActual.transform.rotation);
}
}
Can somebody help me?
Your question can be understood in two ways. Leoverload gave the answer to the repeating position.
In case you do not want to repeat the same type of object (so all objects are unique), then do the following:
Turn objSpawn into a List<GameObject> variable and in the start() function, add all instances from the array that's returned from FindGameObjectsWithTag to it.
Turn objSpawn.Length into objSpawn.Count (which does the same but for lists)
In that same function add: objSpawn.Remove(objActual) at the end.
If those objects are destroyed at some point and you want to create new instances of destroyed objects, ensure that in their Destroy event, you Add(gameObject) to the list again.
Am giving you instructions instead of just code so you can learn to do this yourself in the future.
Also I have the feeling you need to learn how arguments\parameters work. Then you can ommit that objActual variable and instead pass the object to createObj(GameObject theObj) directly.
You are spawning the object in the same position objActual.transform.position
You should set the limits of the spawn with 2 variables, for example:
public float startx;
public float endx;
public float starty;
public float endy;
Then you can easily Instantiate randomly in this positions:
float posX = Random.Range(startx, endx);
float posy = Random.Range(starty, endy);
Instantiate(obj, new Vector3(posx, posy, transform.position.z, Quaternion.Identity);
And so you have a random spawn!
Thanks to AlexGeorg for the answer,
this was how my final code was
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class objectivesGeneration : MonoBehaviour
{
public GameObject recipient;
public List<GameObject> objSpawns = new List<GameObject>();
int spawnSelected;
void Start()
{
//Set all the objs with the tag spawnLocation inside of the list
foreach (GameObject OBJ in GameObject.FindGameObjectsWithTag("spawnLocation"))
{
objSpawns.Add(OBJ);
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
//Check if there are more possible spawns
if (objSpawns.Count > 0)
generateNewObj(recipient);
}
}
public void generateNewObj(GameObject frasco)
{
//Get a random number of the list which contains one obj
spawnSelected = Random.Range(0, objSpawns.Count);
//Instantiate the GameObject in the location of the random selected object
Instantiate(frasco, objSpawns[spawnSelected].transform.position, objSpawns[spawnSelected].transform.rotation);
//Delete that possible location from the list
objSpawns.RemoveAt(spawnSelected);
}
}
Just wondering if anyone could help me out with a small issue in my code. On line 60, there is an error essentially telling me what I am referencing to does not exist. To my knowledge it does, but I am very new to Unity. I am trying to create a random dungeon generator for a University project. My classes are below:
using System.Collections.Generic;
using UnityEngine;
public class RoomSpawner : MonoBehaviour
{
public int openingDirection;
//1 = need bottom door
//2 = need top door
//3 = need left door
//4 = need right door
//So for a room with a door on the right, you will type 3, as a left door is needed in the next room to connect the two rooms.
private RoomTemplates templates;
private int rand;
private bool spawned = false;
private Destroyer destroyer;
void Start()
{
templates = GameObject.FindGameObjectWithTag("Rooms").GetComponent<RoomTemplates>();
Invoke("Spawn", 0.1f);
}
void Spawn()
{
if (spawned == false)
{
rand = Random.Range(0, templates.bottomRooms.Length);
if (openingDirection == 1)
{
//Need to spawn room with BOTTOM door
Instantiate(templates.bottomRooms[rand], transform.position, templates.bottomRooms[rand].transform.rotation);
}
else if (openingDirection == 2)
{
//Need to spawn room with TOP door
Instantiate(templates.topRooms[rand], transform.position, templates.topRooms[rand].transform.rotation);
}
else if (openingDirection == 3)
{
//Need to spawn room with LEFT door
Instantiate(templates.leftRooms[rand], transform.position, templates.leftRooms[rand].transform.rotation);
}
else if (openingDirection == 4)
{
//Need to spawn room with RIGHT door
Instantiate(templates.rightRooms[rand], transform.position, templates.rightRooms[rand].transform.rotation);
}
spawned = true;
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Spawn Point"))
{
if (other.GetComponent<RoomSpawner>().spawned == false && spawned == false)
{
Instantiate(templates.closedRoom, transform.position, Quaternion.identity);
}
spawned = true;
}
}
}
using System.Collections.Generic;
using UnityEngine;
public class Destroyer : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
Destroy(other.gameObject);
}
}
This issue is causing the entry room to be blocked by closed rooms, which are meant to block exits out into the scene view. I have followed BlackThornProd's tutorial on this, but cannot figure out what I have done wrong. Here is a link to the tutorial.
Many thanks!
EDIT
Here I have included the RoomTemplates Class if it helps. Many Thanks!
using System.Collections.Generic;
using UnityEngine;
public class RoomTemplates : MonoBehaviour
{
public GameObject[] bottomRooms;
public GameObject[] topRooms;
public GameObject[] leftRooms;
public GameObject[] rightRooms;
public GameObject closedRoom;
}
Are templates a gameobject? If so, I think you can try something like this:
private GameObject templates;
void Start()
{
templates = GameObject.FindGameObjectWithTag("Rooms");
RoomTemplates Templates = templates.GetComponent<RoomTemplates>();
Invoke("Spawn", 0.1f);
}
My problem is that when all the enemies are killed the scene that should be loaded is not loading. I did add the scene to the Build setting (it has an index of 3) but it is still not loading. The script I created is attached to an empty object and not directly to the sprite (is that okay?). Can someone tell me why the scene isn't loading? Thank you.
This image is for to show you the EnemySpawner empty object inspector
EnemySpawner Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemySpawner : MonoBehaviour {
[SerializeField] GameObject EnemyPreFab;
[SerializeField] int MaxEnemies = 30;
[SerializeField] float EnemySpawnTime = 1.00001f;
[SerializeField] GameObject FirstWaypoint;
int CurrentNumOfEnemies = 0;
public LevelManager myLevelManager;
public int maximumnumberofhits = 0;
int timesEnemyHit;
IEnumerator SpawningEnemies()
{
while(CurrentNumOfEnemies <= MaxEnemies)
{
GameObject Enemy = Instantiate(EnemyPreFab, this.transform.position, Quaternion.identity);
CurrentNumOfEnemies++;
yield return new WaitForSeconds(EnemySpawnTime);
}
}
void Start()
{
StartCoroutine(SpawningEnemies());
timesEnemyHit = 0;
if (this.gameObject.tag == "EnemyHit")
{
CurrentNumOfEnemies++;
}
}
void OnCollisionEnter2D()
{
timesEnemyHit++;
if (timesEnemyHit == maximumnumberofhits)
{
CurrentNumOfEnemies--;
Destroy(this.gameObject);
}
if (CurrentNumOfEnemies == 0)
{
myLevelManager.LoadLevel("NextLevelMenu");
Debug.Log("LevelLoaded");
}
}
}
LevelManger script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelManager : MonoBehaviour {
public void LoadLevel(string name)
{
print("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
EnemyShooting Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyShooting : MonoBehaviour {
[SerializeField] float EnemyLaserSpeed = 10f;
[SerializeField] float EnemyLaserFireTime;
[SerializeField] GameObject LaserBulletEnemyPreFab;
[SerializeField] int MaxNumberOfHits = 1;
int CurrentNumberOfHits = 0;
Coroutine FireCoroutine;
void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject.tag == "PlayerLaser")
{
if(CurrentNumberOfHits < MaxNumberOfHits)
{
CurrentNumberOfHits++;
Destroy(collider.gameObject);
Score.ScoreValue += 2;//The user will be rewarded 1 point
}
}
}
void DestroyEnemy()
{
if(CurrentNumberOfHits >= MaxNumberOfHits)
{
Destroy(gameObject);
}
}
private void Fire()
{
FireCoroutine = StartCoroutine(ShootContinuously());
}
void BecomeVisible()
{
Fire();
}
IEnumerator ShootContinuously()
{
while (true)
{
GameObject LaserBulletEnemy = Instantiate(LaserBulletEnemyPreFab, this.transform.position, Quaternion.identity) as GameObject;
LaserBulletEnemy.GetComponent<Rigidbody2D>().velocity = new Vector2(0, EnemyLaserSpeed);
EnemyLaserFireTime = Random.Range(0.5f, 0.9f);
yield return new WaitForSeconds(EnemyLaserFireTime);
}
}
// Use this for initialization
void Start () {
BecomeVisible();
}
// Update is called once per frame
void Update () {
DestroyEnemy();
}
}
EnemyPathing script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyPathing : MonoBehaviour {
[SerializeField] List<Transform> WayPoints;
[SerializeField] float EnemyMovingSpeed = 5f;
int WayPointIndex = 0;
void EnemyMoving()
{
if (WayPointIndex <= WayPoints.Count - 1)
{
var TargetedPosition = WayPoints[WayPointIndex].transform.position; //The position of where the enemy needs to go
TargetedPosition.z = 0f;
var MoveThisFrame = EnemyMovingSpeed * Time.deltaTime;
transform.position = Vector2.MoveTowards(this.transform.position, TargetedPosition, MoveThisFrame);
if(transform.position == TargetedPosition)
{
WayPointIndex++;
}
}
else
{
Destroy(gameObject);
}
}
// Use this for initialization
void Start () {
transform.position = WayPoints[WayPointIndex].transform.position;
}
// Update is called once per frame
void Update () {
EnemyMoving();
}
}
Problem
You're checking for collisions on the SPAWNER; when someone hits the Spawner it counts down enemies. But the Spawner doesn't have a collision box in the screenshot so it can never be hit. The Scene changing code can never be called.
So the game, based on the code, looks like this:
Spawn X enemies,
Hit the Spawner X times,
(Removed: Destroy the Spawner,)
Change scene.
I'm guessing this is conceptually incorrect and you actually want to check collisions on the spawned enemies, which will then count up the amount of destroyed enemies, and change the scene when they are all dead.
Solution
Conceptually, what you want is:
Spawn X enemies
Count up variable for every enemy
On Enemy death, count it down
When 0, change scene
So how do we code this?
Well, every enemy needs a reference to the object that holds the count. You can do this in several ways, when I personally do it I usually have just one spawner that is responsible for everyone so I make that one a Singleton, that can be references from anywhere:
EnemySpawner
public class EnemySpawner : MonoBehaviour
{
public static Spawner Instance = null;
int CurrentNumOfEnemies = 0;
// ... etc
void Start()
{
if (Instance == null)
Instance = this;
// Spawn enemies like you do already, CurrentNumOfEnemies++ for every spawned
}
public OnEnemyDeath() {
CurrentNumOfEnemies--;
if (CurrentNumOfEnemies < 1)
{
// You killed everyone, change scene:
LevelManager.LoadLevel("Your Level");
}
}
}
Enemy script (I don't know how your current code looks, but here's a minimal solution based on how I THINK your code looks):
void OnDestroy()
{
// This will run automatically when you run Destroy() on this gameObject
EnemySpawner.Instance.OnEnemyDeath(); // Tell the EnemySpawner that someone died
}
This will only work if you have exactly only ONE spawner. If you have multiple ones you will have to send a reference to the instance of its spawner to every spawned enemy. I can show you how to do ths too, if you wish.
Bonus content
LevelManager doesn't need to be on a GameObject, it can be static instead:
Remove the LevelManager script from any GameObject
Change your LevelManager code to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class LevelManager
{
public static void LoadLevel(string name)
{
Debug.Log("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
Now you can use it from ANYWHERE, without needing to initialize a reference to any script or GameObject:
LevelManager.LoadLevel("My Level");
myLevelManager.LoadLevel("NextLevelMenu"); is never executed, because you destroy the object in the if-test above.
I am making a game in unity where there are enemies spawning horizontally in the left direction. I wrote a piece of code for the enemy prefab that when it passes a position , the counter will increase by one. and if 5 enemies passed the point , a game over scene will appear.
When I run the scene , the count is not incremented !
Here is my code :
using UnityEngine;
using System.Collections;
public class GameOver : MonoBehaviour
{
public int count=0;
public void update()
{
if (GameObject.Find("bunny").transform.position.x == -3.0f)
{
count = +1;
if (count == 5)
{
Application.LoadLevel ("gameOver");
}
}
}
}
and here is the code for enemy movement :
using UnityEngine;
using System.Collections;
public class EnemyMovement : MonoBehaviour {
public float speed ;
void Update () {
transform.Translate(-Vector2.right*speed*(Time.deltaTime));
}
}
thanks in advance
modify :
count = +1; => count++;
and
if (GameObject.Find("bunny").transform.position.x == -3.0f)
Floating point comparison is not recommanded. So modifying the code will be better.
if (((int)GameObject.Find("bunny").transform.position.x) == -3)
and cache finding variable as a member variable in the class, because it's so expensive to find object in a scene.
GameObject bunny = GameObject.Find("bunny");
Jinbom Heo's answer is correct but I'd also like to point out that in this line:
if (GameObject.Find("bunny").transform.position.x == -3.0f)
there may be some times position.x wouldn't equal exactly 3.0f. Better change it to:
if (GameObject.Find("bunny").transform.position.x < -3.0f)
As for the caching part, it should look something like this:
public class GameOver : MonoBehaviour
{
public int count=0;
GameObject bunny;
void Start(){
bunny = GameObject.Find("bunny");
}
public void update()
{
if (bunny.transform.position.x < -3.0f){
// ...
}
}
}