Add more stars after a game finish

HI Francesco,

I saw that status thing in your code when I had your project to hand, I had assumed this was something you had added. If it is, what are you using it for?

I don’t see a lot of value in a return type of bool on a method that simply adds/subtracts - if it doesn’t work for some reason, you’d get an error, and resolve it. What do you do with these success/failure values that you are returning?

With that if statement we see if the count value is greater or equal to the value that we are subtracting. If it’s greater or equal we say that the subtraction isn’t giving us a negative amount so the Status of that operation should be successful, It should work like a bool. It’s not a code that I wrote but it’s a part of the course but I don’t remember why we choose the status instead of the bool. Tomorrow I’ll search for that lesson on Udemy

Ahh, ok, yes I see what you’re doing there now, I didn’t realise which method that code example above was from, sorry.

So, this is effectively controlling whether the player can make a purchase or not yes?

With the changes I’ve given you above, that would be outside of the responsibility of the Stars class, or at least, outside of the responsibility of those two methods, they should just be responsible for incrementing or decrementing, any logic for handing whether that can happen should be else where.

So what we’re talking about here now is a check to see whether the player has enough stars to make a purchase, rather than trying to respond to a purchase after its been made to see whether it could have happened, why not prevent it from happening in the first instance?

For example, you know how many stars the player has, so why not deactivate the buttons for the defenders they cannot afford? This would put the responsibility on each button to check whether it should be active or not, they could access the stars class and use the Count property to see what the player has.

These buttons could then also subscribe to the Star OnCountChanged event, so that they can activate themselves if the player gains enough stars as they play. You wouldn’t then need to have the boolean check within this method.

Make sense?

If you didn’t want to do that, then I would suggest having a separate method within the Stars class for handling purchases, this would take a value in for the intended purchase and then do the evaluation you had earlier to see if the purchase could be successful or not, returning true / false accordingly. If it could be successful, you could then implement the Decrement method to reduce the count of stars.

To me, that all seems a bit unnecessary.

I don’t know what I’ve done but I’ve made a huge mistake


I think that I’m not good enough for make a script that set the defenders button active or not.
I added your MethodThatCounts under the button script

private void MethodThatCares(int count)
    {
        Debug.Log("Stars : " + count);
        if (defCost <= count)
        {
            this.gameObject.SetActive(true);
            activeSprite.GetComponent<SpriteRenderer>().color = Color.black;
            
        }
        else{
            this.gameObject.SetActive(false);
            selectedDefender = defenderPrefab;
        print(selectedDefender);
        }
    }

I’ve tried by using the old spawn script and I found an error in the stardisplay.cs

Script error (StarsDisplay): Update() can not take parameters.

this one:

private void Update(int count)
    {
        _stars.text = count.ToString();
    }

I’ve tried to change the method name but then it won’t update every frame.
Maybe I should think harder and read all the unity documentation

HI Francesco,

Don’t worry - as mentioned above, in order to make take a few steps forward, sometimes you have to take a couple backwards as well. Nothing is beyond fixing :slight_smile:

When I wrote the above I did wonder about the Update method example I gave you, originally I wrote it using the name Refresh, but then when I said to myself “Star Display Refresh” that didn’t make as much sense in my head as “Star Display Update”. I went with the overloaded Update method and I did quickly check that in Visual Studio to see whether it would give any warnings. The issue is that there is a method of the same name, as you know, which comes from the MonoBehaviour class which you are inheriting from. I had hoped that, with it having a different signature, e.g. taking an int as a parameter, Unity would realise it was the Update method which it calls every frame. We don’t actually want our method to be called every frame, just as and when the event is triggered. Clearly that didn’t work which was my fault. I can only assume in the background Unity perhaps uses some form of reflection and then calls all the methods which are named Update.

Could you zip up the project files and share them with me again, with the game broken as it stands at this point and I will happily take a look, as I led you down this path that only seems fair :slight_smile:

In your example above by the way, the MethodThatCares (that was just a made up/example name btw, could have been called something more relevant), I note that if the cost of the defender is less that the count of stars you set the sprite renderer color to black? Its been a while since I looked at Glitch Garden, but is that what you actually want? I assumed it would only be black if the player couldn’t afford it?

With regards to making the buttons active/deactive, instead of using SetActive(true) or SetActive(false), which will disable the GameObject in the scene, you could try getting a reference to the Button component and then setting its IsInteractable property to true / false etc. That way, the GameObject is still there, it still has a button, it just doesn’t respond when the user clicks on it. I didn’t give you any hints with that one, so well done for giving it a go.

sorry if I didn’t answer quickly but I was busy yesterday. Anyway this is the glitch garden rar folder
https://drive.google.com/open?id=1oiBony4uvMBISHQFnVbvs-t0QkqNyo7M

you could try getting a reference to the Button component and then setting its IsInteractable property to true / false

so I should change from set active to is interactable.

The core game is a bit too big (9 and a half squares) but that’s ok, I’ll change it later. Another question, can we make the camera and the core game DontDestroyOnLoad() so I don’t have to past the Game camera in every level if I change the canvas (prefab). Maybe with the Game session script i can do a +1 instead of + 20 and use the script for the level number private void OnLevelChange() { _levelNumber += _levelChangeNumberIncrement; }

Hi Francesco,

Absolutely, just be aware that you will need to include a way of not persisting these scenes as well, for example, when the player loses, or wins, at that points you probably want to let them be destroyed and then after the player returns to the main menu they’ll launch them all over again.

Potentially yes, or some form of multiplier, just depends how often you want to provide the player with this bonus, and by how much at each stage.

Let’s clear up the issues we have currently so that you can move forward again.


StarDisplay.cs

  • rename the Update method to Refresh
  • replace Update with Refresh in both the OnEnable and OnDisable methods

Button.cs

I had forgotten that these are not actually Buttons in the sense of Unity, but ones that are created in this project. As such, what I said previously about setting the IsInteractable property is not available because we do not have buttons.

In order to create the ability to enable/disable the buttons you have based on the amount of available stars we will have to take a bit of a hit on the selected defender affordance, only in the short-term, it is something that you’ll want to have I’m sure. But we can’t set all of the buttons to black, apart from the selected one, if the selected one is too expensive. Effectively we are trying to use the same approach for two different things, which isn’t going to work.

What we could do is perhaps change the Buttons background so instead of it being one long block for all of the buttons to sit on top of, we have individual backgrounds for each button, then, when one is selected, we could highlight the background. That would provide the selected defender affordance again whilst still providing you with the ability to enable/disable defenders based on their costs and the player’s available stars. Later, you might like to change this to perhaps put a frame around the background instead, that might look nicer. At this time, I’m going to just drop that bit of functionality, to get this to work.

Quite a few steps for the Buttons.cs script, so instead of listing them all, here’s a copy of the script;

using UnityEngine.UI;
using UnityEngine;

public class Button : MonoBehaviour
{
    public GameObject defenderPrefab;
    public static GameObject selectedDefender;
    private Text costText;
    private int defenderCost;


    void Start()
    {
        costText = GetComponentInChildren<Text>();
        costText.text = defenderPrefab.GetComponent<Defenders>().starCost.ToString();
        defenderCost = defenderPrefab.GetComponent<Defenders>().starCost;
    }

    private void OnEnable()
    {
        Stars.OnCountChanged += DisplayIfAffordable;    // subscribe
    }

    private void OnDisable()
    {
        Stars.OnCountChanged -= DisplayIfAffordable;    // unsubscribe
    }

    // the following method will be called whenever the value of `_count` within the _Stars_ class 
    // is changed (using either of the `Increment` or `Decrement` methods)
    private void DisplayIfAffordable(int count)
    {
        if (defenderCost <= count)
        {
            gameObject.GetComponent<SpriteRenderer>().color = Color.white;
            GetComponent<BoxCollider2D>().enabled = true;
        }
        else
        {
            gameObject.GetComponent<SpriteRenderer>().color = Color.black;
            GetComponent<BoxCollider2D>().enabled = false;
        }
    }

    private void OnMouseDown()
    {
        selectedDefender = defenderPrefab;
    }
}

As we couldn’t use the IsInteractable I get a reference to the BoxCollider2D component in the above, which is what detects the mouse clicks, and then enable/disable it based on whether the defender is affordable or not. The colour of the sprite is also set.


DefenderSpawner.cs

This class is currently responsible for putting the defenders into the play space, the OnMouseDown method needs to check that the selected defender is affordable, and if so, place it in the play space. Again, a few changes to this script, so it’s listed below;

using UnityEngine;

public class DefenderSpawner : MonoBehaviour
{
    public Camera myCamera;
    private GameObject parent;
    private Stars _stars;
    private StarsDisplay starsDisplay;


    void Start()
    {
        parent = GameObject.Find("Defenders");
        _stars = GameSession.Instance.GetComponent<Stars>();
        starsDisplay = GameObject.FindObjectOfType<StarsDisplay>();

        if (!parent)
        {
            parent = new GameObject("Defenders");
        }
    }

    private void OnMouseDown()
    {
        GameObject defender = Button.selectedDefender;
        int defenderCost = defender.GetComponent<Defenders>().starCost;

        if (defenderCost <= _stars.Count)
        {
            _stars.Decrement(defenderCost);

            Vector2 rawpos = CalculateWorldPointOfMouseClick();
            Vector2 roundedPos = SnapOnGrid(rawpos);
            Quaternion zeroRot = Quaternion.identity;
            GameObject newDef = Instantiate(defender, roundedPos, zeroRot) as GameObject;
            newDef.transform.parent = parent.transform;
            Debug.Log("hai i sordi!!");
        }
    }

    Vector2 SnapOnGrid(Vector2 rawWorldPos)
    {
        float newX = Mathf.RoundToInt(rawWorldPos.x);
        float newY = Mathf.RoundToInt(rawWorldPos.y);
        return new Vector2(newX, newY);
    }

    Vector2 CalculateWorldPointOfMouseClick()
    {
        float mouseX = Input.mousePosition.x;
        float mouseY = Input.mousePosition.y;
        float distanceFromCamera = 10f;
        Vector3 weirdTriplet = new Vector3(mouseX, mouseY, distanceFromCamera);
        Vector2 worldPos = myCamera.ScreenToWorldPoint(weirdTriplet);
        return worldPos;
    }
}

There’s a lot of room for refactoring in the above script, specifically in the OnMouseDown method, but for now, it works.


GameSession.cs

using UnityEngine;
using UnityEngine.SceneManagement;

[RequireComponent(typeof(Stars))]
public class GameSession : MonoBehaviour
{
    [Header("Stars")]
    [SerializeField]
    private int _initialStars = 150;

    [SerializeField]
    private int _levelChangeStarsIncrement = 20;

    private static GameSession instance;
    private Stars _stars;
    public enum Status { SUCCESS, FAILURE };

    private void Start()
    {
        _stars = GetComponent<Stars>();

        _stars.Increment(_initialStars);
    }

    public static GameSession Instance
    {
        // Here we use the ?? operator, to return 'instance' if 'instance' does not equal null
        // otherwise we assign instance to a new component and return that
        get { return instance ?? (instance = new GameObject("Singleton").AddComponent<GameSession>()); }
    }

    private void OnDisable()
    {
        SceneManager.sceneLoaded -= OnLevelChange;
    }

    private void OnEnable()
    {
        SceneManager.sceneLoaded += OnLevelChange;
    }

    private void OnLevelChange(Scene scene, LoadSceneMode loadSceneMode)
    {
        DontDestroyOnLoad(gameObject);
        _stars.Increment(_levelChangeStarsIncrement);
    }
}

The GameSession gets a reference to Stars and then sets the initial value, should the level change, this value should increment.


Finally;

  • add a GameObject to the scene (I dropped it into level 1) and name it GameSession
  • add the GameSession.cs script as a script component
    it should automatically add a Stars.cs script component when added
  • make sure the Initial Stars field is set to 150 in the Inspector
  • I set the text value of the Stars GameObject (the child of the StarDisplay GameObject) to “xxx”;
    image
    this helped make it apparent if it was getting updated by the code or not, rather than having “150” set as the text which is what we are hoping it would be set to
  • I also removed the GameObject named “OFF” from both level 01 and level 02.

Note, the game doesn’t seem to transition from level to level? Were you aware of this?

Implement the change above and you should be back on track. You will not be able to buy defenders if you cannot afford then, the defender sprites will be disabled when not affordable. Current issues are the loss of selected defender affordance and the next scene not loading. I would have carried on but figured you should do some too :wink:

Privacy & Terms