I created a simple game in which the player collides with the enemy, and the score will increase by one. How to do it correctly?
Below is my code:
public class Bird : MonoBehaviour {
public GameObject deathparticales;
public Vector2 velocity = new Vector2(-2, 0);
public float range = 5;
public int score;
private string scoreText;
// Use this for initialization
void Start()
{
score = 0;
rigidbody2D.velocity = velocity;
transform.position = new Vector3(transform.position.x, transform.position.y - range * Random.value, transform.position.z);
}
// Update is called once per frame
void Update () {
// Die by being off screen
Vector2 screenPosition = Camera.main.WorldToScreenPoint(transform.position);
if (screenPosition.x < -10)
{
Die();
}
scoreText = "Score: " + score.ToString();
}
void OnGUI ()
{
GUI.color = Color.black;
GUILayout.Label(scoreText);
}
// Die by collision
void OnCollisionEnter2D(Collision2D death)
{
if(death.transform.tag == "Playercollision")
{
score++;
Destroy(gameObject);
Instantiate(deathparticales,transform.position,Quaternion.identity);
}
}
void Die()
{
Application.LoadLevel(Application.loadedLevel);
}
}
Problem
You were trying to update the score inside everyone of those Birds.
Solution
You need a class whose only purpose is to handle the score.
public class ScoreManager : MonoBehaviour
{
private int score = 0;
private static ScoreManager instance;
void Awake()
{
instance = this;
}
public void incrementScore(int amount)
{
score += amount;
}
public static ScoreManager getInstance()
{
return instance;
}
void OnGUI ()
{
string scoreText = "Score: " + score.ToString();
GUI.color = Color.black;
GUILayout.Label(scoreText);
}
}
Then every time you want to update the score you call ScoreManager from another class, like this:
ScoreManager.getInstance().incrementScore(10);
If all the code is from same script, the problem is that you are increasing the score of one instance of Bird class and then killing that instance:
score++;
Destroy(gameObject);
The gameObject refers to that gameobject into which this instance of Bird class is attached to.
Score variable is just public which means that every instance of Bird has their own, but you can access the variable from other scripts. If you want to have just one score variable shared with all Birds you could make the variable static.
But if you have multiple birds, all of them are going to call OnGUI function and draw the value to the screen. So it might be better idea to change your design and move the score to some other gameObject.
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.
I am a newbie and I am making this android game that if the player has destroyed a meteor, a score is added to its score, well the problem is that I want to display the score in my scoreText, but whenever I initialize it in my Update(), it rapidly adds the score in my scoreText. I just cant figure out how to properly add the score to my scoreText This is my game manager script
public class GameManager : MonoBehaviour
{
public static int displayScores;
public int displayTheScore;
public Text scoreText;
// Start is called before the first frame update
void Start()
{
scoreText.text = "" + displayScores;
}
void Update(){
scoreText.text = "" + displayScores;
displayScores += Meteor.displayScore;
}
}
And this is the script to making the conditions that if the meteor is detroyed, a score is added to displayScore depending on the hits to the meteor
public class Meteor : MonoBehaviour
{
public int maxHealth;
public int currentHealth;
public float speed;
public int hits = 0;
public int score = 100;
public static int displayScore;
public int display;
public int currentHealthChecker;
public static int counter;
public Health healthBar;
public GameObject canvas;
public Transform effect;
// Start is called before the first frame update
void Start()
{
currentHealth = maxHealth;
healthBar.setMaxHealth(maxHealth);
}
// Update is called once per frame
void Update()
{
transform.Translate(Vector2.down * speed * Time.deltaTime);
}
public void OnTriggerEnter2D(Collider2D other){
if(other.transform.tag == "bullet"){
hits++;
canvas.SetActive(true);
currentHealth--;
currentHealthChecker = currentHealth;
healthBar.setHealth(currentHealth);
display = displayScore;
if(currentHealth <= 0){
displayScore = score * hits;
Instantiate(effect, other.transform.position, other.transform.rotation);
Destroy(this.gameObject);
counter++;
canvas.SetActive(false);
}
Destroy(other.gameObject);
}
if(other.transform.tag == "bottom"){
Destroy(this.gameObject);
}
}
}
you are initializing in update method, that why it keeps adding rapidly because update is called every frame. instead of adding in update, add the score value in ontriggerenter2d method.
public void OnTriggerEnter2D(Collider2D other){
if(other.transform.tag == "bullet"){//add score}
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);
}
}
}
I am doing a game in Unity for a school project.
I need to increase the score when the ball collides with brick. What I am doing wrong please? Below is the code that I tried to use. Score starts at zero but never goes more than 1. Any help of how I can fix this issue please?
public class Brick : MonoBehaviour
{
public int hitPoint = 3;
public GameObject explosion;
public GameObject heart;
[Range(0f, 1f)]
public float percentage;
float randNum;
public float fallSpeed = 8.0f;
public Sprite OneHits;
public Sprite TwoHits;
public Sprite ThreeHits;
public Text ScoreText;
private int points;
GameObject ball;
void Start()
{
ball = GameObject.FindGameObjectWithTag("ball");
}
void Update()
{
ScoreText.text = "Score " + points ;
randNum = Random.Range(0f, 1f);
if (hitPoint == 1)
{
this.GetComponent<SpriteRenderer>().sprite = OneHits;
}
if (hitPoint == 2)
{
this.GetComponent<SpriteRenderer>().sprite = TwoHits;
}
if (hitPoint == 3)
{
this.GetComponent<SpriteRenderer>().sprite = ThreeHits;
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "ball")
{
ball = collision.gameObject;
hitPoint--;
points++;
if (hitPoint <= 0)
{
Destroy(this.gameObject);
Instantiate(explosion, transform.position, transform.rotation);
if (randNum < percentage)
{
Instantiate(heart, transform.position, transform.rotation);
}
}
}
}
}
There are a couple of things wrong with your code.
If you destroy the game object the script is running on the script will stop, so I would move your destroy command to the end of your method.
I am guessing that you dont want points to be different for each brick, I would move points to another class called scoreboard or something. For the time being I made points static. This means that only one copy of this variable exists and if one brick adds a point it gets reflected across all bricks.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Test : MonoBehaviour
{
public int hitPoint = 3;
public GameObject explosion;
public GameObject heart;
[Range(0f, 1f)]
public float percentage;
float randNum;
public float fallSpeed = 8.0f;
public Sprite OneHits;
public Sprite TwoHits;
public Sprite ThreeHits;
public Text ScoreText;
public static int points;
GameObject ball;
void Start()
{
ball = GameObject.FindGameObjectWithTag("ball");
}
void Update()
{
ScoreText.text = "Score " + points;
randNum = Random.Range(0f, 1f);
if (hitPoint == 1)
{
this.GetComponent<SpriteRenderer>().sprite = OneHits;
}
if (hitPoint == 2)
{
this.GetComponent<SpriteRenderer>().sprite = TwoHits;
}
if (hitPoint == 3)
{
this.GetComponent<SpriteRenderer>().sprite = ThreeHits;
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("ball"))
{
ball = collision.gameObject;
hitPoint--;
points++;
if (hitPoint <= 0)
{
Instantiate(explosion, transform.position, transform.rotation);
if (randNum < percentage)
{
Instantiate(heart, transform.position, transform.rotation);
}
Destroy(gameObject);
}
}
}
}
I currently have a 2d platformer, where the player is given 9 lives on start, and loses a life everytime they hit an obstacle.
The GameManager script stores and displays the health and the Entity script updates the health in GameManager if the player loses a life.
If the player loses a life, they can respawn in that level.
On completion of a level, the player goes to a new scene "Level Complete", where they can choose to continue to the next level or quit the game.
However, when the player continues to the next level, the currentHealth variable does not persist and resets to 0.
I know WHY this problem is occurring, but I do not know how to fix it.
currentHealth is not being called anywhere in LevelComplete, so on Start in GameManager, it has lost the value of currentHealth.
I can't call GameManager on LevelComplete because it will spawn the player. So I'm not sure how to pass the variable.
Here are my scripts.
Game Manager
using UnityEngine.UI;
using System.Collections;
public class GameManager : MonoBehaviour {
public GameObject player;
private GameCamera cam;
private GameObject currentPlayer;
private Vector3 checkpoint;
public Text livesText;
private Entity livesCount;
public static int startHealth = 9;
public int currentHealth;
public bool playerDeath = false;
public static int levelCount = 2;
public static int currentLevel = 1;
void Start () {
cam = GetComponent<GameCamera> ();
livesText.text = currentHealth.ToString ();
if(GameObject.FindGameObjectWithTag("Spawn")){
checkpoint = GameObject.FindGameObjectWithTag("Spawn").transform.position;
}
SpawnPlayer (checkpoint);
}
private void SpawnPlayer(Vector3 spawnPos) {
currentPlayer = Instantiate (player, spawnPos, Quaternion.identity) as GameObject;
cam.setTarget(currentPlayer.transform);
}
public void Update(){
if(!currentPlayer){
if (Input.GetButtonDown ("Respawn")) {
playerDeath = false;
SpawnPlayer (checkpoint);
}
}
if (playerDeath) {
Destroy (currentPlayer);
}
if (currentPlayer) {
currentHealth = currentPlayer.GetComponent<Entity> ().GetHealth ();
}
livesText.text = currentHealth.ToString ();
}
public void SetCheckPoint(Vector3 cp){
checkpoint = cp;
}
public void EndLevel(){
if(currentLevel < levelCount){
currentLevel++;
Application.LoadLevel("Level Complete");
}
else{
Application.LoadLevel("Game Complete");
}
}
}
Entity
using UnityEngine;
using System.Collections;
public class Entity : MonoBehaviour {
public static int currHealth;
private int checkHealth; //Make sure 0 < health < 9
public void ModifyHealth(int amount){
checkHealth = currHealth + amount;
if (checkHealth >= 9) {
currHealth = 9; //Health can't be greater than 9
}
else if(checkHealth <= 0){
Die (); //End game if health is less than or equal to 0
}
else{
currHealth = checkHealth; //Otherwise, update Health
}
}
public int GetHealth(){
return currHealth;
}
public void SetHealth(int currentHealth){
currHealth = currentHealth;
}
public void Die(){
Application.LoadLevel ("Game Over");
}
}
Level Complete
using UnityEngine;
using System.Collections;
public class LevelComplete : MonoBehaviour {
public GUISkin gSkin;
void OnGUI(){
GUI.skin = gSkin;
if(GUI.Button(new Rect((Screen.width/2) - Screen.width *0.15f,Screen.height/2 - Screen.height *0.05f,Screen.width*0.3f,Screen.height*0.1f),"Next Level")){
Application.LoadLevel ("Level 2");
}
if(GUI.Button(new Rect((Screen.width/2) - Screen.width *0.15f,Screen.height/2 + 20.0f,Screen.width*0.3f,Screen.height*0.1f),"Quit")){
Application.Quit ();
}
}
}