Friday 19 August 2016

First person melee combat

I believe it's time for some more combat mechanics. In this section, we'll see how we can create a simple but effective melee combat system for a first person controller. The idea is to create something that resembles the combat system you saw in Skyrim or other RPGs of similar fashion.

I am going to use the first person controller found in the standard asset, plus additional assets found here and a nice skybox downloadable here. As for our foe, I found this asset which suits our needs perfectly.

Setting up the scene

Start by creating the scene. I dropped the first person character controller on a plane, placed a skybox to make the level look pretty and finally position the axe as a child of the first person camera in the controller. This is what the game window looks like:



And below is a srceenshot of the hierarchy as it looks after placing these objects in the scene:


You can see that the axe model is placed as a child of the FirstPersonCharacter, which is where the camera is.

Place a capsule collider on it and set it to trigger. You don't need to be super accurate, something like this should do it:


Create now a new layer, which we can call Axe, and set the axe model to this layer.

Since we are here, let's drag the goblin model in the scene too. That particular model looks a bit too big, so I scaled it down to 0.45 for each axis in its transform. Once in the scene, you can remove the script attached to it and place a capsule collider on it and make it fit nicely around the model. Also, the rigidbody should be checked as isKinematic.


This is pretty much all we need in the scene now. The goblin model already comes with some animation, which we are going to script later.

We should now have the player, with the axe, and our goblin, and they both should collide with each other. However, this is our first problem:



When we get close to the goblin model, the axe passes through it. Unless this is something you want to have, Also, if we had to use the collider on our weapon and not set it to trigger, it could be a little problematic for the player to move around, as it might get stuck in other models when rotating and so on.

There are a couple of ways we can fix this: we could write a shader for the axe in order to have it rendered on top of everything, or we can use 2 cameras. We are going with the latter.

Create a new camera and attach it to the FirstPersonCharacter and set the transform to 0,0,0. Let's call it OverlayCamera. The camera should now be in the exact position as the first person camera that comes with the FPScontroller.Now, in the inspector for this camera, set the Clear Flags to Depth Only, the Culling Mask to Axe, and the Depth to 1.



Now, the other camera, which is a component of FirstPersonCharacter. Leave the Clear flags to Skybox, change the Culling mask by removing the Axe layer, and leave the Depth to 0.

At this point, the OverlayCamera is rendering only the axe (or anything on the Axe layer) and it always draws on top of the FirstPersonCharacter camera, because of the Depth parameter of value 1.


No matter how close we get to the goblin, the axe will always be drawn on top.

Axe animation

We should now create an attack animation for the axe. Honestly, I'm really not the best person to show you how to properly create an animation in Unity, I'm still trying to master the art myself. However, I will show you a GIF picture of the final result so you can have an idea of what I am trying to achieve:


This is the attack animation. I also created a couple of idle animation, you will see them in the playable scene. Later on, we will need to add animation events.

The animation controller for the axe is shown below:


There is a sub state machine which I use to trigger some random idle animations, not something we should be concerned about for our purpose. We should in fact focus on the Axe_Attack state, which holds the animation just created. This state can be triggered by Any State via the trigger Attack and will return to the Axe_Idle state once the animation has reached the end. And don't forget to remove Loop from the Axe_Attack animation.

Here are the inspector screenshots of the 2 transition states side by side:


We can now start writing some scripts.

Scripting time


Create a C# script called Axe.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Axe : MonoBehaviour {

 Animator anim;

 CapsuleCollider cc;


 // Use this for initialization
 void Start () {

  anim = GetComponent<Animator> ();

  cc = GetComponent<CapsuleCollider> ();
  cc.enabled = false;
 
 }


 // Update is called once per frame
 void Update () {

  if (Input.GetMouseButtonDown (0))    
   anim.SetTrigger ("Attack");  
 
 }

 public void ActivateCollider(int active)
 {
  if (active == 0)
   cc.enabled = false;
  else
   cc.enabled = true;
 }
  



 void OnTriggerEnter(Collider c)
 {
  if (c.gameObject.GetComponent<IAxeHittable>()!=null)
{                               cc.enabled = false;
    c.gameObject.SendMessage ("OnGetHitByAxe", 5);

 }
}
}

Pretty simple, after getting all our references we check in the Update function for any mouse click to trigger the attack animation.

The ActivateCollider method is called by animation events: when the axe is about half way extended, we activate the collider in order to trigger the collision. As the axe is animating back to idle position, we deactivate it.


This is necessary, otherwise every time we get close to the goblin it will register a collision, even if we are not attacking.

Also, we deactivate the collider immediately after hitting.

To detect whether the weapon has hit something we use interfaces. If the object collided with implements the interface IAxeHittable, we send a message to it, calling the method OnGetHitByAxe.

This is the interface:

1
2
3
4
public interface IAxeHittable {

 void OnGetHitByAxe(float hitValue);
}

Now, we can create a Goblin script which will implement the interface and attach it to our goblin model.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Goblin : MonoBehaviour, IAxeHittable {

 // Use this for initialization
 void Start () {
 
 }
 
 // Update is called once per frame
 void Update () {
 
 }

 public void OnGetHitByAxe(float hitValue)
 {
  Debug.Log ("Goblin got hit by Axe!");
 }
}

As you can see, we MUST implement the method OnGetHitByAxe, which we'll use for handling damage to the enemy. At this point, all we do is to print to the console.

Conclusion

This is the basic structure on how to create some simple melee combat mechanics. As always, this can be improved to become a much more efficient and flexible system, which can be as complex or simple as your project would require.

Example scene here

Thursday 11 August 2016

Landscape generation with Perlin noise

Unless you have been living under a rock, you have probably heard of a game called Minecraft.
I'm a fan of the game and I was always fascinated by the idea of creating an infinite, randomized world. Minecraft uses something called Perlin noise to procedurally create the landscape.

This type of noise was created in 1983 by Ken Perlin, who ended up winning an Academy Award for technical achievement for developing the algorithm.

Perlin noise can be use with different purposes. If you look at a texture image of perlin noise in black and white you will quickly notice that it looks like a heightmap. A height map is just a representation of the different heights of a terrain and it's useful too display mountain chains, abysses, valleys, ecc.

Because of this property, this type of noise becomes extremely useful when creating random terrain.

Luckily for us, Unity has already a function which enables us to use Perlin noise without having to re write the whole algorithm ourselves. The method is Mathf.Perlin(float x, float y).

We are now going to create a Minecraft-like landscape using cubes and Perlin noise.

First, create a simple cube and apply a green material to it. This is going to be our "grass" cube.



Save it as a prefab as this is going to be the basic building block for the landscape.

Now, the script. You will be surprised how easy it can be to achieve such thing. Create an empty game object and a new C# script called LandscapeGenerator and attach it to it.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class LandscapeGenerator : MonoBehaviour {

 public static LandscapeGenerator instance;

 public GameObject grass;
 public int mapSizeX = 50;
 public int mapSizeZ = 50;

 public float heightScale = 20f;
 public float detailScale = 20f;

 float seed;
 // Use this for initialization
 void Start () {

  if (instance == null)
   instance = this;  
 
  

  for (int x = 0; x < mapSizeX; x++) {
   for (int z = 0; z < mapSizeZ; z++) {
    float y = 0;
    GameObject g = Instantiate (grass) as GameObject;
    g.transform.position = new Vector3 (x, y, z);
    g.transform.SetParent (this.transform);
   
   }
  }
 
 }
 
 // Update is called once per frame
 void Update () {

 }
}

I think the script is pretty easy and at this stage it won't do anything special, it simply lays out a basic,flat map for us.

As the cubes are all 1 by 1 in size, the 2 variables x and z are incremented by one, so the blocks are going to be placed one next to each other without any gap between them or overlapping.

This is the result you should see:


Rather boring. Imagine if the whole world of Minecraft was like this.

You might be tempted to modify line 23 to add some randomness to the Y value in order to create height for the plane. Using pure random functions, the line could become something like this:


23 
float y = Random.Range (0, 20);

With the value of 20 being the maximum height possible for a cube.

However, this is pretty much what you are going to end up with:


I don't think you would be able to have players walking around this "world".

This is because pure randomness is simply not suitable for our purpose, and this is where Perlin noise come in. The randomness of this algorithm is smoother and will give us the effect we trying to achieve.

Let's modify that line again implementing the Perlin noise function:

23
int y = (int)(Mathf.PerlinNoise ((x + seed) / detailScale, (z + seed) / detailScale) * heightScale);

This is a standard implementation of the Perling function. As you can see, there is also a seed variable added to the x  and y parameters. If you omit that, every time you run the game you will obtain the same exact landscape. The Perlin function will return a value based on the 2 float parameters passed in. If such parameters are always the same, the value returned will be the same each time. The seed variable is defined as such:


seed = (float)Network.time;

This simply generate a random value which is then added to the 2 parameters.

I could go on and explain the purpose of the 2 parameters detailScale and heightScale, but I believe I will just confuse you, therefore, I created a simple scene which will let you see the effects that thos 2 variables have when used in our method.

Check it out here.

In the end, what you should see is something like this:


I think you will agree with me when I say that this looks much more playable than the previous one.

We can take things a bit further by adding different block types, let's say snow and sand.

Just create 2 other prefabs made of a cube and 2 materials, one yellow(ish) and one white.


We can then modify the Start function of our script using if statements to check the Y value returned by the Perlin function and instantiate a different block prefab accordingly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 int y = ()int(Mathf.PerlinNoise ((x + seed) / detailScale, (z + seed) / detailScale) * heightScale);
 GameObject g;

 if(y<=5)
 g = Instantiate (sand) as GameObject;

 else if(y>=(heightScale-5))
  g = Instantiate (snow) as GameObject;

 else
  g = Instantiate (grass) as GameObject;
    
 g.transform.position = new Vector3 (x, y, z);
 g.transform.SetParent (this.transform);

As you can see, all we do is to check the y value returned by the Perlin method and we instantiate different types of cubes depending on their Y position.

Now we have a more colorful landscape:


Just as a final touch, we could add some water. Create a water prefab cube, set its collider to trigger (so the player can "walk in it") and adjust its color with a slight transparency.

Then we can add the code to spawn water in the landscape. Please bear in mind that this is quick and dirty way to do it, just for the sake of having some water in our scene.

While referring to the previous script, I modified the code starting from line 4, like this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 if (y <= 5) {
   g = Instantiate (sand) as GameObject;
   g.transform.position = new Vector3 (x, y, z);
   int tempY = y+1;

   while (tempY < 5) {
    GameObject w = Instantiate (water) as GameObject;
     
     
    Vector3 pos = new Vector3 (g.transform.position.x, tempY, g.transform.position.z);
    w.transform.position = pos;
    tempY++;
   }

  }

All this does is to instantiate the water gameobject on top of the sand blocks. A very cheap way to add some water, but it will work for our purpose.

Here's the scene now:


Conclusion

This is a decent example on how to implement Perlin noise to create a random landscape. Minecraft uses the exact same technique to achieve the effect, and as you can see, it works quite well. We could expand on this (and perhaps, I will) by adding cave generation and digging system, something I might consider doing in the future. To conclude, I just added the FPS controller which you can find in the Standard Assets and I put a nice skybox (this one) so you can test the scene out here.

Thursday 4 August 2016

Pathfinding part 2: the map editor

This post is a sort of follow up of the previous one. I thought I would show you how I created the simple level editor to layout the map I used for the path finding tutorial.

This will involve editor scripting and, although is very simple, is a good demonstration on how we can manipulate the Unity editor to improve workflow and create tools.

Obviously, every project is different and will require a different approach. For my particular case, I needed to create a tile map which could be made of 3 different types of tiles: grass, sand and wall. I didn't want to start from an empty map, so the way I did it was to create an initial map of a certain size of all grass tiles and then place the sand and wall tiles. That was my idea and that is what I'm going to show you.

By creating a map editor we can then create many more maps very quickly without having to place each tile prefab one by one, which can be very time consuming.

There are 2 main scripts for this project: one is the Map script, which is the one that featured the GeneratePath method we saw in the last post. This script will only be in charge of laying out the tiles and it's the script we will reference in the second script: MapEditor. This simply overrides the inspector for the Map script and is in charge of all the editor stuff.

Before I show you the scripts, I will display a picture of the inspector:

Fig 1

I highlighted to sections of the inspector with 2 different colors for a reason you will see shortly.

So, this is the MapEditor script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[CustomEditor(typeof(Map))]
public class MapEditor : Editor
{
 Ray r;
 RaycastHit hit;
 bool md = false;

 string[] tiles = { "Grass", "Sand", "Wall" };
 int ind = 0;

 public override void OnInspectorGUI()
 {
  Map map = (Map)target;
  DrawDefaultInspector ();

  if (GUILayout.Button ("Generate MAp")) {
   map.GenerateMap (); 
   //SerializedObject obj = new SerializedObject(map.ti
  }
   
  ind = EditorGUILayout.Popup ("Choose Tile",ind,tiles);
 }

 void OnSceneGUI()
 {

  int controlID = GUIUtility.GetControlID (FocusType.Passive);

  r = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
 
  if (Physics.Raycast (r, out hit, 100, LayerMask.GetMask ("Tile"))) {
   
   
   if (Event.current.type == EventType.mouseDown) {
    GUIUtility.hotControl = controlID;
    md = true;

   }

   if(md)
    hit.collider.gameObject.GetComponent<Tile> ().ChangeType (ind);

  }

  if (Event.current.isMouse && Event.current.type == EventType.MouseUp) {
   GUIUtility.hotControl = 0;
   md = false;
  }


 }


}

Some of the code has been already explained in a previous post I made related to custom inspector. Line 1 and the inheritance from Editor on line 2 are necessary when creating a custom inspector.

Let's focus on the 2 methods in the script.

OnInspectorGui is overridden in order to draw a new inspector. Firstly, we get a reference to the script we want to redraw the inspector for, which is the Map script. The target variable is a built in variable that represents a reference to the object, which needs to be casted accordingly (line 13).

Then we draw the default inspector (line 14), which will display all the public variables that are highlighted in the yellow rectangle in Fig 1. And that's what DrawDefaultInspector() does, simply displays the inspector you would expect to see if you were not overriding it.

It is important to point out that, if you decide to override the drawing process with the method OnIspectorGui(), you MUST call the DrawDefaultInspector() function. If you omit that, this is what you will get:


You'll get nothing. Once you override that method, all the inspector drawing is handled to you.

Going back to the script, on line 16 I created a button which will be displayed in the inspector. By pressing the button, the GenerateMap() method in the Map script is called, which we are going to analyze next.

After that, on line 21 I created a pop up field which will let the user choose what tile to place. these 2 elements (button and pop up field) are visible in Fig 1 in the red rectangle.

The EditorGUILayout.Popup method will return an integer that represents the index of the item selected. As for the lists of selectable items, we pass an array of strings as parameter.

Before move to the next part, OnSceneGui(), let's quickly have a look at the GenerateMap() function in the Map script. I won't post the whole script as there are other things that are unrelated to our purpose, so let's see the method only.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 public void GenerateMap()
    {
  tileParent = new GameObject("Tiles");
  tiles = new GameObject[mapWidth, mapHeight];
  tilescale = grassPrefab.transform.localScale.x;
  levelEditorMap = new int[mapWidth, mapHeight];

  for (int x = 0; x < mapWidth; x++)
  {
   for (int y = 0; y < mapHeight; y++)
   {
    levelEditorMap[x, y] = (int)Tiles.GRASS;
   }
  }

        GameObject g = null;

        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {

  currentPosition = new Vector3(startingPosition.x + (float)(x * (tilescale + 0.05f)) , startingPosition.y + (float)(y * (tilescale + 0.05f)), 0);
  g = Instantiate (grassPrefab, currentPosition, Quaternion.identity) as GameObject;
  g.GetComponent<Tile> ().type = (int)Tiles.GRASS;
  g.transform.SetParent(tileParent.transform);
                g.GetComponent<Tile>().arrayX = x;
                g.GetComponent<Tile>().arrayY= y;
                g.GetComponent<Tile>().mapReferece = this;
  tiles[x, y] = g;

              
            }
           
        }
  Debug.Log ("Length now is " + tiles.Length);
  GeneratePathGraph ();
    
    }

I initialize all the objects I need first, and I iterate through the 2 dimensional array levelEditorMap and initialize each element to a grass type tile. The tileParent object is just a parent object for all the tiles that we are going to spawn when generating the map. The other 2 dimensional array tiles will contain the actual gameobjects, the "physical" tiles we see in the scene, whereas levelEditorMap is the "logical" part. Honestly, there is really no need to separate the two, but that's how the path generation logic was set up previously so I left it as it is.

From line 18 to 35 is where the generation occurs. the position of each tile is represented by the Vector3 currentPosition. For both X and Y axis I added a little offset of 0.05 which will place a little space in between the tiles so they can be easily distinguished from each other. Other thatn that, all I do is to instantiate a grassPrefab (a simple quad colored green), assign some references to the Tile script of each instantiated grass prefab clone and that's it. Simple enough.

This is what you will see once you press the "Generate Map" button:


You can see the parent object highlighted in the red rectangle in the hierarchy and the grass tiles instantiated in the scene. Remember, the game is not running, this happened in the editor.

Now we can go back to the OnSceneGui method of the MapEditor script.

The hot control id is taken as it will become usefull when we are placing the tiles. Because we are placing objects by clicking we don't want to select the just placed object in the inspector, otherwise we will loose the focus that is now on the object containing the MapEditor script. That means that every time we place a tile we will need to re select that object in order to place another tile because we have now selected the in the inspector the tile we have just placed. I know, confusing, try it yourself by commenting out lines 27, 35 and 46, see what happens.

On line 29 we raycast from the camera. I know that raycasting is usually done differently (Camera.main.ScreenToPointRay...), but when we are doing a raycast in the editor, that is how you proceed. It took me a while to find out and I was glad when I did.

Then I simply check that the ray we are casting is hitting the tiles (line 31). If that's the case and we are clicking (line 34), we set the boolean variable md (mouse down) to true. When that variable is true, the tile that is being hit by the raycast will change type (I will show that script next) according to the index of the tile chosen with the pop up field we saw earlier in the inspector. the reason why I used a boolean variable is that so I can keep the mouse button down and drag the cursor around in order to change the type of tile as I go, without having to keep clicking for each tile.

Last pieace of code, when the mouse is released, we let go of the hot control (line 45 to 48).

To confuse you even more, check the ChangeType method in the Tile script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void ChangeType(int t)
 {
  type = t;

  switch (t) {
  case (int)Map.Tiles.WALL:

   AssignColorToMaterial (this.gameObject, grey);
   originalColor = grey;
   movementCost = Mathf.Infinity;

   break;

  case (int)Map.Tiles.GRASS:
   AssignColorToMaterial (this.gameObject, green);
   originalColor = green;
   movementCost = 1;
   break;

  case (int)Map.Tiles.SAND:
   AssignColorToMaterial (this.gameObject, sand);
   originalColor = sand;
   movementCost = 15;
   break;
  }
 }

This method will change the type of the tile (color and movement cost) according to the integer passed. If you don;t know about movement cost and why it's there, please read the previous post about path finding.

One important thing to notice is how we assign the new color to the material. You would be tempted to simply get the renderer reference and change the material color as you normally would in a script, but you cannot do that in the editor. This will cause a material leaking error.

If you want to achieve this, you will need to do a trick. That trick is the AssignColotToMaterial function, which is displayed below:



1
2
3
4
5
6
void AssignColorToMaterial(GameObject g,Color c)
 {
  var tempMaterial = new Material(g.GetComponent<Renderer>().sharedMaterial);
  tempMaterial.color = c;
  g.GetComponent<Renderer>().sharedMaterial = tempMaterial;
 }

We basically create a temporary material, change its color and assign it to the gameobject. This step is necessary in order to to what we want.

That's all folks.

This is all probably very confusing and I was struggling myself to understand what is going on, however, as it always is very much the case in coding, is all about trying it yourself and getting your hands dirty.

I hope this post will help you getting started in editor scripting and creating an awesome custom level editor.