Create stylish and modern tutorials in Unity games using video tips in Pop-Up windows
Devs Daddy
Posted on April 2, 2024
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
}
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;
}
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 });
}
}
}
And after that, I just create a Trigger Collider for my Tutorial zone and customize its settings:
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:
To play Video I use Video Player which passes our video to Render Texture, and from there it goes to Image on our UI.
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}"));
}
}
Video recordings in my case are small 512x512 clips in MP4 format showing certain aspects of the game:
And my TutorialData settings stored in the overall game config, where I can change localization or video without affecting any code or UI:
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!
Posted on April 2, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 2, 2024