18 min read

In this article by Edward Kyle Langley, author of the book Learning Unity iOS Game Development, we will learn that the player has the ability to send input to the device, and we will handle this by manipulating the player character GameObject. We also set up some game logic so that the player character can interact with positive and negative world objects, such as Coins and Obstacles. To further develop the sense of a complete game, we need to create the pieces of the game world that represent a floor that the player will run on.

(For more resources related to this topic, see here.)

To create these pieces, we will create a Unity EditorWindow class that will help us create grids that will represent the ground the player runs on and the dirt below it. Traditionally, you would have to place each sprite one at a time. With this editor tool, we will be able to crate bigger boxes in a grid based on our settings.

After we have our editor tool running, we will begin to create the prefabs that will hold multiple GameObjects and their components in a single file. Finally, we will write the code needed to move the floor and ground pieces below the player character, simulating the character as running forward.

To summarize, in this article, we will cover the following topics:

  • Writing a Unity C# class that extends EditorWindow, which allows you to input settings and sprite files that will give you a box grid and simplify the level pieces creation
  • Creating the game-related prefabs so that you have grouped files in an easy-to-use file
  • Building the main menu user interface with Unity’s UI tools, including buttons for achievements, leaderboards, and store purchases

Use the prefabs we made in the C# script. This will move the level pieces of prefabs under the player character, simulating movement.

We will also 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 at leaderboards /achievements, and have the option to purchase “remove iAds” for the cost of ten thousand coins or 99 cents.

Making the Sprite Tile Editor Tool

The Unity engine is incredibly flexible for all the aspects of game development, including creating custom editor tools to help fast track the more tedious aspects of development. In our case, it will be beneficial to have a tool that creates a root GameObject that will then create children GameObjects in a grid. This will be spaced out by the size of the sprite component they have attached.

For example, if you were to place say 24 GameObjects one at a time, it could take some time to make sure that all are snapped correctly together. With our tool, we will be able to select the X value and the Y value for the grid, the sprite that represents the ground, and the sprite that represents the dirt below the ground. Perform the following steps:

  1. To begin with, navigate to the Assets folder. Right-click on this folder and select Create and then New Folder. Name this folder Level.
  2. Right-click on the new Level folder and select Import New Asset.
  3. Right-click on the Script folder, select Create and then C# Script. Name the script SpriteTiler.

The SpriteTiler C# class

Double-click on the SpriteTiler C# file to open it. Change the file so that it looks similar to the following code:

using UnityEngine;

using UnityEditor;

using System.Collections;

 

public class SpriteTiler : EditorWindow

{

}

The big changes from the normally generated code file is the addition to using UnityEditor, changing the inherited class to EditorWindow, and removing the Start() and Update() functions.

Global variables

We now want to add the global variables for this class. Add the following code in the class block:

 

 // Grid settings to make tiled by

   public float GridXSlider = 1;

   public float GridYSlider = 1;

 

   // Sprites for both the ground and dirt

   public Sprite TileGroundSprite;

   public Sprite TileDirtSprite;

 

   // Name of the GameObject that holds our tiled Objects

   public string TileSpriteRootGameObjectName = "Tiled Object";

The GridXSlider and GridYSlider class will be used to generate our grid, X being left to right and Y being top down. For example, if you had X set to five and Y set to three, the grid would generate columns of five elements and rows of three elements or five sprites long and three sprites down.

The TileGroundSprite and TileDirtSprite sprite files will make up the ground and dirt levels.

TileSpriteRootGameObjectName is the GameObject name that will hold the GameObjects children that have the sprite components. This is editable by you so that you can choose the name of the GameObject that gets created to avoid having the default new GameObject for each one made.

The MenuItem creation

Next, we need to create the MenuItem function. This will represent the Editor selection drop-down list so that we can use our tool. Add the following function to the SpriteTiler class under the global variables:

  

// Menu option to bring up Sprite Tiler window

   [MenuItem("RushRunner/Sprite Tile")]

   public static void OpenSpriteTileWindow()

   {

    EditorWindow.GetWindow< SpriteTiler > ( true, "Sprite
       Tiler" );

   }

As this class extends EditorWindow, and the preceding function is declared as MenuItem, it will create a dropdown in the Editor named RushRunner. This will hold a selection called Sprite Tile:

Learning Unity iOS Game Development

You can name the dropdown and selection anything you like by changing the string that is passed into MenuItem, such as MyEditorTool or Editor Tool Name.

If you save the SpiteTiler.cs file and go back to Unity and allow the engine to compile, you will be able to click on the SpriteTile button under RushRunner. This will create a editor window named Sprite Tiler.

The OnGUI function

Next, we need to add the function that will be used to draw all the windows GUI elements or the fields that we will use to get the settings to make the grid. Under our OpenSpriteTileWindow function, add the following code:

 // Called to render GUI frames and elements

   void OnGUI()

   {

   }

OnGUI is the function that will draw our GUI elements to the window. This allows you to manipulate these GUI elements so that we have values to use when we create the GameObject grid and its GameObjects children with sprite components.

The GUILayout and OnGUI setup

To begin with the OnGUI function, we want to add the GUI elements to the window. In the OnGUI function, add the following code:

 

      // Setting for GameObject name that holds our tiled
         Objects

       GUILayout.Label("Tile Level Object Name", EditorStyles
         .boldLabel);

       TileSpriteRootGameObjectName = GUILayout.TextField(
         TileSpriteRootGameObjectName, 25 );

 

       // Slider for X grid value (left to right)

       GUILayout.Label("X: " + GridXSlider, EditorStyles.
         boldLabel);

       GridXSlider = GUILayout.HorizontalScrollbar( GridXSlider,
         1.0f, 0.0f, 30.0f );

       GridXSlider = (int)GridXSlider;

 

       // Slider for Y grid value(up to down)

       GUILayout.Label("Y: " + GridYSlider, EditorStyles.
         boldLabel);

       GridYSlider = GUILayout.HorizontalScrollbar(GridYSlider,
         1.0f, 0.0f, 30.0f);

       GridYSlider = (int)GridYSlider;

 

       // File chose to be our Ground Sprite

       GUILayout.Label("Sprite Ground File", EditorStyles.
         boldLabel);

       TileGroundSprite = EditorGUILayout.ObjectField
         (TileGroundSprite, typeof(Sprite), true) as Sprite;

 

       // File chose to be our Dirt Sprite

       GUILayout.Label("Sprite Dirt File", EditorStyles.
         boldLabel);

       TileDirtSprite = EditorGUILayout.ObjectField
         (TileDirtSprite, typeof(Sprite), true) as Sprite;

GUILayout.Label is a function that creates a text label in the window we are using. Its first use is to let the user know that the next setting is for Tile Level Object Name: the name of the root GameObject that will hold children GameObjects with Sprite components. By default, this is set to Tiled Object, although we allow the user to change it.

In order to allow the user to change it, we need to give them a TextField parameter to input a new string. We do this by telling that TileSpriteRootGameObjectName is equal to the GUILayout.TextField setting. As this is used in OnGUI, anything the user inputs will change the value of TileSpriteRootGameObjectName. We will use this later when the user wants to create the GameObject.

We then need to create two HorizontalSlider GUI elements so that we can get values from them that represent the X and Y values of the grid. Similar to TextField, we can start each of the HorizontalSlider elements with GUILayout.Label. This describes what the slider is for. We will then assign the GridXSlider and GridYSlider values to what the HorizontalSlider element is set to, which is one by default.

As the user adjusts the sliders, the GridXSlider and GridYSlider values will change so that when the user clicks on a button to create the GameObject, we will have a reference to the values that they want to use for the grid.

After HorizontalSliders, we want to have ObjectFields so that the user can search for and assign sprite files that will represent the ground and dirt of the grid. EditorGUILayout.ObjectField takes a reference to the object you want to assign when the user selects one, the type of object that ObjectField wants, and if ObjectField takes SceneObjects. As we want this ObjectField to be for sprites, we will set the type of object to typeof( Sprite ) and then cast the result that is assigned to TileGroundSprite or TileDirtSprite to the sprite by using as Sprite.

The OnGUI create tiled button

In order to know when the user wants to create the root GameObject and its grid of children GameObjects, we will need a button. Add the following code under the last GUI Elements:

// If butt "Create Tiled" is clicked

      if (GUILayout.Button("Create Tiled"))

     {

      // If the Grid settings are both zero,

       // send notification to user

       if (GridXSlider == 0 && GridYSlider == 0)

       {

        ShowNotification(new GUIContent("Must have either X or Y
           grid set to a value greater than 0"));

         return;

       }

 

       // if Dirt and Ground Sprite exist

       if (TileDirtSprite != null && TileGroundSprite !=null)

       {

        // If the Sprites sizes dont match,

       // send notifcation to user

        if (TileDirtSprite.bounds.size.x != TileGroundSprite.
           bounds.size.x || TileDirtSprite.bounds.size.y !=
           TileGroundSprite.bounds.size.y)

         {

           ShowNotification(new GUIContent("Both Sprites must be
             of matching size."));

           return;

         }

 

         // Create GameObject and tiled

        // Objects with user settings

        CreateSpriteTiledGameObject(GridXSlider, GridYSlider,
           TileGroundSprite, TileDirtSprite, TileSpriteRoot
           GameObjectName);

       }

       else

       {

       // If either Dirt or Ground Sprite dont exist,

         // send notifcation to user

         ShowNotification( new GUIContent( "Must have Dirt and
           Ground Sprite selected." ) );

         return;

       }

     }

The first condition we have set is the GUILayout.Button( “Create Tiled” ) function. The Button function will return true as soon as it is clicked on, but it will still render to the window if false. This means that although the button is not active, it’ll still be seen by the user.

As some settings will create a scenario that is not ideal for the concept of our SpriteTiler, we first want to make sure that the settings are in line with what we have designed the tool to perform.

We will first check whether GridXSlider and GridYSlider are set to zero. If both of these values are set to zero, the grid won’t create anything, and as the concept of the tool is to create a grid of children sprites, we will tell the user that they must have a selection above zero for either GridXSlider or GridYSlider.

We then check whether TileDirtSprite and TileGroundSprite have a value. If either of these values are null, the settings are not complete. This results in you telling the user that Dirt and Ground sprites need a selection.

If the user has set Dirt and Ground sprites to something, but their sizing is not the same, such as one being 32 x 32 and the other being 64 x 64, we will tell the user that both the sprites need to be of the same size. If we didn’t check for this, the grid wouldn’t align correctly, creating negative results and making the tool not function as we want it to.

If the user settings are in order, we will call the CreateSpriteTiledGameObject function and pass GridXSlider, GridYSlixer, TileGroundSprite, TileDirtSprite, and TileSpriteRootGameObjectName.

The CreateSpriteTiledGameObject function

This function is designed to take the user settings and create the grid from them. Add the following function under the OnGUI function:

// Create GameObject and tiled childen based on user settings

public static void CreateSpriteTiledGameObject(float
   GridXSlider, float GridYSlider, Sprite SpriteGroundFile,
   Sprite SpriteDirtFile, string RootObjectName)

{

   // Store size of Sprite

   float spriteX = SpriteGroundFile.bounds.size.x;

   float spriteY = SpriteGroundFile.bounds.size.y;

 

   // Create the root GameObject which will hold children that
     tile

   GameObject rootObject = new GameObject( );

 

// Set position in world to 0,0,0

   rootObject.transform.position = new Vector3( 0.0f, 0.0f, 0.0f
     );

 

   // Name it based on user settings

   rootObject.name = RootObjectName;

 

   // Create starting values for while loop

   int currentObjectCount = 0;

   int currentColumn = 0;

   int currentRow = 0;

   Vector3 currentLocation = new Vector3( 0.0f, 0.0f, 0.0f );

 

   // Continue loop until all rows

// and columns have been filled

   while (currentRow < GridYSlider)

   {

   // Create a child GameObject, set its parent to root,

    // name it, and offset its location based on current
       location

     GameObject gridObject = new GameObject( );

     gridObject.transform.SetParent( rootObject.transform );

     gridObject.name = RootObjectName + "_" + currentObjectCount;

     gridObject.transform.position = currentLocation;

 

     // Give child gridObject a SpriteRenderer and set sprite on
       CurrentRow

     SpriteRenderer gridRenderer = gridObject.AddComponent
       <SpriteRenderer>( );

     gridRenderer.sprite = ( currentRow == 0 ) ? SpriteGroundFile
       : SpriteDirtFile;

 

     // Give the gridObject a BoxCollider

     gridObject.AddComponent<BoxCollider2D>();

 

     // Offset currentLocation for next gridObject to use

     currentLocation.x += spriteX;

 

   // Increment current column by one

     currentColumn++;

 

     // If the current collumn is greater than the X slider

     if (currentColumn >= GridXSlider)

     {

     // Reset column, incrmement row, reset x location

       // and offset y location downwards

       currentColumn = 0;

       currentRow++;

       currentLocation.x = 0;

       currentLocation.y -= spriteY;

     }

 

     // Add to currentObjectCount for naming of

    // gridObject children.

     currentObjectCount++;

   }

}

To start with, we must first have the X and Y sizes of the sprite we want to create so that we can offset the location of the children GameObjects that were created. As we originally checked to make sure that both sprites are of the same size, it doesn’t matter which sprite object we get the size from. In our case, we will use SpriteGroundFile.

We will then move the rootObject position to 0X, 0Y, and 0Z so that it is in the center of our scene. This can be set to anything you like, although when rootObject and its children get created, it is easier to find it at the center of the scene world.

After it has been moved, we can set its name to the setting that the user had entered or Tiled Object (the default one).

Once we have rootObject set up, we can create its children GameObjects. To start this cycle, we will need a few variables to reference and change:

  • currentObjectCount: This specifies the total number of children that will be created. This increments for each one created.
  • currentColumn: This denotes the current column we are on in the row.
  • currentRow: This specifies the current row we are on.
  • currentLocation: This denotes the current location that the children GameObject will use and sets its position too. This is changed after each new child is created based on the X or Y setting of the sprite size.

Now that we have our rootObject and the variables we need to create the children, we can use while loop. A while loop is a loop that will continue until its condition fails. In our case, we will check whether currentRow is less than the GridYSlider value. As soon as currentRow is equal to or greater than GridYSlider, the loop will stop because the condition failed.

The reason we will look at currentRow is that for each column created, we can reset its value to zero and increment currentRow by one. This means that each row will hold as many columns as were set by the GridXSlider value, and we know that the grid is complete when currentRow is equal or greater than GridYSlider.

For example, if we had a grid setting of 3X and 3Y, the first row will hold three columns. When the first row is done, the row changes to two and adds three more columns. In the last row, it completes three more columns and then the while condition fails because the row value is equal to GridYSlider.

In each loop of the while loop, we start by creating gridObject. We set this grid object parent to that of rootObject, set its name to RootObjectName, and concatenate an underscore, followed by currentObjectCount and then set the gridObject position to the currentLocation value, which will change based on the size of the sprite and the column/row.

We will then add a SpriteRenderer component to gridObject and assign a sprite to it. We will change the sprite based on whether currentRow is equal to zero or not. If it is, in the first row, we will set the sprite to SpriteGroundFile. If currentRow is not equal to zero, we will set the sprite to SpriteDirtFile.

The ternary operator is a sort of shorthand for if → else. If the condition is true, we will set the value to what is behind the question mark. If the condition is false, we will set the value based on what’s behind the colon. The question mark represents if, whereas the colon represents else.

The ternary operator is as follows:

Value = ( condtion == true ) ? ifTrue : elseNotTrue;

Once we have the sprite assigned to the SpriteRenderer component of gridObject, we can assign a BoxCollider2D component, which will make itself the same size as the sprite. If we were to add the BoxCollider2D component to SpriteRenderer, it would be the default size of 1, 1, 1, which would be too big.

We will then offset currentLocation by the spriteX size, so the next gridObject will offset the size of the spriteX size.

The currentColumn value is incremented by one, and we then check whether currentColumn is greater than or equal to the GridXSlider value. If it is, we know that we need to start the next row.

To do this, we reset currentColumn to zero, increment currentRow by one, set the currentLocation.x value to zero, and offset currentLocation.y by negative spriteY size. This not only results in an offset location down, but also resets the X value to zero, making it possible for the columns to be created again; just down the size of spriteY.

Finally, we increment currentObjectCount by one.

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 MenuUI GameObject.

Name the buttons and text children as follows:

  • PlayButton, PlayText
  • LeaderboardButton, LeaderboardText
  • AchievementButton, AchievementText
  • RemoveAdsButton, RemoveAdsText
  • RestorePurchaseButton, RestorePurchaseText

Learning Unity iOS Game Development

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. 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:

Learning Unity iOS Game Development

Summary

In this article, we discussed how to create a Unity editor tool and a grid of GameObjects. These were laid out by the size of the sprites you chose and were flexible enough to use with your own settings. We also created prefabs for all of our bigger GameObjects, which could hold all of their components in a neat package.

We also covered the basics of how to create a game for iOS and utilize its GameCenter features. Feel free to explore these features and add to them. Adding more store purchases, achievements, and leaderboards is simply repeating the steps that we have already done.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here