In the last part of this Knife Hit clone tutorial you learned how to create the rotating log and a single knife. This means that you have the core game mechanic working but the game is essentially still unplayable.

In this second and final part you are going to add good looking wooden particles, spawn knives, create UI and game over sequence. Let’s roll and stay tuned for more tutorials like this!

Do you prefer video tutorials?

Make the game come to life with particles

When it comes to games, you can never have enough particles! With Unity, adding particles is pretty easy. Select the Knife object from the hierarchy and add a new component Particle System.

Now that you have it in place, create a new material in the Assets folder and call it WoodParticleMaterial. Then select this material and set its properties as follows.

Next you will again select the Knife from the hierarchy and set a lot of things on the Particle System component. Most of them are self explanatory and all of the fields you need to set are highlighted.

Let’s start with the main properties. Disable looping, set start lifetime and so on. You can see that on the picture are two fields for Start Lifetime and also for Start Speed. To get them too, click on that little triangle next to them on the right and select Random Between Two Constants. As for the Start Color, use a color picker and pick the lighter color of the Log.

In the Emmision tab, set Rate over Time to 0. Create a new burst and the Count is again a Random Between Two Constants.

Set the Shape to be a circle with Arc of 160 degrees, Y position of 1 and Z rotation of 190.

For Size over Lifetime choose the downward linear line at the bottom.

Finally, drag the WoodParticleMaterial which you created a while ago into the Material field of the Renderer.

Make sure that all of the Particle System parts mentioned above are enabled. (Tick the box next to the part’s name.)

Creating a UI

Before you create a script which will spawn multiple knives and control the game logic, it’s not a bad idea to build the UI which will be shown to the player at different stages of the game.

To create the UI, right click on the hierarchy and select UI -> Canvas.

On this Canvas set the Render Mode to “Screen Space – Camera” and drag the Main Camera from the hierarchy over to the Render Camera field. You can also update the Canvas Scaler component as seen on the picture. This will ensure that the Canvas is responsive to the phone’s screen size.

Adding a restart button

First let’s add a button. Right click on the Canvas in the hierarchy and add UI -> Button. Give it a name ButtonRestart. You will come back to it at the end of the tutorial. For now, move it and style it as follows. 

This ButtonRestart has a child object called Text. Change it according to the picture. As you can see, the color of the button and of the text is picked from the Log sprite’s lighter and darker shades of brown.

With everything set correctly, the button should game should look like this:

Now select the ButtonRestart in the hierarchy and disable it by default. This button will be enabled only if the player loses the game.

Showing the count of remaining knives with icons

The player has to somehow know how many knives are remaining. In the original Knife Hit game this is done through icons on the left side. Let’s create just that in this replica.

Right click on Canvas and create UI -> Panel. Call it PanelKnives. Remove the Image component from it (right click -> Remove Component) and add a new component Vertical Layout Group. Now set the fields of the PanelKnives as on the image.

Make sure the panel is aligned to the left and stretches along the Y axis. The Vertical Layout Group will automatically stack the children of this panel one on top of another. We want precisely this for displaying the remaining count of knives. Changing the amout of knives in a level and then displaying them will be as simple as adding / removing a child of this panel.

Now right click on Panel Knives and create a new UI -> Image. Name it IconKnife. It may appear to not be affected by the Vertical Layout Group at first so just temporarily change its width and it should snap to the correct position. Then set the Source Image of the Image component to be the ic_knife sprite from the Art folder. Lastly set the Z rotation of the IconKnife to 45.

Next let’s create a prefab from this Game Object. First, create a new folder Prefabs under Assets. Then simply drag the Icon Knife from the hierarchy to this new folder. You will use this prefab to dynamically add multiple copies of it to the PanelKnives when the game starts. Now you can safely delete IconKnife from the hierarchy.

Scripting the UI

With all of the UI in place, let’s create a script which will control it. Under Scripts create a new C# script GameUI. It won’t do anything by itself, it’s rather an abstraction for the UI. You will use this script from another script GameController which you will create in a little moment.

using UnityEngine;
using UnityEngine.UI;

public class GameUI : MonoBehaviour {

    [SerializeField]
    private GameObject restartButton;

    [Header("Knife Count Display")] //header for organization purposes
    [SerializeField]
    private GameObject panelKnives;
    [SerializeField]
    //this will be set to the icon prefab
    private GameObject iconKnife;
    [SerializeField]
    private Color usedKnifeIconColor;

    //enable the restartButton game object
    public void ShowRestartButton()
    {
        restartButton.SetActive(true);
    }

    //add a number of iconKnife children to panelKnives
    public void SetInitialDisplayedKnifeCount(int count)
    {
        for (int i = 0; i < count; i++)
            Instantiate(iconKnife, panelKnives.transform);
    }

    //keeping track of the last icon representing an unthrown knife
    private int knifeIconIndexToChange = 0;
    //changing the color of the image to represent a thrown (used) knife
    public void DecrementDisplayedKnifeCount()
    {
        panelKnives.transform.GetChild(knifeIconIndexToChange++)
            .GetComponent<Image>().color = usedKnifeIconColor;
    }
}

When you have this script, create a new empty Game Object GameController at the root of the hierarchy. Drag this GameUI script on it and set it up by dragging children of the Canvas to their respective fields. IconKnife will be the prefab you created a little while ago and the usedKnifeIconColor will be white with an alpha of 100.

Awesome! Now it’s time for the last script – GameController.

The script to control it all

In Assets/Scripts create a new C# script GameController. Many of GameController’s methods will be called form other scripts (e.g. KnifeScript). GameController script is a simple version of a singleton. Explaining it is outside the scope of this tutorial BUT I have a separate tutorial on singletons in Unity so definitely check it out.

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

//this script cannot function properly without GameUI
//let Unity know about it with this attribute
[RequireComponent(typeof(GameUI))]
public class GameController : MonoBehaviour {

    //we can get this instance from other scripts very easily
    public static GameController Instance { get; private set; }

    [SerializeField]
    private int knifeCount;

    [Header("Knife Spawning")]
    [SerializeField]
    private Vector2 knifeSpawnPosition;
    [SerializeField]
    //this will be a prefab of the knife. You will create the prefab later.
    private GameObject knifeObject;

    //reference to the GameUI on GameController's game object
    public GameUI GameUI { get; private set; }

    private void Awake()
    {
        //simple kind of a singleton instance (we're only in 1 scene)
        Instance = this;

        GameUI = GetComponent<GameUI>();
    }

    private void Start()
    {
        //update the UI as soon as the game starts
        GameUI.SetInitialDisplayedKnifeCount(knifeCount);
        //also spawn the first knife
        SpawnKnife();
    }

    //this will be called from KnifeScript
    public void OnSuccessfulKnifeHit()
    {
        if (knifeCount > 0)
        {
            SpawnKnife();
        }
        else
        {
            StartGameOverSequence(true);
        }
    }

    //a pretty self-explanatory method
    private void SpawnKnife()
    {
        knifeCount--;
        Instantiate(knifeObject, knifeSpawnPosition, Quaternion.identity);
    }

    //the public method for starting game over
    public void StartGameOverSequence(bool win)
    {
        StartCoroutine("GameOverSequenceCoroutine", win);
    }

    //this is a coroutine because we want to wait for a while when the player wins
    private IEnumerator GameOverSequenceCoroutine(bool win)
    {
        if (win)
        {
            //make the player realize it's game over and he won
            //you can also add a nice animation of the breaking log
            //but this is outside the scope of this tutorial
            yield return new WaitForSecondsRealtime(0.3f);
            //Feel free to set different values for knife count and log's rotation pattern
            //instead of just restarting. This would make it feel like a new, harder level.
            RestartGame();
        }
        else
        {
            GameUI.ShowRestartButton();
        }
    }

    public void RestartGame()
    {
        //restart the scene by reloading the currently active scene
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex, LoadSceneMode.Single);
    }
}

Update KnifeScript to call methods on the GameController and Play Particles

All of the new lines you need to add are highlighted.

using UnityEngine;

public class KnifeScript : MonoBehaviour {

    [SerializeField]
    private Vector2 throwForce;

    //knife shouldn't be controlled by the player when it's inactive 
    //(i.e. it already hit the log / another knife)
    private bool isActive = true;

    //for controlling physics
    private Rigidbody2D rb;
    //the collider attached to Knife
    private BoxCollider2D knifeCollider;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        knifeCollider = GetComponent<BoxCollider2D>();
    }

    void Update () 
	{
        //this method of detecting input also works for touch
		if (Input.GetMouseButtonDown(0) && isActive)
        {
            //"throwing" the knife
            rb.AddForce(throwForce, ForceMode2D.Impulse);
            //once the knife isn't stationary, we can apply gravity (it will not automatically fall down)
            rb.gravityScale = 1;
            //Decrement number of available knives
            GameController.Instance.GameUI.DecrementDisplayedKnifeCount();
        }
	}

    private void OnCollisionEnter2D(Collision2D collision)
    {
        //we don't even want to detect collisions when the knife isn't active
        if (!isActive)
            return;

        //if the knife happens to be active (1st collision), deactivate it
        isActive = false;

        //collision with a log
        if (collision.collider.tag == "Log")
        {
            //play the particle effect on collision,
            //you don't always have to store the component in a field...
            GetComponent<ParticleSystem>().Play();

            //stop the knife
            rb.velocity = new Vector2(0, 0);
            //this will automatically inherit rotation of the new parent (log)
            rb.bodyType = RigidbodyType2D.Kinematic;
            transform.SetParent(collision.collider.transform);

            //move the collider away from the blade which is stuck in the log
            knifeCollider.offset = new Vector2(knifeCollider.offset.x, -0.4f);
            knifeCollider.size = new Vector2(knifeCollider.size.x, 1.2f);

            //Spawn another knife
            GameController.Instance.OnSuccessfulKnifeHit();
        }
        //collision with another knife
        else if (collision.collider.tag == "Knife")
        {
            //start rapidly moving downwards
            rb.velocity = new Vector2(rb.velocity.x, -2);
            //Game Over
            GameController.Instance.StartGameOverSequence(false);
        }
    }
}

Make it all work together

Drag the newly created GameController script to the equally-named GameController object. Also create a prefab out of the Knife game object – drag it into the Prefabs folder and delete the Knife from the hierarchy. Now update the fields on the GameController script. KnifeObject is the prefab.

Connect the ButtonRestart to the appropriate method. Select it and in the Button component add a new OnClick() listener. Drag the GameController game object to the Object field and select RestartGame from GameController script as the method.

Finally let’s do a quick fix for the Log. Set the Rigidbody‘s Collision Detection to continuous. This will detect Log-Knife collisions more precisely and as a bonus it will add a “bounce” animation to the Log when a Knife hits! (Not really an animation, it’s something internal with Unity, but it looks cool!)

That’s it! Play the game, experiment with different values for everything you can come up with, create multiple levels, randomly generate some new values… Your imagination is the only limit.

Conclusion

In this two part tutorial series you learned how to make a fully-functional Knife Hit replica in Unity. I’m sure you learned many new things you will use in your projects. If this tutorial helped you, share it with others. Thank you for reading and see you in the next post!

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter freelancer and most importantly developer educator, he doesn't have a lot of free time 😅 Yet he still manages to squeeze in tough workouts 💪

You may also like

Snackbar, Toast & Dialog in Flutter (Flash Package)

Search Bar in Flutter – Logic & Material UI

  • working fine on my unity engine but when i build for android and run on my device my knife not stuck on wood?
    kindly reply

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >