Wednesday 24 August 2016

Zelda Style Life System Unity Tutorial – Part 4 of 10

Zelda Style Life System Unity Tutorial – Part 4



The end result of the full tutorial.


At the end of Part 4 we will have produced this (See image 1.0).
Image 1.0


Before you start you should know these things:

You should have gone through and completed ‘Part 3’ of this tutorial before going any further. If you find yourself not understanding some of the terminology or code of the tutorial I would also recommend going through the previous tutorial to get up to speed. I will leave a link to it below to go through at your leisure.


http://joseph-easter.blogspot.co.uk/2016/08/zelda-style-life-system-unity-tutorial17.html


If you follow this tutorial and find I am moving too fast or if you don’t know the things in the list above, I would recommend getting up to speed and then come back to this tutorial when you are ready.

Links to Parts 1 - 10 of this tutorial series:

Part 1

Part 2

Part 3

Part 5

Part 6

Part 7

Part 8

Part 9

Part 10


With this tutorial if you want to convert this into JavaScript by all means do so but it may be easier for you to follow this in C# and stick with the language of the tutorial to make it easier.

PROJECT RESOURCES: At the bottom of the post there is a download link to a zip file. This files includes all files needed to follow Part 4 and the completed project file of Part 4.

In this part we will work on finishing the foundations of our framework, after this we will add more functionality and refinements. We will:

  • Reformat some of our code to make it more reusable
  • Instantiate any number of images by parsing it an integer
  • Set a starting health
  • Instantiate a number of images at the beginning of a scene based on starting health

Step 1: – Reformatting our code

We need to a bit of house keeping before we go any further. Some of our code needs reformatting. Specifically how we instantiate our ‘lifeImg’ game object. At the start we instantiate it as we normally would. However, this is no good as we need to be able to instantiate it both on the first frame of the scene and when the player gains a life (or whenever you want to in your project).

If we were to keep this the way this is, it would be a lot of code to write every single time. Not only this, when you spawn the number of lives at the start you could have to copy and paste this multiple times, that’s just messy and inefficient.

We need to create a function for instantiating the ‘lifeImg’ game object, and place our code in there. For now we will call our function ‘
AddLives’.Code:

void Start ()
{
    GetScreenDimentions();
    SetImageSize();
    AddLives();
}

void AddLives()
{

    Image tempImg = Instantiate(lifeImg, 

                                transform.position, 
                                transform.rotation) as Image;
    tempImg.transform.parent = transform;
}



Then respectively add a reference to it in the ‘Start’ method. If we enter ‘Play’ mode, the behaviour of our code will not have changed, we’ve made it easier to read and more usable.


Step 2: - Instantiating multiple prefabs

At the moment with our code, the ‘AddLives’ method only instantiates one ‘lifeImg’ when it's referenced. This means every time we will have to reference the function every time we want to Instantiate a ‘lifeImg’. This in cumbersome, inefficient and produces redundant code. What we need to do is tell the method how many ‘lifeImg’ we want instantiated when we reference the function.

The way we need this to work is, we need to parse the function an int value of how much health we need to add (or how many prefabs to Instantiate). Then tell it to Instantiate one prefab per iterative loop until it has completed the for loop based on the value parsed to it.

First we need to add an int called ‘n’ for the number we want to parse to the function. Then create a for loop which starts at value ‘0’ and stops when it has reached value ‘n’. When this is done place the Instantiate and parenting code inside of the for loop.


Code:

void AddLives(int n)
{
    for(int i = 0; i < n; i++)
    {

    
    Image tempImg = Instantiate(lifeImg, 
                                    transform.position, 
                                    transform.rotation) as Image;
        tempImg.transform.parent = transform;
    }
}



As we want to check that our code works and at the start of the game we want to see we have a full bill of health we will parse a number to the ‘AddLives’ method reference in ‘Start’ function.
Code:

void Start ()
{
    GetScreenDimentions();
    SetImageSize();
    ResetGridLayoutProperties();
    AddLives( 3 );
}



Good stuff, it works! The best thing is the ‘GridLayoutGroup’ component has positioned all of the newly created prefabs for us so we do not need to code that.

For ease of use when designing your project we will make a slight alteration to this before we go any further. Rather than using an arbitrary number, we will create an int called ‘startingHealth’ and parse that to ‘AddLives’ in the start method. This way you can tweak this in the inspector, it will do exactly the same thing. (See figure 1.0)


Code:

public int startingHealth = 3;

void Start ()
{
    GetScreenDimentions();
    SetImageSize();
    ResetGridLayoutProperties();
    AddLives( startingHealth );
}

Figure 1.0

This is good but unlikely what you need, and not the effect we are after, with them descending horizontally. We need the starting axis to be vertical. This is quite a simple fix, we can fix this in the inspector or through code. We will do it through code. In the ‘ResetGridLayoutProperties’ we need to change ‘gridLayoutGroup’s’ starting axis to ‘Vertical’. (See figure 1.1)


Code:

void ResetGridLayoutProperties ()
{
    gridLayoutGroup = GetComponent<GridLayoutGroup>();
    gridLayoutGroup.cellSize = new Vector2(lifeImgWidth, 

                                           lifeImgHeight);
    gridLayoutGroup.startAxis = GridLayoutGroup.Axis.Vertical;
}


Figure 1.1


Good stuff. Now we have this pinned down we can move onto ‘X’ and ‘Y’ spacing which we will cover in ‘Step 3’.
Step 3: - Setting the Image Spacing

When we enter ‘Play’ mode our images appear on screen. However, they look like one long rectangle because we have not added our own image yet. In your project you have have alpha’ed out parts of your image around the sides (or you may not). This will give the illusion of spacing. However you may no have this or you may want more control over the spacing of these images.

Thanks to the ‘GridLayoutGroup’ component this is quite easy to do. You set it once at the start and it takes care of the rest when you Instantiate more images on the fly.

First we need to create two new float variables. One called ‘spacingX’ and the other ‘spacingY’. Then we create a new method called ‘ImageSpacing’ and instead of it having a ‘void’ return type we will use ‘Vector2’ instead. This is because the ‘GridLayoutGroup’ has a spacing variable called ‘spacing’. This is a ‘Vector2’ which is composed of two floats. In this method we want this function to return a type of ‘Vector2’ in order to assign it to the ‘spacing’ variable. To do this we create a temporary ‘Vector2’ called ‘spacingVector2’ and assign it as new ‘Vector2’ properties with it’s ‘X’ and ‘Y’ properties as ‘spacingX’ and ‘spacingY’. After this we return the new ‘spacingVector2’.


Code:

public float spacingX;
public float spacingY;


Vector2 ImageSpacing ()
{

    Vector2 spacingVector2 = new Vector2(spacingX, 

                                         spacingY);
    return spacingVector2;
}



There is one more thing to in this step, and that is to assign returned ‘spacingVector2’ to the ‘spacing’ variable of the ‘gridLayoutGroup’. In the ‘ResetGridLayoutProperties’ method we assign the ‘spacing’ variable the ‘ImageSpacing’ method and out job is done here. You may think this is strange as we are assigning a method to a type, but this is perfectly legal. The return type for the method can’t be void, and must return the same type you are assigning it to (example shown below). As long as you have a return type for the method, and it returns the same type you are assigning it to, everything is fine. It also saves you creating another variable at the top that will take up resources which you will only use once. (See figure 1.2)


Code:

void ResetGridLayoutProperties ()
{
  gridLayoutGroup = GetComponent<GridLayoutGroup>();
  gridLayoutGroup.cellSize = new Vector2(lifeImgWidth,
                                         lifeImgHeight);
  gridLayoutGroup.startAxis = GridLayoutGroup.Axis.Vertical;
  gridLayoutGroup.spacing = ImageSpacing();
}





Figure 1.2


Great, part four is finished. We have sorted out the X and Y spacing between the images and made the images spawn vertically in a row and not horizontally in a descending column.

We have learnt how to:



  • Reformat our code to easily instantiate images
  • Parse types to methods
  • Use parsed integer in a for loop
  • Instantiate an image multiple times with one method reference using a for loop
  • Change GridLayoutGroup starting axis to vertical via code
  • Set GridLayoutGroup spacing via code
  • Make a temporary Vector2 out of two floats and use it as a return type
  • Assign a method to a variable that has the same return type

In Part 5 we will learn how to set a maximum number of UI elements per row and create a new row under the last one if the max is exceeded. Click here for Part 5.
If you liked this tutorial leave a comment below telling me why you like it and share it with a friend who will find it useful. If you didn’t like it, please leave a comment below saying why.

If you would would like to see more of these tutorials, please leave a comment below. And if you want more of this particular tutorial say what you want to see more of in the comments.


Download resources and project files.

https://db.tt/chdHl92L


No comments:

Post a Comment