Create stylish and modern tutorials in Unity games using video tips in Pop-Up windows
Tags: unity3d, tutorial, programming, gamedev
Published: 2024-04-02 18:54:41

Hi everyone, in today's tutorial I'm going to talk about **creating stylish tutorial windows for your games using video**. Usually such inserts are used to show the player what is required of him in a particular training segment, or to show a new discovered ability in the game.

## Creating Tutorial Database
**First, let's set the data about the tutorials. I set up a small model that stores a value with tutorial skip, text data, video reference and tutorial type:**
```csharp
// Tutorial Model
[System.Serializable]
public class TutorialData
{
    public bool CanSkip = false;
    public string TitleCode;
    public string TextCode;
    public TutorialType Type;
    public VideoClip Clip;
}

// Simple tutorial types
public enum TutorialType
{
    Movement,
    Collectables,
    Jumping,
    Breaking,
    Backflip,
    Enemies,
    Checkpoints,
    Sellers,
    Skills
}
```

**Next, I create a payload for my event that I will work with to call the tutorial interface:**
```csharp
public class TutorialPayload : IPayload
{
    public bool Skipable = false;
    public bool IsShown = false;
    public TutorialType Type;
}
```

## Tutorial Requests / Areas
Now let's deal with the call and execution of the tutorial. Basically, I use the Pub/Sub pattern-based event system for this ([you can read more about it in my other tutorial](https://dev.to/devsdaddy/methods-of-object-interaction-in-unity-how-to-work-with-patterns-and-connections-in-your-code-3fnh)), and here I will show how a simple interaction based on the tutorial areas is implemented.

```csharp
public class TutorialArea : MonoBehaviour
{
    // Fields for setup Tutorial Requests
    [Header("Tutorial Data")] 
    [SerializeField] private TutorialType tutorialType;
    [SerializeField] private bool showOnStart = false;
    [SerializeField] private bool showOnce = true;

    private TutorialData tutorialData;
    private bool isShown = false;
    private bool onceShown = false;

    // Area Start
    private void Start() {
        FindData();

        // If we need to show tutorial at startup (player in area at start)
        if (showOnStart && tutorialData != null && !isShown) {
            if(showOnce && onceShown) return;
            isShown = true;
            // Show Tutorial
            Messenger.Instance.Publish(new TutorialPayload
                { IsShown = true, Skipable = tutorialData.CanSkip, Type = tutorialType });
        }
    }

    // Find Tutorial data in Game Configs
    private void FindData() {
        foreach (var tut in GameBootstrap.Instance.Config.TutorialData) {
            if (tut.Type == tutorialType)
                 tutorialData = tut;
        }

        if(tutorialData == null)
            Debug.LogWarning($"Failed to found tutorial with type: {tutorialType}");
    }

    // Stop Tutorial Outside
    public void StopTutorial() {
        isShown = false;
        Messenger.Instance.Publish(new TutorialPayload
            { IsShown = false, Skipable = tutorialData.CanSkip, Type = tutorialType });
    }

    // When our player Enter tutorial area
    private void OnTriggerEnter(Collider col) {
        // Is Really Player?
        Player player = col.GetComponent<Player>();
        if (player != null && tutorialData != null && !showOnStart && !isShown) {
            if(showOnce && onceShown) return;
            onceShown = true;
            isShown = true;
            // Show our tutorial
            Messenger.Instance.Publish(new TutorialPayload
                { IsShown = true, Skipable = tutorialData.CanSkip, Type = tutorialType });
        }
    }

    // When our player leaves tutorial area
    private void OnTriggerExit(Collider col) {
        // Is Really Player?
        Player player = col.GetComponent<Player>();
        if (player != null && tutorialData != null && isShown) {
            isShown = false;
            // Send Our Event to hide tutorial
            Messenger.Instance.Publish(new TutorialPayload
                { IsShown = false, Skipable = tutorialData.CanSkip, Type = tutorialType });
        }
    }
}
```

**And after that, I just create a Trigger Collider for my Tutorial zone and customize its settings:**

![Video Tutorial Zone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xwlcomt10wvsev1p2125.png)

## Tutorial UI
Now let's move on to the example of creating a UI and the video in it. To work with UI I use Views - each View for a separate screen and functionality. However, you will be able to grasp the essence:

![Video Tutorial UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/adg7y9jzyw7y8v9v58ar.png)

To play Video I use Video Player which passes our video to Render Texture, and from there it goes to Image on our UI.

![Video Player Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilioupkna1k1rr3c8zhz.png)

**So, let's look at the code of our UI for a rough understanding of how it works** (Ignore the inheritance from BaseView - this class just simplifies showing/hiding UIs and Binding for the overall UI system)*:
```csharp
public class TutorialView : BaseView
{
    // UI References
    [Header("References")] 
    public VideoPlayer player;
    public RawImage uiPlayer;
    public TextMeshProUGUI headline;
    public TextMeshProUGUI description;
    public Button skipButton;

    // Current Tutorial Data from Event
    private TutorialPayload currentTutorial;

    // Awake analog for BaseView Childs
    public override void OnViewAwaked() {
        // Force Hide our view at Awake() and Bind events
        HideView(new ViewAnimationOptions { IsAnimated = false });
        BindEvents();
    }

    // OnDestroy() analog for BaseView Childs
    public override void OnBeforeDestroy() {
        // Unbind Events
        UnbindEvents();
    }

    // Bind UI Events
    private void BindEvents() {
        // Subscribe to our Tutorial Event
        Messenger.Instance.Subscribe<TutorialPayload>(OnTutorialRequest);

        // Subscribe for Skippable Tutorial Button
        skipButton.onClick.RemoveAllListeners();
        skipButton.onClick.AddListener(() => {
            AudioSystem.PlaySFX(SFXType.UIClick);
             CompleteTutorial();
        });
    }

    // Unbind Events
    private void UnbindEvents() {
        // Unsubscribe for all events
        skipButton.onClick.RemoveAllListeners();
        Messenger.Instance.Unsubscribe<TutorialPayload>(OnTutorialRequest);
    }

    // Complete Tutorial
    private void CompleteTutorial() {
        if (currentTutorial != null) {
            Messenger.Instance.Publish(new TutorialPayload
                { Type = currentTutorial.Type, Skipable = currentTutorial.Skipable, IsShown = false });
            currentTutorial = null;
        }
    }

    // Work with Tutorial Requests Events
    private void OnTutorialRequest(TutorialPayload payload) {
        currentTutorial = payload;
        if (currentTutorial.IsShown) {
           skipButton.gameObject.SetActive(currentTutorial.Skipable);
           UpdateTutorData();
           ShowView();
        }
        else {
           if(player.isPlaying) player.Stop();
           HideView();
        }
    }

    // Update Tutorial UI
    private void UpdateTutorData() {
        TutorialData currentTutorialData =
            GameBootstrap.Instance.Config.TutorialData.Find(td => td.Type == currentTutorial.Type);
        if(currentTutorialData == null) return;

        player.clip = currentTutorialData.Clip;
        uiPlayer.texture = player.targetTexture;
        player.Stop();
        player.Play();
        headline.SetText(LocalizationSystem.GetLocale($"{GameConstants.TutorialsLocaleTable}/{currentTutorialData.TitleCode}"));
        description.SetText(LocalizationSystem.GetLocale($"{GameConstants.TutorialsLocaleTable}/{currentTutorialData.TextCode}"));
    }
}
```

**Video recordings in my case are small 512x512 clips in MP4 format showing certain aspects of the game:**

![Tutorial Videos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivq39ryr6ajz3bnm2ftt.png)

**And my TutorialData settings stored in the overall game config, where I can change localization or video without affecting any code or UI:**

![Tutorial Database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/do8u40efeatn6ldgvuvj.png)

## In conclusion
This way you can create a training system with videos, for example, showing what kind of punch your character will make when you press a key combination (like in Ubisoft games). You can also make it full-screen or with additional conditions (that you have to perform some action to hide the tutorial).

**I hope I've helped you a little. But if anything, you can always ask me any questions you may have.**

**Thanks!**

---

[My Discord](https://discord.gg/a7cHDtfYbv) | [My Blog](https://devsdaddy.hashnode.dev/) | [My GitHub](https://github.com/DevsDaddy) | [Buy me a Beer](https://boosty.to/devsdaddy)