Making lives in Block Breaker

Just to confirm - there is an object called “Canvas” on the Start Menu?

on the Scene named “Start”, yes. Where all the buttons and the title are.

That isn’t going to help us :slight_smile:

If you look at the LevelManager script you’ll see this:

/// <summary>
/// Initialisation
/// </summary>
private void Start()
{
    if (GameObject.Find("Canvas"))          // This is a bit messy.  It checks to see if there is a canvas, if so, updates the remaining lives
    {                                       // which prevents a null reference exception error occuring on non-playable levels (Win, Loose, Start)
        UpdateUIPlayerLivesRemaining();     
    }
}

Read the comments to the right…

On the completed game I downloaded from the course, the canvas object on Start, Win and Lose is called Play Space - thus the only place an object called Canvas would appear would be on the playable scenes.

The code will be trying to update a UI Text object that it cannot find because of this.

Can you rename the canvas object on Start, Win and Lose to Play Space and see what happens.

Ok well that solves the error showing up, but the number remains unchanged sadly :frowning:

Phew… progress at last! lol :slight_smile:

Ok, so running through the steps below - can you indicate if any of them are incorrect for me;

  • you run the game from the Start scene
  • you click Play
  • the level_01 scene is loaded
  • the UI Text object is at that top of the screen and displays “lives x 5”, which is from the script and not from the text property of the Inspector (which is set to "lives x ").
    -
  • you launch the ball
  • you dont hit the ball with the paddle
  • the ball hits the lose collider
  • the ball is placed back onto the paddle
  • the number of lives do not update at the top of the scene

Anything there incorrect?

the only part that doesn’t happen is when it hits the lose collider it doesn’t get placed onto the paddle nor do the lives update.

…**** I should’ve mentioned that earlier maybe? Sorry, I bet that’s probably the one vital clue.

hehe, it’s ok, but it does feel like a step closer, here’s the method;

/// <summary>
/// Handles the OnTriggerEnter2D event triggered by another object entering a trigger collider attached to the LoseCollider
/// </summary>
/// <param name="collider">The other Collider2D involved in the collision</param>
private void OnTriggerEnter2D(Collider2D collider)
{
    _gameManager.PlayerLosesLife();
}

So, let’s pop a Debug.Log("Ball hit collider"); statement in there, so that when it occurs we should see something in the Console. Pop that above the _gameManager.PlayerLosesLife(); method call and then let me know what happens please :slight_smile:

Oh, on yours you’ll need to remove the undescore (I spotted you had removed them all)…

/// <summary>
/// Handles the OnTriggerEnter2D event triggered by another object entering a trigger collider attached to the LoseCollider
/// </summary>
/// <param name="collider">The other Collider2D involved in the collision</param>
private void OnTriggerEnter2D(Collider2D collider)
{
    Debug.Log("Ball hit collider");
    gameManager.PlayerLosesLife();
}

yeah I remove the underscore because one of my ideas was there might have been a error on my part where your code have all the underscores, but mine didn’t.

Don’t know what’s going on, but it doesn’t appear to even show the debug at all…i’ve got a theory to this though.

later…

I tried out my theory but didn’t work I’m afraid.

I went back to my original script for the Lose Collider and changed it from trigger to collision, however that didn’t appear to work and complained about it not being the right type, I also played around with changing it from (Collider2D collider) but it had the same issue and then I looked closely at the code and thought about what Ben said at the begining of the course when we were first making the collider and the difference between Trigger and Collision and that’s when it hit me.

For the past few times when looking at your script I had wondered about the change to “OnTriggerEnter2D” but I didn’t mention it (****** should’ve) and have been acting under the assumption that the Lose Collider was meant to stay the same, however when I ticked the “on trigger” box as I missed the first ball it went from 5 to 4.

I was so excited I was about to post, but then I thought I should do more testing and I discovered that the lives don’t go to 0. They went 5, 4, 3, 2, 1, then game over. So I may alter it to -1 or something so that, like in many arcade games I’ve played in the past, you have a 0 value.

Thank you for staying with me this long in this issue and I really wish I brought up the collider bit first instead of simply left it, believing that we were both on the exact same page on everything of ALL the items.

As frustraiting as this has been, I have to say I have been laughing at myself for overlooking something that I KNEW was off, but didn’t mention it. headslap

Atleast that’s that mystery solved Now…onto building more levels! I may ask for help with other topics, but consider this one SOLVED!

The underscores don’t do anything particularly clever, sorry, I should have probably explained this when I posted the code originally, as it is slightly different from what is shown on the course. Whenever I create member variables, e.g. those variables that are specific to the object (defined under the class definition), I use an underscore in front of their names. I am then able to distinguish more easily between the class level variables and variables that are specific to methods contained within. It won’t make a lot of difference to you, but in other languages you may need to use alternative names to be prudent, for example GameManager and gameManager may not be recognised as being different which could lead to problems.

BOOM! There it is… well spotted! It would have taken a little while longer before we had screen shots of everything on here - so nicely spotted :slight_smile:

And yes, with 5 lives, once the counter reaches zero that means the player has played their 5 lives, if you want it to actually display zero (which would make sense) - you’ll need an offset of 1. Just remove the equals sign in the remaining lives check, so it only triggers GameOver() when it’s < 0

hehe, its ok and you’re more than welcome… often some issues are quick and easy to spot, you tend to see the same questions asked by new students as previous ones. As I mentioned before it is a bit harder to do this remotely without having the full project files. I didn’t check that option myself, that was from the completed course files on the Unity course for reference.

hehe, go with your gut :slight_smile: It’s highly unlikely here or on the Facebook pages anyone would berate you if you were wrong, everyone here is just working together to try and help each other out - that’s community :slight_smile:

Please do - I am firm believer that every question asked here has value, as there may well be other students who want to know the same thing but don’t have the confidence to be the first to post the question - so all questions and answers can have a great benefit to all :slight_smile:

On a personal note, I have enjoyed going through this - will look forward to seeing your game showcased here with some more levels to play :slight_smile:

Well eitherway, again thank you for sticking with me on this. It’s a bit weird that’s what it was in the complete file, but it might’ve been different in the course vs finished files.

Well at the end of the day, I hope this whole situation has been education for anyone else reading this even years later as a lesson on how the most simple things can be the biggest problem and for them to feel free to ask and i’m sure either you, me or Ben might respond and help them :smiley:

1 Like

I kinda wish I posted my completed scripts. Rebuilding the game after losing all my stuff and before I finished making it and rebuilding it certainly not an easy case. I hope to get this done though, then I can continue with the course. Again, thanks a lot of helping out with this. Once I’m done, I’ll upload the complete scripts and probably do the same on my Itch.io account to help out other fellow developers.

1 Like

Sorry to hear you have lost all of your work Aron, so frustrating.

Before you restart, checkout Git and get yourself a GitHub or BitBucket account, you will then be able to store to files in source control, useful for when you make changes that perhaps dont work (you can roll back), but more importantly, for times like this, when something bad happens and you need to get it all back.

There’s a free Git course on Udemy called Git Started with GitHub, highly recommended.

Hope this is of use and I will look forward to seeing your game(s) as and when you’re back on track.

On a side note - it’s also more useful for people trying to assist if the full code is posted as we get a better idea of the whole project :slight_smile:

1 Like

Right, here we go I decided to be patient, went back through this thread to go step by step, for a moment I was worried of it not working, until I re-wired the button to add the GameManager function then the lives would work perfectly fine.

The only issue I have if that a few scripts are reading as errors because they don’t seem to know what the GameManager is, however during play there is no problem and they work fine so it shouldn’t be a big issues.

For future reference to anyone who might want to use some of this code here are all the scripts, as well as making sure to follow Rob’s directions about the UI being implimented and re-wiring the buttons.

Ball:

using UnityEngine;

public class Ball : MonoBehaviour
{

private Paddle _paddle = null;
private bool _inMotion = false;
private Vector3 _paddleToBallVector;

// Initialisation
private void Start()
{
    Initialise();
}

/// <summary>
/// Update
/// </summary>
private void Update()
{
    DetermineMotionState();
}

/// <summary>
/// Sets the position of the Ball to the Paddle
/// </summary>
private void Initialise()
{
    _paddle = GameObject.FindObjectOfType<Paddle>();
    _paddleToBallVector = this.transform.position - _paddle.transform.position;
}

/// <summary>
/// Determins the current state of the Ball's motion. If the Ball isn't in motion it's position is reset.
/// </summary>
private void DetermineMotionState()
{
    if (_inMotion != true)
    {
        SetLaunchPosition();
        LaunchOnMouseClick();
    }
}

/// <summary>
/// Sets the launch position of the Ball on the Paddle and keeps it there until launch
/// </summary>
private void SetLaunchPosition()
{
    this.transform.position = _paddle.transform.position + _paddleToBallVector;
}

/// <summary>
/// Launches the Ball upon mouse click
/// </summary>
private void LaunchOnMouseClick()
{
    if (Input.GetMouseButtonDown(0) == true)
    {
        _inMotion = true;
        this.GetComponent<Rigidbody2D>().velocity = new Vector2(2f, 10f);
    }
}

void OnCollisionEnter2D(Collision2D collision)
{
    // Ball does not trigger sound when brick is destoyed.
    // Not 100% sure why, possibly because brick isn't there.
    Vector2 tweak = new Vector2(Random.Range(0f, 0.5f), Random.Range(0f, 0.5f));

    if (_inMotion)
    {
        GetComponent<AudioSource>().Play();
        GetComponent<Rigidbody2D>().velocity += tweak;
    }
}

/// <summary>
/// Resets the Ball's position after exiting the PlaySpace
/// </summary>
public void ResetLaunchPosition()
{
    _inMotion = false;
}
}

GameManager:

using UnityEngine;

public class GameManager : MonoBehaviour
{

// GameManager instance
private GameManager _gameManager = null;

// Player
private int _playerDefaultLives = 5;        // Hard coded value
private int _playerLivesRemaining = -1;


/// <summary>
/// Returns the number of lives remaining for the player
/// </summary>
public int PlayerLivesRemaining
{
    get { return _playerLivesRemaining; }
}


/// <summary>
/// Pre-Initialisation
/// </summary>
private void Awake()
{
    // singleton pattern for GameManager
    if (_gameManager != null)
    {
        Destroy(gameObject);
    }
    else
    {
        _gameManager = this;

        GameObject.DontDestroyOnLoad(gameObject);
    }
}

/// <summary>
/// Initialises a new game by setting the player lives to the default value (3)
/// </summary>
public void NewGame()
{
    _playerLivesRemaining = _playerDefaultLives;
}

/// <summary>
/// Decreases the player's lives by one and checks for a game over state.
/// </summary>
public void PlayerLosesLife()
{
    LevelManager levelManager = GameObject.FindObjectOfType<LevelManager>();

    --_playerLivesRemaining;

    if (_playerLivesRemaining <= -1)
    {
        GameOver();
    }
    else
    {
        levelManager.UpdateUIPlayerLivesRemaining();
        levelManager.ResetBall();
    }
}

/// <summary>
/// Loads the "Loose" scene ;)   and destroys the GameManager
/// </summary>
private void GameOver()
{
    LevelManager levelManager = GameObject.FindObjectOfType<LevelManager>();

    levelManager.LoadLevel("Lose");

    Destroy(this);
}
}

Brick:

using UnityEngine;
using System.Collections;

public class Brick : MonoBehaviour {

public AudioClip crack;
public Sprite[] hitSprites;
public static int breakableCount = 0;
public GameObject smoke;

private int timesHit;
private LevelManager levelManager;
private bool isBreakable;

// Use this for initialization
void Start () {
	isBreakable = (this.tag == "Breakable");
	// Keep track of breakable bricks
	if (isBreakable) {
		breakableCount++;
	}
	
	timesHit = 0;
	levelManager = GameObject.FindObjectOfType<LevelManager>();
}

// Update is called once per frame
void Update () {

}

void OnCollisionEnter2D (Collision2D col) {
	AudioSource.PlayClipAtPoint (crack, transform.position, 0.8f);
	if (isBreakable) {
		HandleHits();
	}
}

void HandleHits () {
	timesHit++;
	int maxHits = hitSprites.Length + 1;
	if (timesHit >= maxHits) {
		breakableCount--;
		levelManager.BrickDestoyed();
		PuffSmoke();
		Destroy(gameObject);
	} else {
		LoadSprites();
	}
}

void PuffSmoke () {
	GameObject smokePuff = Instantiate (smoke, transform.position, Quaternion.identity) as GameObject;
	smokePuff.GetComponent<ParticleSystem>().startColor = gameObject.GetComponent<SpriteRenderer>().color;
}

void LoadSprites () {
	int spriteIndex = timesHit - 1;
	
	if (hitSprites[spriteIndex] != null) {
		this.GetComponent<SpriteRenderer>().sprite = hitSprites[spriteIndex];
	} else {
		Debug.LogError ("Brick sprite missing");
	}
}

// TODO Remove this method once we can actually win!
void SimulateWin () {
	levelManager.LoadNextLevel();
}
    }

LevelManager:

using UnityEngine;
using UnityEngine.UI;
using System.Linq;

 public class LevelManager : MonoBehaviour
{

/// <summary>
/// Initialisation
/// </summary>
private void Start()
{
    if (GameObject.Find("Canvas"))          // This is a bit messy.  It checks to see if there is a canvas, if so, updates the remaining lives
    {                                       // which prevents a null reference exception error occuring on non-playable levels (Win, Loose, Start)
        UpdateUIPlayerLivesRemaining();
    }
}

public void LoadLevel(string name)
{
    Debug.Log("New Level load: " + name);
    Brick.breakableCount = 0;
    Application.LoadLevel(name);
}

public void QuitRequest()
{
    Debug.Log("Quit requested");
    Application.Quit();
}

public void LoadNextLevel()
{
    Brick.breakableCount = 0;
    Application.LoadLevel(Application.loadedLevel + 1);
}

public void BrickDestoyed()
{
    if (Brick.breakableCount <= 0)
    {
        LoadNextLevel();
    }
}

/// <summary>
/// Updates the player lives UI.Text object with the player's remaining lives
/// </summary>
public void UpdateUIPlayerLivesRemaining()
{
    Text uiDisplayPlayerLives = GetTextObjectByName("PlayerLives");
    GameManager gameManager = GameObject.FindObjectOfType<GameManager>();

    uiDisplayPlayerLives.text = "lives x " + gameManager.PlayerLivesRemaining.ToString();
}
/// <summary>
/// Resets the location of the Ball to the Paddle
/// </summary>
public void ResetBall()
{
    Ball ball = GameObject.FindObjectOfType<Ball>();
    ball.ResetLaunchPosition();
}
// Helper method which could be moved to a separate class later

/// <summary>
/// Returns a UI.Text object for the specified name
/// </summary>
/// <param name="name">The name of the UI.Text object to return</param>
/// <returns></returns>
private Text GetTextObjectByName(string name)
{
    var canvas = GameObject.Find("Canvas");         // Hard coded, assumes the canvas will always be called "Canvas"
    var texts = canvas.GetComponentsInChildren<Text>();
    return texts.FirstOrDefault(textObject => textObject.name == name);
}
}

Paddle:

using UnityEngine;
using System.Collections;

public class Paddle : MonoBehaviour {

public bool autoPlay = false;
public float minX, maxX;

private Ball ball;

void Start () {
	ball = GameObject.FindObjectOfType<Ball>();
}
	
// Update is called once per frame
void Update () {
	if (!autoPlay) {
		MoveWithMouse();
	} else {
		AutoPlay();
	}
}

void AutoPlay() {
	Vector3 paddlePos = new Vector3 (0.5f, this.transform.position.y, 0f);
	Vector3 ballPos = ball.transform.position;
	paddlePos.x = Mathf.Clamp(ballPos.x, minX, maxX);
	this.transform.position = paddlePos;
}

void MoveWithMouse () {
	Vector3 paddlePos = new Vector3 (0.5f, this.transform.position.y, 0f);
	float mousePosInBlocks = Input.mousePosition.x / Screen.width * 16;
	paddlePos.x = Mathf.Clamp(mousePosInBlocks, minX, maxX);
	this.transform.position = paddlePos;
}
 }

LoseCollider:

using UnityEngine;
using System.Collections;

public class LoseCollider : MonoBehaviour
{

private GameManager _gameManager;

/// <summary>
/// Pre-Initialisation
/// </summary>
private void Awake()
{
    _gameManager = GameObject.FindObjectOfType<GameManager>();
}

/// <summary>
/// Handles the OnTriggerEnter2D event triggered by another object entering a trigger collider attached to the LoseCollider
/// </summary>
/// <param name="collider">The other Collider2D involved in the collision</param>
private void OnTriggerEnter2D(Collider2D collider)
{
    _gameManager.PlayerLosesLife();
}
}

I have a smoke destroyer script for getting rid of the partical effects, however I don’t have it yet, but I’ll look it up. I’ll go with my origonal plan and when I publish the full game on itch.io I’ll include the entire code for any aspiring developer out there :slight_smile:

1 Like

Looking forward to the link to Itch.io and playing the game, and glad you’ve got it all back together…

Did you look into the source control yet?

For the moment I’ve been saving most of my files on Googledrive. I’ve not yet done that with the Unity stuff, but I might try that later to make sure I don’t lose everything again.

Been busy with other stuff, but I’m getting back into the swing of game design which is always a good thing and then I’ll be taking another crack at that idea I had in the still-open topic about reversing the paddle. The new paddle I have is from the lecture though so the wings might make the job easier.

1 Like

It would most likely be worth the investment of your time, plus you’d have the ability to roll back to specific versions of your code… so if you try something new, and it doesn’t work, getting back to where you were is fairly straight forward.

As always, just post if there’s anything anyone may be able to help with :slight_smile:

Aron and Rob–thank you for this discussion. Got lives tracker added to my game based on this. I had the right ideas but missed some key steps and couldn’t get the syntax correct in a few spots.

I now have lives counting–only issue is the win screen > start screen is not resetting the lives counter to default. Do we need to be sure to destroy gameManager instance if the “next level” is Win?

1 Like

Hello Jason,

I would opt for a method to simply reset the various attributes which need to be reset upon restarting the game personally. So perhaps, lives, score, health and so on where applicable.

Depending on your current approach may determine how/where you do this. You could for example call a method from the start scene via GameManager which resets these, thus, everytime you are on the start scene, they will be reset. At the win scene, if the play clicks Play Again you simply take them back to the start scene and these values are then reset. The downside with that approach is you are effectively doing this;

  • “Would you like to play again”
  • “Yes”
  • “Click Start to Play”
  • “Click”

If the player has indicated they would like to play again, then you should just take them to the first scene of the game which they can play.

In this case, you may want to have a method which handles the play again functionality within GameManager which is called from the win scene, e.g. the player’s click on the play again button triggers the method to be called which resets/restarts the game, clearing the scores, resetting the lives and then launching the first playable scene.

As I say, depending on your approach will invariably determine the way you tackle this.

Rob,

I have to say I feel I have got a great value out of this course and folks like you are a big reason for that. Expectation was some videos that may or may not explain things very well. Well the videos are actually quite good and then we have folks like you with such thoughtful and quick responses.

Big props to Aron as well for sharing his problem and learning so everyone gains from it.

I used the solution you and Aron worked out to get lives working in my game. My win screen only (currently) has a “return to start”. When you go: Win > Start > Newgame the lives persist from where you won the game. If you lose then start a new game it properly initializes from NewGame() method in GameManager. Likewise–starting a game from Start works, just not after winning.

I may be missing something but I would think that the new game should work…only difference I see is that when we load the Lose screen we run GameOver() which has “Delete(this);”

I have tried adding a GameWin() method and added it to the button before it loads Start. In that method I put Delete(this) but still no luck. I will post here if I figure it out–let me know if I am way off track. :slight_smile:

Hi Jason,

Thank you for your kind words, they are appreciated :slight_smile:

My apologies, I am on my mobile at the moment which is less than ideal for scrolling through the past posts with the code etc.

I would be happy to take a look at this but it would probably be easier if you could zip up your project files and pop a link up to perhaps Dropbox or Google Drive where I can grab them from.

I will be traveling for a couple of hours this evening, so if I can’t look tonight I will look first thing in the morning.

Best guess based on what you have described is that perhaps under a lose condition the game manager is destroyed and is then being recreated, this resetting the correct number of lives. But, under a win condition this isn’t perhaps happening and the values are being persisted because the object still exists and hasn’t been destroyed.

As I say, happy to take a look but would be easier with the full project files so I can see it all in context.

Thanks again for your kind words Jason.

Privacy & Terms