Ball Store

In my version of this game i am trying to create a ball store were people can buy different colors and types of balls using an in game currency (different sprites) however I am having trouble figuring out how to load that specific ball to the scene and destroy the old ball. Can someone please help?

Hi Fadi,

Do you want to actually destroy the ball in the scene, or simply swap the sprite? If it’s just a case of swapping the sprite you could access the ball’s SpriteRenderer and set the new sprite that way.

Regarding the balls/shop, do they just look different at the moment, or do they have unique functionality?

The reason I ask is that if you are going to introduce new functionality then you may want to consider having a ball class which you inherit from for each ball type, that way the core functionality can be in the base class and available to all of the different types, but without duplicating. Just a thought.

Hi Rob,

Thanks for your reply. I don’t know which is best, rendering a new sprite or a game object, but I am trying the latter, though unity keeps quitting when testing. Here’s the script. (The void awake is where the game object is spawned). Am I doing it correctly?

using System.Collections;
public class Ball : MonoBehaviour {
	public LevelManager levelmanager;
	public Paddle paddle;

	private bool hasStarted = false;

	private Vector3 paddleToBallVector;
	public GameObject prefab;
 
private void Awake () {
    GameObject InstanceOfPrefab = Instantiate (prefab);
}
 
	void Start ()
	{

		paddleToBallVector = this.transform.position - paddle.transform.position;

	}
	// Update is called once per frame
	void Update () {
		if (!hasStarted) {
			this.transform.position = paddle.transform.position + paddleToBallVector;

		if (Input.GetMouseButtonDown(0)) {
			hasStarted = true;
			print ("Mouse Clicked, ball launched");
			this.GetComponent<Rigidbody2D>().velocity = new Vector2(2f, 10f);
			}
		}	
	}

     }

Do you receive any error messages?

Just a warning, but unity crashes every time

Hi Fadi,

I believe the reason Unity is crashing is because you have created a cyclic dependency.

If you consider what you have in that script in steps…

  • the Ball.cs script is attached to the Ball prefab.
  • the Awake method is called when the Ball is initialised
  • within your Awake method you then initialise another Ball
  • this has a Ball.cs script
  • the Awake method is called…
  • repeat…

Unity crashes when you’re out of memory, or heading that direction.

I think there are two approaches you could take, the first would be the quickest but would effectively only change the sprite, not any additional behaviour, the second would change the Ball object entirely.

For the quick approach;

  • create a public variable which will allow you to refefence a sprite
  • in your Start method, set the sprite property of the Sprite Renderer component

You would want to have a sprite which was just the default ball obviously as well as any in your shop, you would then use the same approach for all sprites, e.g. no different behaviour for the initial ball sprite.

The second approach, where-by you would be creating a specific GameObject, you would need to change your Hierarchy/objects a little.

Currently, the player is the ball. What I would suggest is instead creating a separate Player GameObject.

You could then add a Player.cs script, and instantiate the default ball prefab as a child object of the Player GameObject. As the Player GameObject would not be being passed in via this reference, you would avoid the cyclic dependency situation you have above.

You would want to keep all of the existing ball code within the Ball.cs script, all of the basic functionality.

Hope this helps :slight_smile:


See also;

Thanks Rob, I did something similar, where a public bool will activate a spawn controller script which will instantiate a game object, however, the bool changes whenever I load a different scene (It is a public bool because I need to access it in the spawn script). How can I fix this?

Hi,

If you set the bool to be static the value will persist, but not if the object it is attached to is destroyed, which, on each scene change your ball would be. If you have a persistent GameManager or similar object in your game, you could apply this variable to that instead, it would be accessible to your other object/script and as long as the GameManager is using the DontDestroyOnLoad you should be ok.


See also;

Hi Rob, Sorry for the late reply, but thank you for helping me. I have updated my app, but I noticed a terrible bug, whenever I close the application and restart it, the in game progress resets ( I also have the in game currency saved in the playerprefs, but that doesn’t seem to be the problem), can you please help?

Hi Fadi,

Are you storing the in game progress anywhere?

If it counts, in the Playerprefs.
the line goes as follows

coins = PlayerPrefs.GetInt("coins", coins+25);

I also have a playerprefs.save();

void Update ()
	{
	PlayerPrefs.Save();
	}

Hi Fadi,

Those code examples are somewhat abstract.

The top line is returning any stored value for the key “coins”, you are then overriding the default returned value of zero, with coins + 25.

This seems somewhat odd. If you did have a stored value, and assuming coins was initially zero at this point in the code, it would now be set to whatever comes out of the settings. If there is a stored value, then you are returning zero + 25.

With regards to the PlayerPrefs.Save statement, why are you calling this every frame?

Do you have any code resembling this;

PlayerPrefs.SetInt("coins", coins)

This would be required to set the key and value before calling Save.

Hi Rob, thanks for the reply. the coins integer and about 10 others starting with boughtball is referenced in the beginning as public static variables to be accessed by other scripts. here is the full script (and the void update is just in case the player closes the app)

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;

public class LevelManager : MonoBehaviour {
	public Text text;

	public static int coins;
	public static int redcost = 25;
	public static int greencost = 50;
	public static int yellowcost = 75;
	public static int tenniscost = 100;
	public static int ball7p = 150;
	public static int ball10p = 200;
	public static int afball = 275;
	public static int basketcost = 325;
	public static int socerost = 400;
	public static int discost = 500;
	public static bool boughtRedBall = false;
	public static bool boughtGreenBall = false;
	public static bool boughtYellowBall = false;
	public static bool boughtTennisBall = false;
	public static bool boughtball7pball = false;
	public static bool boughtball10pball = false;
	public static bool boughtafball = false;
	public static bool boughtbasketball = false;
	public static bool boughtsoccerball = false;
	public static bool boughtdiscoball = false;
	void Start ()
	{
	PlayerPrefs.SetInt("coins", coins);
	}
	public void LoadLevel (string name)
	{
		Debug.Log ("New Level load: " + name);
		SceneManager.LoadScene (name);

		}
	
	public void adtext() {
	text.text = "Watch an ad for 25 coins!";
	}
	public void QuitRequest(){
		Debug.Log ("Quit requested");
		Application.Quit ();
	}

	public  void adcoins ()
	{
	coins = PlayerPrefs.GetInt("coins", coins+25);
	}
	public void AddCurrency0 (){
	coins = PlayerPrefs.GetInt("coins", coins+5);
	}
	public void AddCurrency ()
 {
     coins = PlayerPrefs.GetInt("coins", coins+10);
	}


	public void AddCurrency2 ()
 {
     coins = PlayerPrefs.GetInt("coins", coins+20);
	}


	public void BuyRedBall ()
	{
		if (coins >= 25 && redcost == 25) {
			PlayerPrefs.GetInt("coins", coins -= 25);
			PlayerPrefs.GetInt("redcost",redcost -= 25);
			boughtRedBall = true;
			boughtYellowBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughtred");
		}
		if (redcost == 0) {
		boughtRedBall = true;
			boughtYellowBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		Debug.Log("setred");
		}
		if (coins <= 50) {
			Debug.Log("Not enough money");
			}
 	}
	public void BuyGreenBall ()
	{
		if (coins >= 50 && greencost == 50) {
			PlayerPrefs.GetInt("coins", coins -= 50);
			PlayerPrefs.GetInt("greencost", greencost -= 50);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = true;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughtgreen");
		}
		if (greencost == 0){
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = true;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		Debug.Log("setgreen");
		}
		if (coins <= 50) {
			Debug.Log("Not enough money");
}
 	}
	public void BuyYellowBall ()
	{
		if (coins >= 75 && yellowcost == 75) {
			PlayerPrefs.GetInt("coins", coins -= 75);
			PlayerPrefs.GetInt("yellowcost", yellowcost -= 75);
			boughtYellowBall = true;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughtyellow");
		}
		if (yellowcost == 0) {
			boughtYellowBall = true;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		}
 	}
	public void BuyTennisBall ()
	{
		if (coins >= 100 && tenniscost == 100) {
			PlayerPrefs.GetInt("coins", coins -= 100);
			PlayerPrefs.GetInt("yellowcost", tenniscost -= 100);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = true;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughtyellow");
		}
		if (tenniscost == 0) {
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = true;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		}
		if (coins <= 100) {
			Debug.Log("Not enough money");

		}
 	}
	public void Buy7ballpBall ()
	{
		if (coins >= 150 && ball7p == 150) {
			PlayerPrefs.GetInt("coins", coins -= 150);
			PlayerPrefs.GetInt("ball7p", ball7p -= 150);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball7pball = true;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughtyellow");
		}
		if (ball7p == 0) {
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = true;
			boughtsoccerball = false;
			boughtdiscoball = false;
		}
		if (coins <= 150) {
			Debug.Log("Not enough money");

		}
 	}
	public void Buy10ballpBall ()
	{
		if (coins >= 200 && ball10p == 200) {
			PlayerPrefs.GetInt("coins", coins -= 200);
			PlayerPrefs.GetInt("ball10p", ball10p -= 200);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = true;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughtyellow");
		}
		if (ball10p == 0) {
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = true;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		}
		if (coins <= 300) {
			Debug.Log("Not enough money");

		}
 	}
	public void Buyafball ()
	{
		if (coins >= 275 && afball == 275) {
			PlayerPrefs.GetInt("coins", coins -= 275);
			PlayerPrefs.GetInt("afball", afball -= 275);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = true;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughta");
		}
		if (afball == 0) {
		boughtafball = true;
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		}
		if (coins <= 275) {
			Debug.Log("Not enough money");

		}
 	}
	public void Buybasketball ()
	{
		if (coins >= 325 && basketcost == 325) {
			PlayerPrefs.GetInt("coins", coins -= 325);
			PlayerPrefs.GetInt("basketcost", basketcost -= 325);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = true;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
			Debug.Log("boughta");
		}
		if (basketcost == 0) {
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = true;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = false;
		}
		if (coins <= 200) {
			Debug.Log("Not enough money");

		}
 	}
	public void Buysoccerball ()
	{
		if (coins >= 400 && socerost == 400) {
			PlayerPrefs.GetInt("coins", coins -= 400);
			PlayerPrefs.GetInt("basketcost", socerost -= 400);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = true;
			boughtdiscoball = false;
			Debug.Log("boughta");
		}
		if (basketcost == 0) {
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = true;
			boughtdiscoball = false;
		}
		if (coins <= 400) {
			Debug.Log("Not enough money");

		}
 	}
	public void Buydiscoball ()
	{
		if (coins >= 500 && discost == 500) {
			PlayerPrefs.GetInt("coins", coins -= 500);
			PlayerPrefs.GetInt("discost", discost -= 500);
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = true;
			Debug.Log("boughta");
		}
		if (discost == 0) {
			boughtYellowBall = false;
			boughtRedBall = false;
			boughtGreenBall = false;
			boughtTennisBall = false;
			boughtball10pball = false;
			boughtafball = false;
			boughtbasketball = false;
			boughtball7pball = false;
			boughtsoccerball = false;
			boughtdiscoball = true;
		}
		if (coins <= 500) {
			Debug.Log("Not enough money");

		}
 	}
 	void Update ()
	{
	PlayerPrefs.Save();
	}
}

Also this is not what it was in the build, I just thought this could work but haven’t updated it yet. Could it work?
The difference is that the old one didn’t have the void start

Hi Fadi,

The Save method call being in the Update is not a good thing. If you consider what this method actually does, on a PC/Mac, it’s writing to the disc, in many cases this will be a mechanic disk. Having that happen every frame, that is a huge overhead and potentially for no gain. If the player hasn’t actually collected/gained any additional coins you are making thousands of hits to the disc for nothing.

If you read the documentation for PlayerPrefs, specifically the Save method, you’ll note that it is called internally upon OnApplicationQuit, so the main reason for your desire to do this is already covered.

With regards to the various AddCurrency methods, I would probably separate this out a bit. From what I can see you effectively have the same thing going on in all of those methods, but with varying quantities, so why not just pass that in as a paramenter to one method?

The PlayerPrefs.GetInt and PlayerPrefs.SetInt methods could equally be abstracted.

In your Buy<colour>Ball methods, you are setting every other ball type to be false. Is the player actually buying a ball type which they are then free to select to use in the game, or, are you only allowing them to have one ball and if they want others, even one they previously had, they have to buy it again (I personally wouldn’t like that).

If the former, then you shouldn’t need to set the state for those other booleans, just the one that they are buying.

When you look at all of those methods for purchasing, there is so much duplication going on, it could really be a lot more simplified if you refactor it. For example, you are testing to see if the player has enough coins in each method, I’m not sure why, but you are also testing for the cost of the specific ball also, despite having these set as variables at the top of your script, you have hard coded values again in these methods.

The following is not particularly thought out, and will most likely not map to your project at all, but when you consider the act of making a transaction, a method for that may look like this;

private bool MakePurchase(PurchasableItem item)
{
    bool result;

    if(item.cost > player.Wallet.Funds)
    {
        // decline purchase, not enough cash
        result = false;
    }
    else
    {
        // has enough cash, make purchase
        player.Wallet.Deduct(item.Cost);
        player.Items.Add(item);

        result = true;
    }

    return result;
}

The above would potentially cover any purchase, in its most simple form. You could use a List to store the items the player has purchased, which could then be re-used in order to perhaps allow them to choose from a menu of ball types that they would like to use.

The Deduct method of Wallet above would perhaps call the PlayerPrefs.SetInt. With regards to when you called PlayerPrefs.Save, I would add this for the specific actions/events that a player can perform, so, if we think about what you player can do;

  • buy currency
  • spend currency

So, once they have made a successful transaction to buy currency, use SetInt for the currency and Save. When they spend currency on an item, SetInt for the currency, and then perhaps SetInt against the key for the purchasable item, or, maybe just add a “key” for that item (and use “HasKey” when you want to see if they have bought it), and Save.

You will reduce the number of calls to PlayerPrefs significantly and improve the performance of your game.


See also;

Hi Rob, Thanks for the advice on the player prefs. as for the bough ball methods, there is also a set function were all the static integers (other than the coins) like the redcost, they give the functionality for the player to set the ball if he bought another one and not to buy it again. The game actually works fine and the set method is also functional, but its only that when the player exits the application and opens it back again where his progress resets to 0. can this be fixed?

Perhaps you should run this in debug mode Fadi, step through each call and see at which point you call PlayerPrefs.Save after setting the relevant keys/values in PlayerPrefs for the progress.

would that make a difference, because I already am living the progress every frame.

It would if you are not doing the above. Just calling PlayerPrefs.Save isn’t going to help if you are not using PlayerPrefs.SetInt or one of the other methods to actually set the value in PlayerPrefs, at the moment, from what I can see in your code, you are not doing this - so the values only exist whilst the game is running.

I still firmly believe that calling PlayerPrefs.Save within the Update method is a massive overkill.

From the Unity documentation;

"Writes all modified preferences to disk.

By default Unity writes preferences to disk during OnApplicationQuit(). In cases when the game crashes or otherwise prematuraly exits, you might want to write the PlayerPrefs at sensible ‘checkpoints’ in your game. This function will write to disk potentially causing a small hiccup, therefore it is not recommended to call during actual gameplay."

Update isn’t a sensible checkpoint.

Privacy & Terms