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
Related
I am trying to update the lives of my player in a brick breaker game, however I find myself unable to update the text UI responsible for displaying the score (it always displays 3 lives remaining).
Is there any way to fix this issue?
Here is the relevant code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
// Start is called before the first frame update
int lives;
public bool isDead;
public GameObject[] bricks;
int numberBricks;
public Text livesText;
private void Start()
{
isDead = false;
lives = 3;
livesText.text = "lives left: " + lives.ToString();
Debug.Log(lives);
}
// Update is called once per frame
void Update()
{
livesText.text = "lives left: " + lives.ToString();
numberBricks = GameObject.FindGameObjectsWithTag("brick").Length;
if (numberBricks == 0) {
passLevel();
}
}
private void passLevel() {
}
public void ReduceHealth() {
lives--;
Debug.Log(lives);
}
public void CheckDeath() {
if (lives == 0) {
isDead = true;
}
}
}
the ReduceHealth() function is called at
Ball.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour
{
// Start is called before the first frame update
Vector3 Direction;
int speedFactor;
float minX;
float maxX;
float additionalForce;
Rigidbody2D rb;
public GameObject paddle;
public AudioSource collisionSound;
public GameObject gameManager;
GameManager gm;
void Start()
{
float sign = Mathf.Sign(Random.Range(-1,1));
minX = -10f * sign;
maxX = -2f * sign;
Direction.x = Random.Range(minX,maxX) ;
Direction.y = -40f;
speedFactor = 1;
rb = GetComponent<Rigidbody2D>();
gm = gameManager.GetComponent<GameManager>();
}
// Update is called once per frame
void Update()
{
transform.position += Direction * speedFactor * Time.deltaTime;
}
void Reset() {
transform.position = new Vector3(0.14f,-4.58f,0f);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player" || other.gameObject.tag == "border-vertical") {
Direction.y *=-1;
} else if(other.gameObject.tag=="border-bottom") {
gm.ReduceHealth();
Reset();
}else {
Direction.x *= -1;
}
}
}
and here is the Screenshot of my inspector (GameManager object)
any help would be appreciated.
It looks like the function ReduceHealth() has no references. I created a button to see if the lives are reduced or not, it's working. Here is an attached image of OnClick() in button inspector
A few tips
It is not a good practice to update the text in Update function, instead do it in the function where the value of lives is changing. Also check if the lives == 0 in the same function only, there is no need for the function CheckDeath().
public void ReduceHealth() {
lives--;
livesText.text = "lives left: " + lives.ToString();
if (lives == 0) {
isDead = true;
}
}
It is also not a good practice to keep checking the length of the number of bricks at every frame, instead have a check for it in whichever function the breaking of bricks is dealt with.
For eg when the health of the brick reaches zero, get a reference to the GameManager script and do the length check there.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShootBullets : MonoBehaviour
{
public float bulletDistance;
public bool automaticFire = false;
public float fireRate;
public Rigidbody bullet;
private float gunheat;
private bool shoot = false;
private GameObject bulletsParent;
private GameObject[] startpos;
// Start is called before the first frame update
void Start()
{
bulletsParent = GameObject.Find("Bullets");
startpos = GameObject.FindGameObjectsWithTag("Fire Point");
}
private void Fire()
{
for (int i = 0; i < startpos.Length; i++)
{
Rigidbody bulletClone = (Rigidbody)Instantiate(bullet, startpos[i].transform.position, startpos[i].transform.rotation, bulletsParent.transform);
bulletClone.velocity = transform.forward * bulletDistance;
Destroy(bulletClone.gameObject, 0.5f);
}
}
// Update is called once per frame
void FixedUpdate()
{
if (automaticFire == false)
{
if (Input.GetMouseButtonDown(0))
{
Fire();
}
}
else
{
if (shoot == true)
{
Fire();
shoot = false;
}
}
if (gunheat > 0) gunheat -= Time.deltaTime;
if (gunheat <= 0)
{
shoot = true;
gunheat = fireRate;
}
}
}
now the bullets firing up the air and i want the bullets to fire to a target with physics.
the main goal later is to make some kind of side mission where the player third person view should shoot on object and if and when hitting the object the object will fall down and then the player will be able to pick it up.
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.
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.
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);
}
}
}