I am trying to spawn some GameObjects in game based on the current player position, essentially trying to make an infinite runner type of game...I made a function so I can spawn the pylons with it but for some reason the function gets called only once per frame, It does not get called the second time with a different parameter.
Why does the second call of the function not work?
This is my code:
public class CameraScript : MonoBehaviour {
public float cameraSpeed = 1;
public float horizontalSpeed;
private int spawnIndex;
public float spawnNormPylonDis;
public float spawnCoinPylonDis;
private int currPosition ;
public GameObject[] pilons;
public GameObject spawnMainPoint;
public Transform[] spawnPoints;
public Transform[] coinsSpawnPoint;
public float enamySpeed;
private int currentPoint;
public Transform[] pointPosition;
// Use this for initialization
void Start () {
//This spawns the Pilons.
spawnMainPoint.transform.position = pointPosition [0].position;
currPosition = (int) transform.position.z;
}
// Update is called once per frame
void FixedUpdate () {
spawnMainPoint.transform.position = Vector3.MoveTowards (spawnMainPoint.transform.position, pointPosition[currentPoint].position, Time.deltaTime * horizontalSpeed);
SpawnPylon (pilons[1],spawnPoints,spawnNormPylonDis,"Check function");
SpawnPylon (pilons [0], spawnPoints, spawnCoinPylonDis,"Check the second function");
GetComponent<Rigidbody> ().velocity = transform.forward * cameraSpeed;
//the next if statements make the Pilons spawn randomly and corectly.
if (spawnMainPoint.transform.position == pointPosition [currentPoint].position) {
currentPoint++;
}
if (currentPoint == pointPosition.Length) {
currentPoint = 0;
}
}
/*This function spanws the a GameObject randomly at a GameObject's position and it takes 2 arguments :
Argument 1: type GameObject
2: type Transform[]*/
void SpawnPylon (GameObject whatSpawn,Transform[] whereSpawn,float spawnDistance,string plm)
{
bool hasSpawnedPylon = false;
if (currPosition != (int)transform.position.z)
{
if ((int)transform.position.z % spawnDistance == 0)
{
Debug.Log (plm);
if (!hasSpawnedPylon)
{
//this makes the GameObject spawn randomly
spawnIndex = Random.Range (0, spawnPoints.Length);
//This is instantiationg the GameObject
Instantiate (whatSpawn, whereSpawn [spawnIndex].position, whereSpawn [spawnIndex].rotation);
//this makes shore that the GameObject is not spawned multiple times at aproximetley the same position.
currPosition = (int)transform.position.z;
}
}
}
else
{
hasSpawnedPylon = false;
}
}
}
Here I have a picture with the script in the inspector:
Script Inspector
And here is the console, trying to figure it out by using Debug.Log () for the calls of the function.
Using Debug.Log for the calls.
I tested it and it works fine under some conditions. The problem most likely is that your spawnNormPylonDis and spawnCoinPylonDis are public variables that are the same in the inspector, or spawnNormPylonDis is a multiple of spawnCoinPylonDis, pretty much with your code, you cannot have spawnNormPylonDis be 1. If they are then in the SpawnPylon function because it has already spawned one. You yourself commented.
//this makes shore that the GameObject is not spawned multiple times at aproximetley the same position.
currPosition = (int)transform.position.z;
I tested this with values of 2 and 3 and both functions got called, however, it would skip the second function if they were both divisible (6).
Therefore you got bottlenecked at this line of code because you set the currPosition after the first function call.
if (currPosition != (int)transform.position.z)
By making a basic wrapper function all is well in the world! So we set the currPosition after we checked both spawning calls, and voila, problem solved.
void SpawnPylon(int index, Transform[] spawnPoint, float dist, string debug)
{
if ((int)transform.position.z % dist == 0)
{
//Debug.Log(debug);
//this makes the GameObject spawn randomly
spawnIndex = Random.Range(0, spawnPoints.Length);
//This is instantiationg the GameObject
GameObject go = Instantiate(pilons[index], spawnPoint[spawnIndex].position, spawnPoint[spawnIndex].rotation) as GameObject;
go.name = string.Format("ID: {0}", index);
}
}
private void SpawnMultiplePylons()
{
if (currPosition != (int)transform.position.z)
{
SpawnPylon(1, spawnPoints, spawnNormPylonDis, "Spawn1");
SpawnPylon(0, spawnPoints, spawnCoinPylonDis, "Spawn2");
currPosition = (int)transform.position.z;
}
}
I tested this and it worked. I hope that it also works for you!
Related
Ive been at it all day trying to solve this, because I dont want to make a separate script for every bullet, instead I want to have a single EnemyBulletMovement script, that can move each bullet in a different way, depending on the BulletType int that I feed into it when I instantiate it.
The problem is that if I fire multiple bullets at the same time, they will all change BulletType as soon as another bullet is instantiated, because they are all sharing the same script.
My goal is to have all bullets have a private script, but no matter what I try the variable will still be changed for all active bullets every time I try to change it for just one of them.
EnemyController script that Instantiates the bullets and gives them a BulletType value:
GameObject enemyBullet = ObjectPooler.SharedInstance.GetPooledEnemyBullet();
if (enemyBullet != null)
{
Quaternion rotationAmount = Quaternion.Euler(0, 0, 0);
Quaternion postRotation = transform.rotation * rotationAmount;
enemyBullet.transform.position = transform.position;
enemyBullet.transform.rotation = postRotation;
enemyBullet.SetActive(true);
enemyBullet.GetComponent<EnemyBulletMovement>().SetBullet(2);
}
GameObject enemyBullet2 = ObjectPooler.SharedInstance.GetPooledEnemyBullet();
if (enemyBullet2 != null)
{
Quaternion rotationAmount = Quaternion.Euler(0, 3, 0);
Quaternion postRotation = transform.rotation * rotationAmount;
enemyBullet2.transform.position = transform.position;
enemyBullet2.transform.rotation = postRotation;
enemyBullet2.SetActive(true);
enemyBullet2.GetComponent<EnemyBulletMovement>().SetBullet(0);
}
Shared EnemyBulletMotion script, that gets the bulletType from the above script. This is the problem:
public void SetBullet(int typeIndex)
{
bulletType = typeIndex;
Debug.Log(bulletType);
}
private void BulletMotion()
{
if (bulletType == 0)
{
rb.velocity = pos;
}
if (bulletType == 1)
{
axis = Mathf.Sin(Time.deltaTime * -frequency) * magnitude * transform.up; // Up down Wave motion
transform.Rotate(Vector3.forward * sideMag); // Side Sway Motion (+ Wave = Spiral Motion)
rb.velocity = pos + axis; // Combine all Motions
}
if (bulletType == 2)
{
Debug.Log("Shootin");
axis = Mathf.Sin(Time.deltaTime * -frequency) * magnitude * transform.up; // Up down Wave motion
transform.Rotate(Vector3.forward * -sideMag); // Side Sway Motion (+ Wave = Spiral Motion)
rb.velocity = pos + axis; // Combine all Motions
}
}
EDIT: This Debug Shootin never appears in my console though, so maybe the bulletType is not being read correctly here after all? For Clarification, the first Debug.Log(bulletType); return a 0 or a 2 so it is working. But the second Debug.Log("Shootin"); does not get printed, ever.
I tried private, static, and made an Instance out of the whole EnemyBulletMovement script but nothing works.
When I debug this code, the script works, the bulletType will change from 2 to 0 and back, but when it does it will change both bullets, so both will fly the same way, when what I want is to have each bullet follow its own motion. If there is no way to do this I will have to create a separate script for each bulletmotion, but im only showing 2 here and I already have a lot of them, thats why I wanted to try this out and make it work with if statements and then use case statements later.
EDIT: Added ObjectPooler script below.
public List<GameObject> pooledEnemyBullets;
public GameObject EnemyBulletToPool;
public int EnemyBulletAmountToPool;
pooledEnemyBullets = new List<GameObject>();
public static ObjectPooler SharedInstance;
void Awake()
{
SharedInstance = this;
}
void Start()
{
for (int i = 0; i < EnemyBulletAmountToPool; i++)
{
GameObject obj3 = (GameObject)Instantiate(EnemyBulletToPool);
obj3.SetActive(false);
pooledEnemyBullets.Add(obj3);
}
}
public GameObject GetPooledEnemyBullet()
{
//1
for (int i = 0; i < pooledEnemyBullets.Count; i++)
{
//2
if (!pooledEnemyBullets[i].activeInHierarchy)
{
return pooledEnemyBullets[i];
}
}
//3
return null;
}
}
Bullet type should be declared as private int bulletType. If you declare bullet type as private static int bulletType it will be same for all bullets.
If it is already declared as private int bulletType, then you should check the logic where you use object pooler. You may be processing the same bullet over and over, or processing all bullets in the pool.
Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}
im working at a infinit runner game and i want to add objectpooling to the platforms, but i got 2 errors:
Assets\PlatformGenerator.cs(37,26): error CS1501: No overload for method 'SpawnObject' takes 1 arguments
Assets\ObjectPool.cs(36,16): error CS0165: Use of unassigned local variable 'ToReturn'
Here is the platform generator script:
{
public GameObject ThePlatform;
public Transform GenerationPoint;
public float DistanceBetween;
private float PlatFormWidth;
public float DistanceBetweenMin;
public float DistanceBetweenMax;
public ObjectPool PlatformPool;
// Start is called before the first frame update
void Start()
{
PlatFormWidth = ThePlatform.GetComponent<BoxCollider2D>().size.x;
}
// Update is called once per frame
void Update()
{
if(transform.position.x < GenerationPoint.position.x)
{
DistanceBetween = Random.Range(DistanceBetweenMin, DistanceBetweenMax);
transform.position = new Vector3(transform.position.x + PlatFormWidth + DistanceBetween,
transform.position.y, transform.position.z);
//Instantiate(ThePlatform, transform.position, transform.rotation);
PlatformPool.SpawnObject(ThePlatform, transform.position, transform.rotation);
}
}
}
Here is the ObjectPooler script:
{
public GameObject ObjectToPool;
public List<GameObject> ThePool = new List<GameObject>();
public int StartAmount;
// Start is called before the first frame update
void Start()
{
for(int i = 0; i < StartAmount; i++)
{
ThePool.Add(Instantiate(ObjectToPool));
ThePool[i].SetActive(false);
ThePool[i].transform.parent = transform;
}
}
// Update is called once per frame
void Update()
{
}
public GameObject SpawnObject(Vector3 Position)
{
GameObject ToReturn;
ToReturn = ThePool[0];
ThePool.RemoveAt(0);
ToReturn.transform.position = Position;
ToReturn.SetActive(true);
return ToReturn;
}
}
hope you can help me because im new at programming and don't now hot fix things like this and if you see other things i should improve it would be very nice if you tell it me.
In the Object Pooler script you put the following function public GameObject SpawnObject(Vector3 Position) which takes only one argument for position. But in the platform generator you are calling the function with three arguments PlatformPool.SpawnObject(ThePlatform, transform.position, transform.rotation). So, replace the PlatformPool.SpawnObject(ThePlatform, transform.position, transform.rotation) in the platform generator script with PlatformPool.SpawnObject(transform.position);.
First error, your method is only asking for one argument. You are passing 3. (Vector3 just represents the x,y,z points of a transform or a gameObject.) If you wanna dynamically pass a Vector3 object, you can do something like this
Vector3 v = new Vector3 (0,0,0);
then you pass it on to your method SpawnObject(v);
For the second error, make sure ThePool list is not empty and you are assigning them properly, because you are assigning it to ToReturn.
Logging helps a lot, try to debug something like this
Debug.LogError(ThePool == null);
or check it's length
Debug.LogError(ThePool.Length);
if it is, try to add a condition so that you don't get an error
if(ToReturn != null)
// ....
Hello everybody so my program is quite a simple one,what i'm trying to do is do have to objects and destroy one of them by pressing either H for hero or E for enemy damage is random but i made it very close so they go down quickly , non the less i can't seem to figure it out after hours of trying and googling please help, because it's still not working :(
So the first script is placed in the both objects i have a cube named Hero with a Tag named Hero as well,and the second object is a sphere with the name Enemy and with the same name of the Tag.
This is the script they both own :
public class Character : MonoBehaviour
{
public float HP = 100;
public float minDmg = 23f;
public float maxDmg = 25f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public float DamageDealt()
{
float damage;
damage = Random.Range(minDmg, maxDmg);
return damage;
}
}
And the second script is placed onto the camera which should be fine i think ?
public class FightControl : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject _hero = GameObject.Find("Hero");
GameObject _villain = GameObject.Find("Enemy");
//object se pravi da bi moglo da se preuzme var iz Character u obkejtu _hero
Character chscript = _hero.GetComponent<Character>();
//object se pravi da bi moglo da se utice na hp u objectu _villain
Character chscripta = _villain.GetComponent<Character>();
if (Input.GetKeyDown(KeyCode.H))
{
float dmg = chscript.DamageDealt();
chscripta.HP = chscript.HP - dmg;
if (chscripta.HP <= 0)
{
Destroy(_villain);
}
}
if (Input.GetKeyDown(KeyCode.E))
{
Character chscript1 = _villain.GetComponent<Character>();
float dmg1 = chscript1.DamageDealt();
Character chscript1a = _hero.GetComponent<Character>();
chscript1a.HP -= dmg1;
if(chscript1a.HP <= 0)
{
Destroy(_villain);
}
}
}
// Update is called once per frame
void Update()
{
}
}
Your second script is not working because you are running everything only once.
The Start() method runs only once once the game object has been enabled. You are reading input during the start method, but not during any other frames.
Consider moving some of your code to the Update() method so that you are reading the input every frame:
void Update()
{
if (Input.GetKeyDown(KeyCode.H))
{
float dmg = chscript.DamageDealt();
chscripta.HP = chscript.HP - dmg;
if (chscripta.HP <= 0)
{
Destroy(_villain);
}
}
if (Input.GetKeyDown(KeyCode.E))
{
Character chscript1 = _villain.GetComponent<Character>();
float dmg1 = chscript1.DamageDealt();
Character chscript1a = _hero.GetComponent<Character>();
chscript1a.HP -= dmg1;
if(chscript1a.HP <= 0)
{
Destroy(_villain);
}
}
}
Rather than finding the game object during runtime, you could consider creating a reference and configuring the hero/villain references by dragging the gameobject to the inspector after making this change to your code. You can do this for both Components and GameObjects, so with the following code you should have 4 "slots" in your inspector:
public class FightControl : MonoBehaviour
{
public GameObject _hero;
public GameObject _villain;
public Character chscript;
public Character chscripta;
//... the rest of your code (the Update() method above)
This is my spawning script belowwritten in c# The script should create objects randomly in the scene.
The issue is that I'm getting this error at runtime.
IndexOutOfRangeException: Array index is out of range.
CreateEasterEggs.MakeThingToSpawn () (at Assets/CreateEasterEggs.cs:52)
CreateEasterEggs.Update () (at Assets/CreateEasterEggs.cs:28)
Not sure what I have done wrong, thinking its something to do with the game object?
Thank you.
using UnityEngine;
using System.Collections;
public class CreateEasterEggs : MonoBehaviour
{
public float secondsBetweenSpawning = 0.1f;
public float xMinRange = -25.0f;
public float xMaxRange = 25.0f;
public float yMinRange = -5.0f;
public float yMaxRange = 0.0f;
public float zMinRange = -25.0f;
public float zMaxRange = 25.0f;
public GameObject[] spawnObjects; // what prefabs to spawn
private float nextSpawnTime;
void Start ()
{
// determine when to spawn the next object
nextSpawnTime = Time.time+secondsBetweenSpawning;
}
void Update ()
{
// if time to spawn a new game object
if (Time.time >= nextSpawnTime) {
// Spawn the game object through function below
MakeThingToSpawn ();
// determine the next time to spawn the object
nextSpawnTime = Time.time+secondsBetweenSpawning;
}
}
void MakeThingToSpawn ()
{
//Start the vector at an invalid position
Vector3 spawnPosition = new Vector3(0, 0, 0);
//while we are not in the right range, continually regenerate the position
while ((spawnPosition.z < 4 && spawnPosition.z > -4) || (spawnPosition.x < 4 && spawnPosition.x > -4))
{
spawnPosition.x = Random.Range (xMinRange, xMaxRange);
spawnPosition.y = Random.Range (yMinRange, yMaxRange);
spawnPosition.z = Random.Range (zMinRange, zMaxRange);
}
// determine which object to spawn
int objectToSpawn = Random.Range (0, spawnObjects.Length);
// actually spawn the game object
GameObject spawnedObject = Instantiate (spawnObjects [objectToSpawn], spawnPosition, transform.rotation) as GameObject;
// make the parent the spawner so hierarchy doesn't get super messy
spawnedObject.transform.parent = gameObject.transform;
}
}
IndexOutOfRange means that you tried to access to an element of an array that doesn't exist.
In your case as you are doing it with Random.Range (0, spawnObjects.Length); Then the only possible case is that your array is empty.
Try to Debug.Log(spawnObjects.Length): before the Instantiate and you will see that in fact your array of gameobjects is empty as It will return 0.