Chapter 6. Main Menu, iAds, Leaderboards, Store Purchases, and Achievements

In this chapter, we will go through the steps to get the final aspects of the iOS integration function and set up the main menu UI so that the player can navigate between playing the game, view leaderboards /achievements, and have the option to purchase "remove iAds" for the cost of ten thousand coins or 99 cents. We will also display iAds if the player has not purchased the remove iAds.

Similar to the last chapter, this one will have a fair bit of the tedious UI work. As it is difficult to explain the workflow, I will give you the exact locations and anchor points for the UI buttons and images. These locations are not definitive, meaning you can change them however you feel. Also, it won't affect the players experience with the UI because the logic for them is based in code. Feel free to take the liberty with the UI design if you are comfortable with it.

iAds, Leaderboards, and Achievements are all handled with the iOS plugin. Sadly, Unity has not integrated the native iOS functionality into the engine, and the iOS plugin is required to call the setting and get information for leaderboards and achievements and when and how to display the iAds. As I mentioned in the first chapter, I will use the plugin called IOS Native. It is on the Unity asset store for twenty dollars. If you do not want to spend that much money, there are some other options, although I have found that IOS Native is one of the better ones.

Tip

There are many alternatives to IOS Native. All of them have different methods to do the same thing at different costs. If for whatever reason you would like to use another or want to see the alternatives, you can use the Unity Asset Store to find them.

We will also need to expand our existing class in order to fill the gaps to save information regarding achievements, leaderboards, and store purchases. Apple also requires a restore purchase option.

In this chapter, we will cover the following topics:

  • Building the main menu user interface with Unity's UI tools, including buttons for achievements, leaderboards, and store purchases
  • Displaying iAds on the screen if the player has not purchased remove iAds
  • Writing code that hides and shows different aspects of the UI, such as the transition between the main menu and the game, any call needed for store purchases, or bringing up the iOS leaderboard and achievement scenes
  • Addition to LevelPieceManager so that the character continues to run on a flat ground until the player chooses to start playing
  • Addition to character and saving the system so that it can check whether achievements are completed

Building the main menu UI

The main menu UI will be its own Canvas GameObject. We will then handle the main menu and the game UI via the GameInfo class. We will also use the GameInfo class to manage button presses and the iOS integration.

In Hierarchy, right-click and select UI and then click on Canvas. Name this new Canvas GameObject MenuUI.

Let's start by adding five buttons to achievements, playing, leaderboards, remove iAds, and restore purchase.

Right-click on the new MenuUI GameObject, navigate to UI, and left-click on Button. Do this four more times, so there are a total of five buttons that are children of the Menu UI GameObject.

Name the buttons and text children as follows:

  • PlayButton, PlayText
  • LeaderboardButton, LeaderboardText
  • AchievementButton, AchievementText
  • RemoveAdsButton, RemoveAdsText
  • RestorePurchaseButton, RestorePurchaseText
Building the main menu UI

Adding button images

Next, we need to import the art that will be used for the main menu UI. In the Assets/UI folder, right-click and select Import New Asset…. Navigate to the Art folder for this book and search for the chapter labeled ChapterSix_MenuUI. Import all the images into this folder.

Select all the new images in the Assets/UI folder and change their settings as follows:

  • Filter Mode: Trilinear
  • Max Size: 256
  • Format: Truecolor

PlayButton

Select PlayButton in Hierarchy and search for Inspector. Change its settings as follows:

  • Anchor: bottom center
  • Pos X: 0
  • Pos Y: 115
  • Pos Z: 0
  • Width: 128
  • Height: 128
  • Source Image: MenuButton

Now, select PlayButtonText. In the Inspector window, change its settings as follows:

  • Text: Play
  • Font: Arial
  • Font Style: Bold
  • Font Size: 36
  • Alignment: Center

LeaderboardButton

Select LeaderboardButton in the Hierarchy tab and search for Inspector. Change its settings as follows:

  • Anchor: bottom center
  • Pos X: 135
  • Pos Y: 115
  • Pos Z: 0
  • Width: 128
  • Height: 128
  • Source Image: MenuButton

Select LeaderboardText. In the Inspector window, change its settings to:

  • Text: Leaderboards
  • Font: Arial
  • Font Style: Bold
  • Font Size: 17
  • Alignment: Center

AchievementButton

Select AchievementButton. In Hierarchy, search for Inspector. Change its settings as follows:

  • Anchor: bottom center
  • Pos X: -135
  • Pos Y: 115
  • Pos Z: 0
  • Width: 128
  • Height: 128
  • Source Image: MenuButton

Now, select AchievementText and then in Inspector, change its settings to:

  • Text: Achievements
  • Font: Arial
  • Font Style: Bold
  • Font Size: 17
  • Alignment: Center

RemoveAdsButton

Select RemoveAdsButton in the Hierarchy tab and navigate to Inspector. Change its settings as follows:

  • Anchor: bottom center
  • Pos X: -64
  • Pos Y: 55
  • Pos Z: 0
  • Width: 96
  • Height: 42
  • Source Image: RestartButton

Now, select RemoveAdsText and then in the Inspector window, change its settings as shown here:

  • Text: Remove iAds
  • Font: Arial
  • Font Style: Bold
  • Font Size: 12
  • Alignment: Center

RestorePurchaseButton

Let's select RestorePurchaseButton in the Hierarchy tab and search for Inspector. Change its settings as follows:

  • Anchor: bottom center
  • Pos X: 64
  • Pos Y: 55
  • Pos Z: 0
  • Width: 96
  • Height: 42
  • Source Image: RestartButton

Now, select RestorePurchaseText and then in the Inspector window, change its settings as follows:

  • Text: Restore Purchase
  • Font: Arial
  • Font Style: Bold
  • Font Size: 14
  • Alignment: Center

You should now have a button layout that looks similar to the following image:

RestorePurchaseButton

The Purchase Remove iAds panel

We need to create a few panels that will show when the player clicks on RemoveAdsButton and what selection they choose from here. The selection to purchase remove iAds with ten thousand coins will check whether the player has that many. If they don't, we will display an error panel that tells how many coins they have and that the purchase has failed. We also need a panel to show when the player has successfully purchased the remove iAds purchase.

  • Select the MenuUI GameObject from the Hierarchy tab and then right-click on it. Navigate to UI and select Panel. Name this RemoveAdsBackgroundScreen.
  • With RemoveAdsBackgroundScreen selected, in the Hierarchy tab, right-click on it and select UI and then Image. Name this image RemoveAdsScreen.
  • Still, with RemoveAdsBackgroundScreen selected, right-click on it, navigate to UI, and select Text. Name this text RemoveAdsTextOption.
  • Finally, again with RemoveAdsBackgroundScreen selected, right-click on it, navigate to UI, and select Button. Name this button ForCoin. Do this twice more. Name the next button ForCash and the last button CloseRemoveAds.
  • Name the child Text GameObject for ForCoin as ForCoinText.
  • Name the child Text GameObject for ForCash as ForCashText.
  • Delete the child Text GameObject for CloseRemoveAds.

RemoveAdsBackgroundScreen

Select RemoveAdsBackgroundScreen in the Hierarchy tab and navigate to the Inspector window. Change its settings as follows:

  • Anchor: Center
  • Pos X: 0
  • Pos Y: 0
  • Pos Z: 0
  • Width: 265
  • Height: 180
  • Source Image: Background

RemoveAdsScreen

Select RemoveAdsScreen in Hierarchy and search for Inspector. Change its settings as follows:

  • Anchor: Center
  • Pos X: 0
  • Pos Y: 0
  • Pos Z: 0
  • Width: 256
  • Height: 171
  • Source Image: None
  • Color: 32—red, 32—green, 32—blue, 255—alpha

RemoveAdsTextOption

  • Anchor: top center
  • Pos X: 0
  • Pos Y: -40
  • Pos Z: 0
  • Width: 155
  • Height: 64
  • Text: Purchase Remove iAds
  • Font: Arial
  • Font Style: Bold
  • Font Size: 24
  • Alignment: Center

ForCoin

  • Anchor: bottom left
  • Pos X: 68.5
  • Pos Y: 68.5
  • Pos Z: 0
  • Width: 128
  • Height: 128
  • Source Image: Pickup_Coin_0

ForCoinText

  • Anchor: stretch
  • Pos X: 6.1
  • Pos Y: 101.5
  • Pos Z: 0
  • Right: -6.1
  • Bottom: 7.5
  • Text: 10,000 Coins
  • Font: Arial
  • Font Style: Bold
  • Font Size: 14
  • Alignment: Center

ForCash

  • Anchor: bottom left
  • Pos X: -68.5
  • Pos Y: 68.5
  • Pos Z: 0
  • Width: 76
  • Height: 76
  • Source Image: CentSign

ForCoinText

  • Anchor: stretch
  • Pos X: 6.1
  • Pos Y: 101.5
  • Pos Z: 0
  • Right: -6.1
  • Bottom: 7.5
  • Text: 99 Cents
  • Font: Arial
  • Font Style: Bold
  • Font Size: 14
  • Alignment: Center

CloseRemoveAds

  • Anchor: top left
  • Pos X: 10
  • Pos Y: -10
  • Pos Z: 0
  • Width: 32
  • Height: 32
  • Source Image: CloseButton

Your RemoveAdsBackgroundScreen should now look similar to the following image:

CloseRemoveAds

The Purchase Succeeded panel

The next window we need is the panel that shows when the purchase succeeded.

To make your work easy, select RemoveAdsBackgroundScreen and then in the Inspector window, click on the checkbox next to its name in order to disable the GameObject. This will hide it from the scene, so when we create the next panel, they won't overlap.

The Purchase Succeeded panel
  1. First, right-click on the MenuUI GameObject, navigate to UI, and left-click on Panel. Name this Panel PurchaseSucceededBackgroundScreen.
  2. Then, right-click on PurchaseSucceededBackgroundScreen, navigate to UI, and left-click on Image. Name this image PurchaseSucceededScreen.
  3. Now, right-click on PurchaseSucceededBackgroundScreen, navigate to UI, and left-click on Text. Name this text PurchaseSucceededText.
  4. Right-click on PurchaseSucceededBackgroundScreen, navigate to UI, and left-click on Button. Name this button PurchaseSucceededAccept. Name its Text child as PurchaseSucceededAcceptText.
  5. Finally, let's right-click on PurchaseSucceededBackgroundScreen, navigate to UI, and left-click on Button. Name this button PurchaseSucceededClose and delete its Text child.

PurchaseSucceededBackgroundScreen

  • Anchor: center
  • Pos X: 0
  • Pos Y: 0
  • Pos Z: 0
  • Width: 265
  • Height: 180
  • Source Image: Background

PurchaseSucceededScreen

  • Anchor: center
  • Pos X: 0
  • Pos Y: 0
  • Pos Z: 0
  • Width: 256
  • Height: 171
  • Source Image: None
  • Color: 19—red, 19—G, 19—blue, 255—alpha

PurchaseSucceededText

  • Anchor: center
  • Pos X: 0
  • Pos Y: 0
  • Pos Z: 0
  • Width: 254
  • Height: 30
  • Text: Purchase Succeeded!
  • Font: Arial
  • Font Style: Bold
  • Font Size: 24
  • Alignment: Center

PurchaseSucceededAccept

  • Anchor: center
  • Pos X: 0
  • Pos Y: -50
  • Pos Z: 0
  • Width: 64
  • Height: 64
  • Source Image: MenuButton

PurchaseSucceededAcceptText

  • Anchor: stretch
  • Pos X: 0
  • Pos Y: 0
  • Pos Z: 0
  • Text: Ok
  • Font: Arial
  • Font Style: Bold
  • Font Size: 24
  • Alignment: Center

PurchaseSucceededClose

  • Pos X: 10
  • Pos Y: -10
  • Width: 32
  • Height: 32
  • Source Image: CloseButton

Your PurchaseSucceeededBackgroundScreen should now look similar to the following image:

PurchaseSucceededClose

The Purchase Failed panel

We need the exact same window for our failed window. There are two ways we could handle this. The first and more involved way is to use the succeeded window and change the text, so when something fails, we say it failed. The other way is to duplicate the same window and rename everything that says "Succeeded" to "Failed". For the sake of ease, go ahead and left-click on PurchaseSucceededBackgroundScreen in Hierarchy to select it.

Then, click on Ctrl + D. This will duplicate the GameObject. In this duplicated GameObject, rename everything from Succeeded to Failed, including the text component of PurchaseFailedText.

You should now have two separate Panel GameObjects in your Hierarchy, as shown in the following screenshot:

The Purchase Failed panel

We now need to write the code for the buttons of the PurchaseSucceeded panel and the PurhcaseFailed panel.

In the Assets/Scripts folder, double-click on the GameInfo class to open it.

At the top of the GameInfo class, add the following variables:

    // Reference to the RemoveAds screen
    private Transform RemoveAdsBackgroundScreen;

    // Reference to the Purchase screen
    private Transform PurchaseSucceededScreen;

    // Reference to the Purchase failed screen
    private Transform PurchaseFailedScreen;

Then, in the Start function, add the following code:

    // Called after Awake but before Update
    void Start( )
    {
      if ( MainMenuUI != null )
      {
        RemoveAdsBackgroundScreen = MainMenuUI.transform.Find( "RemoveAdsBackgroundScreen" );
        PurchaseSucceededScreen
        = MainMenuUI.transform.Find( "PurchaseSucceededBackgroundScreen" );
        PurchaseFailedScreen = MainMenuUI.transform.Find( "PurchaseFailedBackgroundScreen" );
      }
    }

Tip

If the Start function doesn't exist in the class because it was removed earlier, write it under the Awake function.

Under HideRestartButton, write the following functions:

      // Show or hide Purchase Window
      public void ShowPurchaseScreen( bool bShow )
      {
        if (RemoveAdsBackgroundScreen != null)
        {
          RemoveAdsBackgroundScreen.gameObject.SetActive( bShow );
        }
      }

      // Show or hide Success Window
      public void ShowSuccessScreen( bool bShow )
      {
        if (PurchaseSucceededScreen != null)
        {
          PurchaseSucceededScreen.gameObject.SetActive( bShow );
        }
      }

      // Show or hide Fail Window
      public void ShowFailScreen( bool bShow )
      {
        if (PurchaseFailedScreen != null)
        {
          PurchaseFailedScreen.gameObject.SetActive( bShow );
        }
      }

All three of these functions will handle their connected screen in the same way. If the bShow bool value is set to true, it will show the associated GameObject. If bShow is set to false, it will hide it. This is done using the SetActive function.

Now, save the GameInfo class.

We now need to make sure that the buttons in the MenuUI are using the correct function calls.

Let's go back to Unity and select the CloseRemoveAds GameObject in the MenuUI | RemoveAdsBackgroundScreen GameObject. In Inspector, search for the Button component and click on the small plus symbol to add an element to the On Click () list. Left-click and drag the GameInfo GameObject onto the object field and then select the drop-down menu located at the right. Navigate to GameInfo and then select the ShowPurchaseScreen function. Leave the small checkbox under the drop-down list unchecked.

Select the PurchaseSucceededAccept GameObject in the MenuUI | PurchaseSucceededBackgroundScreen GameObject. Again, navigate to the Inspector window. In the Button component, click on the small plus symbol to add an element to the On Click () list. Then, left-click and drag the GameInfo GameObject onto the object field. From the drop-down list, select GameInfo and then the ShowSuccessScreen function. Again, leave the small checkbox unchecked.

Do this again for the PurchaseSucceededClose GameObject, also a child of the PurchaseSucceededBackgroundScreen GameObject, which is in the MenuUI GameObject. Use the same ShowSuccessScreen function and leave the small checkbox unchecked.

Follow the same steps for PurchaseFailedBackgroundScreen, but instead of selecting ShowSuccessScreen, select ShowFailedScreen. Also, do this for the PurchaseFailedClose button.

Connecting the MainMenuUI reference to GameInfo

At the top of the GameInfo class, add the following variable:

    // MainMenu UI reference
    public Canvas MainMenuUI;

Next, select the GameInfo GameObject in Hierarchy by left-clicking on it. Then, left-click and drag MenuUI onto the Main Menu UI object slot of the GameInfo GameObject in Inspector.

Then, select the GameUI GameObject in Hierarchy. If it is not already hidden, click on the checkbox at the top of the Inspector window near the GameUI name to hide it.

Now, select the children GameObjects in MenuUI named RemoveAdsBackgroundScreen, PurchaseSucceededBackgroundScreen, and PurchaseFailedBackgroundScreen. Then, use the checkbox at the top of the Inspector window near their names and hide them.

The menu should now only show the Achievements, Play, Leaderboards, Remove iAds, and Restore Purchase buttons.

The menu code

My goal is to have the code compile at every section of this book. As of now, many of the alterations in this section will feel disorganized. It was a judgment call on my end to not have you do work on something that will break everything until a later chapter, which is why in this chapter, we will go back to some C# classes in order to expand or change the existing code within them. Although it is not uncommon for this sort of thing to happen while creating a video game, I know that it can be a bit irritating when you learn to have some back and forth.

The GameInfo menu code

For the main menu to have movement without any game play, we need to set a few things so that the level pieces are used only when they are needed. In the Assets/Scripts folder, open the GameInfo class by double-clicking on it.

At the top of the GameInfo class, add the following global variables:

    // If game is running or at menu idle
    [System.NonSerialized]
    public bool bGameRunning;
    // GameUI reference
    public Canvas GameUI;

Next, we need a function to call when Start button is pressed by the player. This button will transition MainMenuUI over to GameUI as well as change the way the LevelPieces are being connected. At the bottom of the GameInfo class, add the following function:

    // Game state button was pressed
    public void GameStateButtonPressed( bool bRunning )
    {
      bGameRunning = bRunning;

      if (MainMenuUI != null)
      {
        MainMenuUI.gameObject.SetActive(!bRunning);
      }

      if (GameUI != null)
      {
        GameUI.gameObject.SetActive(bRunning);
      }

      RestartGame( );
    }

This function will be used by PlayButton and a button that we will create to return to the main menu. As our game state is either true or false, we can use the single function and enter what type of game state we are looking for. For PlayButton, we want the game state to be set to true. For the menu button, we will set it to false.

In the last chapter, we saw how the RestartGame function began the cycle of fading the screen to black and then back to transparent. At the midpoint of going back to transparent, we can restart the character and the LevelPieces. As of the new flow of getting into the gameplay, we also need to update the RestartLevelAndCharacter function so that it sends the correct information to LevelManager. Change the RestartLevelAndCharacter function as follows:

    // Restart level and character
    private void RestartLevelAndCharacter( )
    {
      bLevelAndCharacterRestart = false;

      if ( GameCharacter != null )
      {
        GameCharacter.ReviveCharacter( );
      }

      if ( LevelManager != null )
      {
        LevelManager.ResetLevelPieces( bGameRunning );
      }
    }

The LevelPieceManager menu code

We will now pass the bGameRunning bool value to the RestartLevelPieces function of the LevelManager. To accommodate the new bool value, navigate to the Assets/Scripts folder and double-click on the LevelPieceManager C# file to open it.

Before we change the RestartLevelPieces function, we need to add a couple of global variables to the class. At the top of the LevelPieceManager class, add the following code:

    [System.NonSerialized]
    // If the game is being played or at main menu
    public bool bGameRunning;

    // MainMenu level piece
    public LevelPiece IdleLevelPiece;

From the LevelPieceManager perspective, bGameRunning will handle the current state of the game. IdleLevelPiece will be the secondary LevelPiece that we will use during the menu portion of the game (where the character runs forever).

We need to change the Start function as well because it was handling ActiveLevelPieces too directly for what our game needs now. Change the Start function as follows:

  // Use this for initialization
  void Start( ) 
  {
    ActiveLevelPieces = new LevelPiece[2];
    ResetLevelPieces( bGameRunning );
  }

This now only creates the ActiveLevelPieces array and then uses the RestartLevelPieces function to begin running the game in the correct state. As bool values default to false, we know that when we call this using Start, the game will start in the idle mode.

Change the RestartLevelPieces function in the LevelPieceManager class, as shown in the following code:

    // Resets all LevelPieces
    public void ResetLevelPieces( bool bRunning )
    {
      bGameRunning = bRunning;

      StartingLevelPiece.transform.position = StartingLevelPiece.GetInitialLocation();
      StartingLevelPiece.gameObject.SetActive(true);
      IdleLevelPiece.gameObject.SetActive( !bGameRunning );

      for (int i = 0; i < LevelPieces.Length; i++)
      {
        LevelPieces[i].transform.position = LevelPieces[i].GetInitialLocation();
        LevelPieces[i].ResetAllChildrenCoins();
      }

      if (bGameRunning)
      {
        SetGamePieces();
      }
      else
      {
        SetIdlePieces();
      }
    }

You'll see here that this class also has a bGameRunning bool value. This is because LevelPieceManager is in sync with GameInfo. We can also reset StartingLevelPiece and make sure that it can be seen because this piece is used in both the menu and game states. We can then set IdleLevelPiece so that it is only active if the bGameRunning bool is set to false. We do not want to use it when the game is running.

No matter what the game state is, we need to reset the LevelPieces before running SetIdlePieces or SetGamePieces. This way, the LevelPieces are out of the view of the player until they are needed, which is handled in the Update function. We accomplish this with the for loop that sets the LevelPieces to their GetInitialLocation and also resets all of their children, coins, and obstacles so that they can be used again by the player.

This function also has two more function calls: SetGamePieces and SetIdlePieces. These two functions are designed to make the ActiveLevelPieces fit to whichever game state we are in: idle or game.

In the Assets/Scripts folder, double-click on the LevelPiece code file to open it. At the top of the class, add the following global variable:

    // End location of Level Piece
    public Transform EndLocation;

This will give us a direct reference to the EndLocation Transform so that we don't have to find it every time we use it.

Save this file and go back to Unity. Select each of the LevelPieces, take the GameObject with the name of EndLocation in Hierarchy, and left-click and drag it onto the new EndLocation object field of the LevelPiece. Do this for all the LevelPiece GameObjects in the scene.

The LevelPieceManager menu code

Tip

Remember to put EndLocation, a child for each of the LevelPiece GameObjects, and not put the same EndLocation for all of them.

Back in the LevelPieceManager, under the Start function, write the follow functions:

    // Set ActiveLevelPieces to Idle
    void SetIdlePieces( )
    {
      ActiveLevelPieces[ 0 ] = StartingLevelPiece;
      ActiveLevelPieces[ 1 ] = IdleLevelPiece;
      ActiveLevelPieces[ 1 ].transform.position = StartingLevelPiece.EndLocation.position;
    }

    // Set ActiveLevelPieces to Game
    void SetGamePieces( )
    {
      ActiveLevelPieces[0] = StartingLevelPiece;
      ActiveLevelPieces[1] = GetRandomLevelPiece();
      ActiveLevelPieces[1].transform.position = StartingLevelPiece.EndLocation.position;
    }

SetIdlePieces takes StartingLevelPiece and IdleLevelPiece and assigns them to the zero and one elements of ActiveLevelPieces. It also takes IdleLevelPiece and moves it to the EndLocation of the first element of ActiveLevelPieces, which is more easily referred to as StartingLevelPiece.

SetGamePieces does the same thing as we had the Start function doing earlier. It assigns the first element of ActiveLevelPieces to StartingLevelPiece and then uses GetRandomLevelPiece to assign the next element. It then assigns the return LevelPiece from the GetRandomLevelPiece position to the EndLocation of StartingLevelPiece.

These two functions behave very similarly. The only difference is what's being assigned.

We now need to change the Update function to change between moving the game LevelPieces and the idle LevelPieces. Change the current Update function as follows:

  // Update is called once per frame
  void Update ( )
  {
    for (int i = 0; i < ActiveLevelPieces.Length; i++)
    {
      Vector3 newLocation = ActiveLevelPieces[i].transform.position;
      newLocation.x -= LevelPiecesMoveRate * Time.deltaTime;

      ActiveLevelPieces[i].transform.position = newLocation;

      if (ActiveLevelPieces[i].transform.position.x < transform.position.x)
      {
        if (bGameRunning)
        {
          if (ActiveLevelPieces[i] == StartingLevelPiece)
          {
            ActiveLevelPieces[i].gameObject.SetActive(false);
          }

          ActiveLevelPieces[i].transform.position = ActiveLevelPieces[i].GetInitialLocation();

          ActiveLevelPieces[i] = GetRandomLevelPiece();
          ActiveLevelPieces[i].transform.position = FindOtherLevelPiece(ActiveLevelPieces[i]).EndLocation.position;
          ActiveLevelPieces[i].ResetAllChildrenCoins();
        }
        else
        {
          LevelPiece nextLevelPiece = ( i == 0 ) ? ActiveLevelPieces[ 1 ] : ActiveLevelPieces[ 0 ];

          ActiveLevelPieces[ i ].transform.position = nextLevelPiece.EndLocation.position;
        }
      }
    }
  }

You'll see that this time, we are using the bGameRunning bool to decide how to transition between the ActiveLevelPieces. Luckily, the menu only uses two, so if bGameRunning is set to false, we can assign nextLevelPiece with the opposite of what element i represents. As we know that there are only two ActiveLevelPieces, if the game is not running, we can use the value of i to get the other ActiveLevelPieces element, using the ternary operator to check whether I is equal to zero. If it is, we will assign nextLevelPiece to the second element of the ActiveLevelPieces array. If it isn't, we will assign nextLevelPiece to the first element of the ActiveLevelPieces array.

Whichever piece gets chosen as nextLevelPiece, we set the current i value of the ActiveLevelPiece array position to the end of it. This takes the ActiveLevelPiece that has gone out of viewpoint of the player and moves it back so that it can be used again.

The code used if bGameRunning is set to true is the same as we had before; if a piece gets out of view of the player, we can choose another with GetRandomLevelPiece, reset it to default, and attach it at the end of the moving ActiveLevelPieces so that it can be used again.

Now, save the LevelPieceManager class.

The character menu code

The last step to getting the idle menu code working is adding it to the character so that it is aware when it should be counting up distance and when to accept input.

In the Assets/Scripts folder, open the Character C# file by double-clicking on it. At the top of the class, add the following global variable:

    public LevelPieceManager LevelManager;

As there is a delay fading the GameInfo class, we will use the bGameRunning bool LevelPieceManager as the more accurate one before the LevelPieceManager becomes aware that the game is running. This way, the distance calculations and the input will be more in line with when the player starts to see the game scene.

We now need to use the LevelManager.bGameRunning bool value in a couple of places. Navigate to the AddDistance function and change if (GameUI != null) to if (GameUI != null && LevelManager.bGameRunning).

This will prevent any distance calculations before the game is set to running. Next, navigate to the RecieveInput function and change if (characterRigidBody != null && characterAnimator != null) to if (characterRigidBody != null && characterAnimator != null && LevelManager.bGameRunning).

This again prevents any player input from manipulating the character unless the game is running.

Now, save the Character class.

Completing changes in Unity

For the changes to be complete, we need to add the new references and GameObjects to the scene.

Open the Assets/Prefabs folder and left-click and drag the StartingLevelPiece prefab onto the scene. Move it away from the view of the camera; I put mine near the other LevelPieces. In Hierarchy, rename it as IdleLevelPiece.

Select the LevelPieceManager GameObject in Hierarchy and then left-click and drag the new IdleLevelPiece GameObject from Hierarchy onto the Idle Level Piece object slot of LevelPieceManager in the Inspector window.

Select the Character GameObject from Hierarchy by left-clicking on it. Then, left-click and drag the LevelPieceManager onto the Level Manager object slot of Character in the Inspector window.

Select the PlayButton GameObject under the MenuUI GameObject. In the Button component, in the Inspector window, click on the small plus sign to add a new On Click () list element. Left-click and drag the GameInfo GameObject from Hierarchy and move it into the object slot of the On Click list element. Left-click the drop-down menu for the On Click () list and select GameInfo and then select GameStateButtonPressed. Also, for PlayButton, we need to tick the small box that appears under the On Click () drop-down menu because we want PlayButton to enter true for the game state to change to true so that the LevelPieceManager and GameInfo will know that the game is running.

You should now be able to play the game and see the character run forever on the menu, but when you press play, MenuUI will disappear and GameUI will show. The screen will also fade to black and back to transparent, and the game will start normally with the LevelPieces being put together and reset as the game continues. If the character dies, the restart button will be shown.

Adding the BackToMenu button in GameUI

The next step is to allow the player to go back to the menu from the game when they want to. In Hierarchy, select MenuUI and use the checkbox next to its name in Inspector to hide it from the scene. Then, do the same for GameUI, but instead of hiding it, it shows it in the scene.

With the GameUI GameObject still selected in Hierarchy, right-click on it and navigate to UI and then select Button. Name this button BackToMenuButton. Name its child Text GameObject BackToMenuText.

Now, change the BackToMenuButton settings in Inspector as follows:

  • Anchor: bottom center
  • Pos X: 0
  • Pos Y: 158.5
  • Pos Z: 0
  • Width: 128
  • Height: 64
  • Source Image: RestartButton

Now, change the BackToMenuText settings in the Inspector as follows:

  • Anchor: stretch
  • Text: Menu
  • Font: Arial
  • Font Style: Bold
  • Font Size: 32
  • Alignment: Center
  • Color: 255—red, 255—green, 255—blue, 255—alpha

You should now have another button before the Restart button with the Menu text, as shown in the following image:

Adding the BackToMenu button in GameUI

We now need to update the HideRestartButton function in the GameInfo class so that it also hides or shows the Menu button with it. In the Assets/Scripts folder, open the GameInfo C# class by double-clicking on it.

Find HideRestartButton and change it, as shown in the following code:

    // Shows or hides the restart butt
    public void HideRestartButton(bool bHide)
    {
      if (GameUI != null)
      {
        GameUI.transform.Find( "RestartButton" ).gameObject.SetActive( !bHide );
        GameUI.transform.Find( "BackToMenuButton" ).gameObject.SetActive( !bHide );
      }
    }

Save the GameInfo class and go back to Unity.

As we wrote the GameStateButtonPressed function earlier, we can also use it for the BackToMenu button.

Select BackToMenuButton in Hierarchy under the GameUI GameObject. In Inspector, click on the small plus symbol to add an element to the On Click () list.

Then, left-click and drag the GameInfo GameObject from Hierarchy onto the On Click () object field for BackToMenuButton. In the drop-down list, navigate to GameInfo and select GameStateButtonPressed. Leave the checkbox that appears unchecked. We don't want this checked because we want to enter false so that the bGameRunning value changes to false. This will call the SetIdlePieces function and begin running the ActiveLevelPieces for the menu in LevelPieceManager.

Make sure that you hide the GameUI GameObject using the Inspector checkbox next to its name and unhide the MenuUI GameObject using the same checkbox before you run the game. If you do not do this, the first shown menu when the game starts will be of the GameUI, instead of the MenuUI.

You should now be able to click on Play button to start playing, Restart button to restart a game, and BackToMenu button to go back to the main menu.

Code for leaderboard and achievement buttons

In the Assets/Scripts folder, double-click on the GameInfo class to open it. Under the GameStateButtonPressed function, add these two functions:

    // Opens the leaderboards window
    public void ShowLeaderboards()
    {
      GameCenterManager.ShowLeaderboard( "G_Distance" );
    }

    // Opens the achievements window
    public void ShowAchievements()
    {
      GameCenterManager.ShowAchievements( );
    }

Both of these functions are very simple. As GameCenterManager will manage most of the GameCenter features, it includes the ShowLeaderboards and ShowAchievements functions that will open their related windows.

Save the GameInfo class and go back to Unity.

Left-click on the Leaderboard button in Hierarchy under the MenuUI GameObject to select it. Search for Inspector. Now, in the Button component, click on the small plus symbol to add the On Click () elements. Left-click and drag the GameInfo GameObject onto the object field of the On Click () elements. Click on the drop-down menu that appears at the right-hand side, navigate to GameInfo, and select Show Leaderboard.

Left-click on Achievement Button in Hierarchy under the MenuUI GameObject to select it. Search for Inspector. Then, in the Button component, click on the small plus symbol to add the On Click () elements. Left-click and drag the GameInfo GameObject onto the object field for the On Click () elements. Click on the drop-down menu that appears at the right-hand side, navigate to GameInfo, and select Show Achievements.

This will allow the Leaderboard button and the Achievement button to open the windows so that the player can see the current state of each.

Code for achievements

At this point, the final steps we need to take is to use the IOS Native plugin to call achievements, leaderboards, and store purchases.

If you don't remember, the three achievements we set up are:

  • 10Rounds
  • 100Pickups
  • 100Yards

This means that we need to begin storing how many rounds the player has played with our PlayerPrefs class. As we already know how many coins the player has, we don't need to save this, but we do need to have a condition so that we can award the achievement as soon as they collect 100 coins. Finally, we need to check how far the player has run every time we call the AddDistance function in the Character class to check whether they have reached 100 yards. If they have, we then can award the achievement.

In the Assets/Scripts folder, double-click on the GameInfo C# file to open it. At the top of the class, add the following enum list:

    public enum RunnerAchievements
    {
      RA_Rounds,
      RA_Pickups,
      RA_Yards,
    };

We will use this enum list to know which achievement to set the progress for or unlock.

Next, we need to set up GameCenter so that it is running and available. Add the Awake function so that it looks similar to the following code:

    // When object starts
    void Awake( )
    {
      if ( FadeObject != null )
      {
        FadeObject.transform.position = new Vector3( 0.5f, 0.5f, 0.0f );
        FadeTexture = FadeObject.GetComponent<GUITexture>( );
        FadeTexture.pixelInset = new Rect (0.0f, 0.0f, Screen.width, Screen.height );

        RestartGame( );
      }

      // Register all achievements
      GameCenterManager.RegisterAchievement( "G_100Yards" );
      GameCenterManager.RegisterAchievement( "G_100Pickups" );
      GameCenterManager.RegisterAchievement( "G_10Rounds" );

      // Delegates for Achievements
      GameCenterManager.Dispatcher.addEventListener( GameCenterManager.GAME_CENTER_ACHIEVEMENT_PROGRESS, OnAchievementProgress );
      GameCenterManager.Dispatcher.addEventListener( GameCenterManager.GAME_CENTER_ACHIEVEMENTS_RESET, OnAchievementsReset );

      // OnAchievementLoaded Delegate
      GameCenterManager.OnAchievementsLoaded += OnAchievementLoaded;

      // Init GameCenter
      GameCenterManager.init( );

      // DEBUGGING ONLY -- REMOVE FOR FINAL BUILD
      //     Reset Achievements
      GameCenterManager.ResetAchievements( );
    }

Tip

GameCenterManager is a part of the IOS Native plugin and is designed to help use call native code for the GameCenter features.

The new code starts by using RegisterAchievement, which takes in a string value. We do this so that we have access to these achievements, although they do not have any reported progress.

Next, we will do something that we haven't done yet. We will use the addEventListener function to add a delegate function. A delegate is a way to call a function when something else happens. In our case, we will use it when we get the progress back from an achievement and when we reset achievements.

We then use the OnAchievementLoaded delegate and assign it to GameCenterManager.OnAchievementLoaded. Delegates look a bit strange because they are assigned with the += operator. What this will do is tell GameCenterManager that when its OnAchievementLoaded event is called to call the OnAchievement loaded function in our GameInfo class. This is the handle part of delegates; you don't have to check whether something has happened, but only assign what to do when it does happen.

After this, we can use the Init function for GameCenterManager. The Init function starts GameCenterManager so that the player gets logged in to GameCenter, or if they haven't logged in before, the GameCenter login screen will show for them.

Lastly, we will call the ResetAchievements function. This is purely for testing purposes. This means that every time you load the game, the achievements will be reset. This is not ideal for the end user and should only be used for us, so we can test that everything is working. If we didn't reset the achievements, we could only unlock them once. This would become an issue if we wanted to check whether they can be unlocked.

Game center events

In order to know what is going on with the achievements, we need some functions that get called when specific aspects of the achievement system gets used. At the bottom of the GameInfo class, add the following function:

    // When achievements are loaded
    private void OnAchievementLoaded(ISN_Result Result)
    {
      if (Result.IsSucceeded)
      {
        foreach (AchievementTemplate template in GameCenterManager.Achievements)
        {
          print( template.id + ": " + template.progress );
        }
      }
    }

This function will let us know when an achievement has been loaded. Right now, it only prints out what the achievement ID is and its progress. If you were making a more complex game with a custom UI, you could use this to enter the information of the achievement that is being loaded.

Beneath the OnAchievementLoaded function, add the following code:

    // When achievements are reset
    private void OnAchievementsReset()
    {
      // Achievements are reset
    }

Again, this is just another function that will be called when the achievements are reset. It doesn't do anything yet, but I wanted to add it for you if you would like to use it.

Next, add the following code under OnAchievementReset:

    // When achievements send progress
    private void OnAchievementProgress(CEvent Event)
    {
      ISN_AchievementProgressResult result = Event.data as ISN_AchievementProgressResult;

      if (result.IsSucceeded)
      {
        AchievementTemplate template = result.info;
        print( template.id + ": " + template.progress.ToString( ) );
      }
    }

Once again, this function is only here if you want to use it. This will give you the achievement progress about the event data. It is doing nothing here but printing out the progress and ID that is being sent when its progress has changed. The CEvent class as the function argument comes from the IOS Native plugin.

Finally, add the last of the event functions:

    // Player is logged into GameCenter
    void OnAuthFinished(ISN_Result res)
    {
      if (res.IsSucceeded)
      {
        IOSNativePopUpManager.showMessage("Player Authored ", "ID: " + GameCenterManager.Player.PlayerId + "
" + "Alias: " + GameCenterManager.Player.Alias);
      }
      else
      {
        IOSNativePopUpManager.showMessage("Game Center ", "Player auth failed");
      }
    }

If ISN_Result is successful, this function will let the player know that they have logged in to GameCenter by displaying a message to them that they have (while in the game). If it is not, it will tell the player that the GameCenter authentication has failed.

SubmitAchievementProgress

Next, we need to add the functions that we will use to set our achievements for our game. Add the following function under OnAchievementProgress:

    // Set achievement for RunnerGame
    public void SubmitAchievementProgress(RunnerAchievements Achievement, float AchievementValue )
    {
      string lastAchievement = "";
      switch (Achievement)
      {
        case RunnerAchievements.RA_Pickups:
          lastAchievement = "G_100Pickups";
            break;
        case RunnerAchievements.RA_Rounds:
          lastAchievement = "G_10Rounds";
            break;
        case RunnerAchievements.RA_Yards:
          lastAchievement = "G_100Yards";
            break;
      }

      GameCenterManager.SubmitAchievement(GameCenterManager.GetAchievementProgress(lastAchievement) + AchievementValue, lastAchievement);
    }

As the SubmitAchievement function takes a string, we need to take the enum value we have for our achievements and use it to create a string of the string ID of the achievements we set up in iTunes Connect.

We can use a switch statement to see what enum value was passed into the function and then based on that, assign LastAchievement to the value of the string ID we set in iTunes Connect.

Based on the value of LastAchievement, we can use the SubmitAchievement function to submit the current progress value of the achievement plus the new value being passed. This will update the current progress of the achievement. If it reaches the value we set in iTunes Connect for when it unlocks, GameCenter will send the user a broadcast banner that they have unlocked it.

SubmitAchievementAsWhole

Because of the way we unlock the distance achievement, we need a way to reset it back to zero if the player failed to reach the 100 distance marker. As a result, we don't want the achievement to continue to count upwards each attempt until the distance of 100 has been traveled and instead will only unlock if the player reaches the distance of 100 in a single attempt.

Under the SubmitAchievementProgress function, add the following code:

    // Instead of adding to an achievement progress value, set it as a whole
    public void SubmitAchievementAsWhole(RunnerAchievements Achievement, float AchievementWholeValue)
    {
      string lastAchievement = "";
      switch (Achievement)
      {
        case RunnerAchievements.RA_Pickups:
          lastAchievement = "G_100Pickups";
            break;
        case RunnerAchievements.RA_Rounds:
          lastAchievement = "G_10Rounds";
            break;
        case RunnerAchievements.RA_Yards:
          lastAchievement = "G_100Yards";
            break;
      }

      GameCenterManager.SubmitAchievement(AchievementWholeValue, lastAchievement);
    }

This function is very similar to the SubmitAchievementProgress function, but instead of adding the existing progress of the achievement, we set a whole value to it or a complete value. If there is ever a point when you wanted to submit an achievement for a single action or all at once, this is a way to handle it. For the current game, it will be used to either reset the G_100Yards achievement to zero or complete.

SubmitLeaderboardScore

Next, we need a function to submit a leaderboard score. In the GameInfo class, under the SubmitAchievementAsWhole function, add the following code:

    // Submits a leaderboard score
    public void SubmitLeaderboardScore(int DistanceScore)
    {
      GameCenterManager.ReportScore( DistanceScore, "G_Distance" );
    }

This will submit an integer score to the leaderboard with the ID of G_Distance, which is our only leaderboard.

The character achievement code

We now need to use the Character class to both keep track of the data we need, such as AttemptCount, and call the GameInfo achievement functions in order to update them.

At the top of the Character class, add the following code:

    [System.NonSerialized]
    public int AttemptCount;

This will be used to keep a tally on how many times the player has played the game.

Next, we need to save AttemptCount with the PlayerPrefs class. Change the Start function so that it looks similar to the following code:

  // Use this for initialization
  void Start ()
  {
    RestartLocation = gameObject.transform.position;

    AttemptCount = PlayerPrefs.GetInt( "Attempts" );
    CoinCount = PlayerPrefs.GetInt( "Coins" );
    AddCoins( 0 );
  }

We set the AttemptCount to be set each time the character is loaded so that it is equal to what has been saved in PlayerPrefs. This way, when we send it to the SubmitAchievement function, it will get updated from the correct AttemptCount value.

The last thing we need to do is to add the portion of code that will send the achievement data when the player is being revived.

Change the KillCharacter function as follows:

    // Kills the character
    public void KillCharacter()
    {
      Rigidbody2D characterRigidBody;

      if (!isDead)
      {
        characterRigidBody = gameObject.GetComponent<Rigidbody2D>();
        if (characterRigidBody != null)
        {
          characterRigidBody.AddForce(new Vector2(Random.Range(-1, 1), 1) * 512);
          isDead = true;
          isFadeOut = true;

          if (Game != null)
          {
            Game.HideRestartButton(false);
          }

          PlayerPrefs.SetInt("Coins", CoinCount);
          PlayerPrefs.Save();

          // Save attempts
          AttemptCount += 1;
          PlayerPrefs.SetInt("Attempts", AttemptCount);
          PlayerPrefs.Save();

          if (Game != null)
          {
            Game.SubmitAchievementProgress(GameInfo.RunnerAchievements.RA_Rounds, AttemptCount);

Game.SubmitAchievementAsWhole(GameInfo.RunnerAchievements.RA_Pickups, CoinCount);

            // If DistanceCount is less than 100 AND achievement hasn't been unlocked, reset it 
            if (GameCenterManager.GetAchievementProgress("G_100Yards") < 100.0f && DistanceCount < 100)
            {
              Game.SubmitAchievementAsWhole(GameInfo.RunnerAchievements.RA_Yards, 0.0f);
            }
            // If DistanceCount is more than 100, set achievement as complete
            else
            {
              Game.SubmitAchievementProgress(GameInfo.RunnerAchievements.RA_Yards, DistanceCount);
            }
            Game.SubmitLeaderboardScore( DistanceCount );
          }
        }
      }
    }

This moves on to what we already had at AttemptCount += 1. From here, we set AttempCount to PlayerPrefs using the SetInt function and then use the Save function to save this value.

We then check to make sure that the reference to Game exists before we submit the achievement progress using the SubmitAchievementProgress function. We do this for AttemptCount. We then use SubmitAchievementAsWhole for CoinCount because this progress will be a whole value from what is saved using PlayerPrefs.

Before we set the distance achievement, we want to make sure that the progress of the distance achievement is less than one hundred and that the player had reached a DistanceCount of one hundred. If they didn't or if the achievement hasn't been unlocked, we reset its progress to zero. This way, if the player starts again, they won't be able to continue only from where they failed.

If the player did reach the one hundred distance, we can use the SubmitAchievementAsWhole function to set the progress to the value of DistanceCount, which will be at least one hundred, meaning they unlocked the achievement.

We can also call SubmitLeaderboardScore that passes through DistanceCount, so the leaderboard will reflect how far the player has gone as a record.

This is it for the achievement code. Make sure to save the Character and GameInfo classes.

Displaying iAds

As we want to display ads only to those who have not purchased to remove them, we need another value to save the PlayerPrefs class. In the Assets/Scripts folder, open the GameInfo class by double-clicking on it.

At the bottom of the list of global variables, add the following code:

    // If HideiAds has been unlocked
    private int HideiAds;

    // Reference to iAd banner
    private iAdBanner AdBanner;

As the PlayerPrefs class doesn't store bool values, we have to store it as an int. We will use the default of zero to know that the player is going to see ads. Then, if they buy to remove them, we will save the HideiAds value to one.

We also need to keep the reference to the created AdBanner so that we can hide and show it, depending on the bGameRunning value. If the bGameRunning value is set to true, we will hide the iAds so that it doesn't get in the way of GameUI.

At the bottom of the GameInfo class, write the following function:

    // Creates instance of iAd if doesn't exist
    // Show or hide it depending on bShow
    public void ShowIAds(bool bShow)
    {
      HideiAds = PlayerPrefs.GetInt( "ShowiAds" );
      if (HideiAds == 0)
      {
        if (AdBanner == null)
        {
          if (bShow)
          {
            AdBanner = iAdBannerController.instance.CreateAdBanner(TextAnchor.UpperCenter);
            AdBanner.Show();
            AdBanner.AdViewFinishedAction += BannerLoaded;
          }
        }
        else
        {
          if (bShow)
          {
            AdBanner.ShowOnLoad = true;
            AdBanner.Show();
          }
          else
          {
            AdBanner.ShowOnLoad = false;
            AdBanner.Hide();
          }
        }
      }
    }

We start this function by assigning the value of HideiAds to the PlayerPrefs saved valued of it. If it's set to zero, when check whether the AdBanner reference is null. We know that if it's null, we need to create it, which is what the code does if the banner is null and the bool value being passed is set to true. Once it's created, we show it using the Show function. We also set a delegate so that we know when it's loaded. This is useful, so we can check whether the game is running when the banner has loaded. If the game is running, we can use the BannerLoaded function to hide it.

If AdBanner exists, we then check whether the bool value is set to true or false. If it's set to true, we show the banner. If it' set to false, we hide it.

Add the following function before the ShowIAds function:

    // Banner has loaded
    public void BannerLoaded()
    {
      if (bGameRunning)
      {
        AdBanner.Hide( );
      }
    }

This is a very simple function that will be called when AdBanner has been loaded. If the game is running, we hide it so that it doesn't get in the way of the gameplay.

We now need to add the GameStateButtonPressed function so that it shows or hides the banner based on the value of bGameRunning. Change the GameStateButtonPressed function as follows:

    // Game state button was pressed
    public void GameStateButtonPressed( bool bRunning )
    {
      bGameRunning = bRunning;

      if (MainMenuUI != null)
      {
        MainMenuUI.gameObject.SetActive(!bRunning);
      }

      if (GameUI != null)
      {
        GameUI.gameObject.SetActive(bRunning);
      }

      ShowIAds( !bGameRunning );

      RestartGame( );
    }

The addition here is the ShowIAds function, which passes the opposite value of bGameRunning. More simply, it only shows the banner if bGameRunning is set to false.

Now, at the bottom of the Awake function, add the following code:

ShowIAds( true );

This will try to show the AdBanner object as soon as the GameInfo class starts running. It will fail if the HideiAds value is not set to zero.

Save the GameInfo class.

Purchasing remove iAds

The first thing we need to do is finish setting up the iTunes Connect in-app purchases settings. Perform the following steps:

  1. Navigate to https://itunesconnect.apple.com.
  2. Sign in using your developer account.
  3. Then, go to My Apps.
  4. Select your app, the one we created in Chapter 1, Requirements and Preparation Work.
  5. Now, click on In-App Purchases.
  6. It should have a red circle that says Waiting for Screenshot.
  7. Click on Edit next to In-App Purchase Details.
  8. Make sure that No is selected for Hosting Content With Apple.
  9. You do not need to include Review Notes.
  10. Now, select File.
  11. Navigate to the book Art files and search for the ChapterSix_InApp folder.
  12. Select RemoveIAds.png. (This will only work if you plan on using the UI we created. If you create your own game or your own UI art, you will need to include a screenshot for this.)
  13. Go back to My Apps and select your game again.
  14. Scroll down to the In-App Purchases section and click on the plus symbol to add a purchase.
  15. Select our G_RemoveAds purchase.
  16. Then, click on Save in the top-right corner.
  17. Now, scroll to the top of the web page and click on the My Apps drop-down list and then select Users and Roles.
  18. Then, click on Sandbox Testers on the Users and Roles page.
  19. Now, click on the small plus sign next to Testers to add one.
  20. Enter the relevant information on this page. The e-mail has to be something other than what you normally use. This is a bit of an annoyance, but this is how the testing needs to be set up (as of writing this book).
  21. When on your iOS device, make sure to sign out of your normal account and use this testing account for testing purposes.

New RemoveiAds Purchase for coins

In order for us to remove iAds for the cost of coins, we need to create another in-app purchase. Perform the following steps:

  1. In iTunes Connect, navigate to My Apps and click on your game.
  2. Now, click on the In-App Purchases section at the top of the page.
  3. Then, click on Add New on the In-App Purchases page.
  4. Select Non-Consumable.
  5. Then, select Reference Name as Remove_Ads_Coins.
  6. Similarly, select Product ID as G_RemoveAdsCoins.
  7. Then, select Cleared For Sale as No.
  8. Now, select Price Tier as Free.
  9. Then, select Screenshot For Review as ChapterSix_InApp, Remove IAds.png, or what's suitable for you.
  10. Select Add Language as English or whatever suits you.
  11. Select Hosting Content With Apple as No.
  12. Also, select Review Notes as None.
  13. Finally, click on Done to save this.

We want this to be free because we will check to see how many coins the player has before purchasing. We also want the player to keep the purchase if they did use coins to buy it.

The In App Purchase code

Go back to Unity. In the Assets/Scripts folder, double-click on the GameInfo class to open it. In the Awake function, before the GameCenterManager code, but under the FadeObject code, add the following code:

// RemoveAds as Product
IOSInAppPurchaseManager.instance.addProductId( "G_RemoveAds" );
IOSInAppPurchaseManager.instance.addProductId( "G_RemoveAdsCoins" );

// Add listening for Delegates
IOSInAppPurchaseManager.instance.addEventListener( IOSInAppPurchaseManager.RESTORE_TRANSACTION_FAILED, OnRestoreTransactionFailed );
IOSInAppPurchaseManager.instance.addEventListener( IOSInAppPurchaseManager.VERIFICATION_RESPONSE, OnVerificationResponse );

// Assign delegates 
IOSInAppPurchaseManager.instance.OnStoreKitInitComplete += OnStoreKitComplete;
IOSInAppPurchaseManager.instance.OnTransactionComplete += OnTransactionComplete;

// Load the store.
IOSInAppPurchaseManager.instance.loadStore( );

This is similar to what we did with the GameCenterManager setup. The only difference this time is that it's specific for IOSInAppPurchaseManager.

We start by adding a product ID, using addProductID, which takes the name of our purchase: G_RemoveAds.

We will then set up a couple of event listeners. This will be used for delegate calls when events related to InAppPurchase are called.

Let's assign a couple of more delegates to OnStoreKitInitComplete and OnTransactionComplete.

Finally, we will load the store using the loadStore function, which prepares the store to be used.

Next, we need to add the function calls that are used for the delegates. At the bottom of the GameInfo class, under the ShowIAds function, start by adding this function:

    // Transaction Complete
    private static void OnTransactionComplete(IOSStoreKitResponse Response)
    {
      GameInfo game = FindObjectOfType<GameInfo>();
      switch (Response.state)
      {
        case InAppPurchaseState.Purchased:
        case InAppPurchaseState.Restored:
          game.ShowSuccessScreen( true );
          game.HaveUnlockedIAds( Response.productIdentifier == "G_RemoveAdsCoins", Response.state == InAppPurchaseState.Restored );
            break;
        case InAppPurchaseState.Deferred:
          break;
        case InAppPurchaseState.Failed:
          game.ShowFailScreen( true );
            break;
      }
    }

This is called when a transaction has been completed, including if the transaction fails for any reason. We start using this by getting a reference to the GameInfo class by using the FindObjectOfType function. We do this because this function is a static function, meaning it is usable even if there is no reference to the class instance. Due of this, the static function sort of lives in its own portion of code and doesn't know of the reference of the GameInfo, although it exists within it, which is why we need a reference to it.

From there, we use a switch statement and check the Response.state. If the state is Purchased or Restored, we show the success screen with ShowSuccessScreen and then call a function in GameInfo called HaveUnlockIAds and pass productIdentifier as equal to G_RemoveAdsCoins. This means that if the product ID of the purchase is from coins, we know that because the bool value passed HaveUnlockedIAds, it will be set to true. This is the same for if the response state is equal to Restored.

The next three functions are delegates used by the store manager, but for our game, we do not need them. Just in case you do, use the following code:

    // Verification response
    private static void OnVerificationResponse(CEvent e)
    {
      // Verified response on purchase
    }

    // Transaction failed
    private static void OnRestoreTransactionFailed()
    {
      // Transaction failed
    }

    // StoreKit Init
    private static void OnStoreKitComplete(ISN_Result result)
    {
      // Store was completed Init
      if (result.IsSucceeded)
      {
        // succeeded
      }
      else
      {
        // failed
      }
    }

OnVerificationResponse is used when a store purchased has been verified.

OnRestoreTransactionFailed is used when a restore transaction has failed; we will use our OnTransactionComplete function to handle restore purchases.

OnStoreKitComplete is used when the store has been initialized.

The Purchase RemoveIAds functions

Under OnStoreKitComplete, add the following function:

    // Purchase was successful, remove coins, and hide ad banner
    public void HaveUnlockedIAds(bool bForCoins, bool bRestored)
    {
      if (bForCoins && !bRestored)
      {
        GameCharacter.AddCoins(-10000);
      }

      PlayerPrefs.SetInt("ShowiAds", 1);
      if (AdBanner != null)
      {
        AdBanner.Hide();
        AdBanner = null;
      }
    }

This is the function we will call from OnTransactionComplete if the purchase succeeded or the restore purchase succeeded.

Let's start by checking whether the bForCoins argument is set to true and bRestored is set to false because we don't want to dock the player if the purchase is being restored. If it is, we subtract the ten thousand coin cost from the character using AddCoins. If we pass a negative value into it, it will subtract the amount.

Next, we will set the ShowIAds int value, which we save with PlayerPrefs to one. This way, AdBanner won't show anymore. Then, we need to check whether the AdBanner exists by checking it against null. If it does, we hide it using the Hide function and then set the AdBanner reference to null.

Lastly, we need a function that gets called from MenuUI when the player taps a button to buy something. Add the following code under the HaveUnlockedIAds function:

    // Tries to purchase RemoveiAds
    public void UnlockRemoveAds(bool bForCoins)
    {
      if (bForCoins)
      {
        if ( GameCharacter.CoinCount >= 10000)
        {
          IOSInAppPurchaseManager.instance.buyProduct("G_RemoveAdsCoins");
          ShowPurchaseScreen(false);
        }
      }
      else
      {
        IOSInAppPurchaseManager.instance.buyProduct("G_RemoveAds");
        ShowPurchaseScreen(false);
      }
    }

If the purchase is for coins, we then check whether the character has ten thousand or more coins. If they do, we can use IOSInAppPurchaseManager to buy the product with buyProduct. We also pass the product ID for the remove ads with coins. We then close the purchase screen so that it doesn't stay open during the purchase process. If the player is trying to pay with coins, but doesn't have enough coins, the window will simply stay open and nothing will happen.

If the purchase is not for coins, we will again use IOSInAppPurchaseManager to buy a product, but we can pass the product that costs ninety nine cents. Again, close the purchase screen.

Now, save the GameInfo class and go back to Unity.

Purchasing buttons

If RemoveAdsBackgroundScreen is hidden, select it in Hierarchy and use the Inspector window to unhide it by clicking on the small checkbox next to its name.

Select the button called For Coins and search for Inspector. In the Button component, click on the small plus symbol to add an element to the On Click () list. Then, left-click and drag the GameInfo GameObject onto the object field of the On Click () list. Click on the drop-down menu, navigate to GameInfo, and select UnlockRemoveAds. Then, check the small checkbox under the drop-down list because this purchase is for coins.

Next, select the button called For Cash and repeat the preceding steps. This time, do not click on the small checkbox under the On Click () drop-down list because this purchase is for money.

Restoring purchases

Open the Assets/Scripts folder and double-click on the GameInfo class to open it. At the bottom of the class, add the following function:

    // Attempts to restore purchases
    public void RestorePurchases()
    {
      IOSInAppPurchaseManager.instance.restorePurchases( );
    }

This will attempt to restore purchases and then call OnTransactionComplete so that we can handle how to use it.

Tip

Apple requires you to have an extra button in the game to restore purchases. You cannot simply do this on purchase; they will not allow the game on the iTunes store if you do not have this extra Restore Purchases button.

Now, save the GameInfo class and go back to Unity.

Select the RestorePurchase button in MenuUI and search for Inspector. In the Button component, click on the small plus symbol to add the On Click () list. Left-click and drag the GameInfo GameObject onto the On Click () object field and then click on the drop-down list. Now, select GameInfo and then RestorePurchase.

Then, save the Unity scene.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset