Create stylish and modern tutorials in Unity games using video tips in Pop-Up windows

Devs Daddy - Apr 2 - - Dev Community

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:

// 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
}
Enter fullscreen mode Exit fullscreen mode

Next, I create a payload for my event that I will work with to call the tutorial interface:

public class TutorialPayload : IPayload
{
    public bool Skipable = false;
    public bool IsShown = false;
    public TutorialType Type;
}
Enter fullscreen mode Exit fullscreen mode

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), and here I will show how a simple interaction based on the tutorial areas is implemented.

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 });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Video Tutorial Zone

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

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

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)*:

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}"));
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Tutorial Videos

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

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 | My Blog | My GitHub | Buy me a Beer

. . . . . . . . . . . . . . . . . . . . . . .