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”;
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