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!
Top comments (0)