It was actually my first time dealing with cutscenes, so I had little idea how this works. Luckily, a great YouTube channel Night Run Studio (mentioned in the previous post) had some tutorials on this topic. By following their videos I set up Cutscene Handler, Cutscene Initiator and some cutscene elements responsible for Zoom, Pan and Dialogues. So, I learned that a cutscene system in Unity can be implemented by just calling scripts of each cutscene element added to the parent Empty Object in order. You basically need to add Cutscene Handler and Initiator to set up the base functionality, and then add children representing exact things you want your cutscene to do (pan, zoom, pop up dialogue).
There are a few things that I have added myself (mostly) to make this cutscene system a better fit for my game.
This part was not mentioned in the tutorials, but I really like this effect when text is printed letter by letter rather than all at once. This effect is really easy to implement, and a lot of resources online explain how to do it. The idea is to print characters after at certain interval. In order to print faster than one character per frame, you can keep constantly checking if during this frame enough character have been printed already and suspend the function to wait for the next frame. I am using the following code. More explanation you can find here.
IEnumerator TypeTextUncapped(string line) { finishedTyping = false; float timer = 0; float interval = 1 / charactersPerSecond; string textBuffer = null; char[] chars = line.ToCharArray(); int i = 0; while (i < chars.Length) { if (timer < Time.deltaTime) { textBuffer += chars[i]; popUpText.text = textBuffer; timer += interval; i++; } else { timer -= Time.deltaTime; yield return null; } } finishedTyping = true; }
I also added a possibility to skip the typing by pressing the "Interact" key. This can be easily implemented with a flag variable, that is false originally and should be set to true if the typing subroutine finished. The "Interact" key can also be used to pass to the next cutscene element if the entire text is already displayed.
if (Input.GetButtonDown("Interact") && isActive) { if (!finishedTyping) { StopAllCoroutines(); finishedTyping = true; popUpText.text = dialogue; } else { // continue to the next cutscene element anim.Play("dialogue_box_disappear"); popUpText.gameObject.SetActive(false); cutsceneHandler.PlayNextElement(); } }
I wanted to prevent the player from moving while the cutscene is playing, so I used the similar approach: a flag variable to make the player ignore keyboard inputs. I made two functions that can set it to true or false (plus some extra things, which are not so important for now). Pretty simple. But the problem is that player scripts are not aware of when the cutscene is starting. One approach was to create a reference to the Player_Movement script in the Cutscene Handler component, and then drag and drop the player there. This way, I would be able to call the necessary Player_Movement function from the Cutscene Handler script. But... Anyone who has ever studied programming will say that this is not the best way to do it, because we should keep our objects as independent as possible, this makes the system more modular and easy to handle. So, I had another solution: Observers.
Observer is a well-known design pattern, that can reduce coupling (coupling is when objects rely too much on each other, they are not independent), there are plenty of articles about it that you can read, I won't dive into that too much, I will just describe how my variation works.
Observers in Unity can be implemented using delegates, which are "function containers". You create a delegate and add functions to it (literally by using "+" operator) and when you invoke it, it will call all those functions. The best part is that if we make a delegate static and public, it allows us to add functions from other scripts to this "container" without creating direct references between the scripts. That's really convenient.
So, I created two delegates in my Cutscene Handler script:
public delegate void CutSceneStarted(); public static CutSceneStarted cutSceneStarted; public delegate void CutSceneEnded(); public static CutSceneStarted cutSceneEnded;
Then, in Player's script, I added the following lines to the Start function:
CutsceneHandler.cutSceneStarted += DisableMovement; CutsceneHandler.cutSceneEnded += EnableMovement; CutsceneHandler.cutSceneEnded += setCameraFollow;
The first two lines are responsible for enabling and disabling the movement and the last one set the camera follow back to player (during cutscenes camera moves, so no longer following the player).
Finally, I invoke these delegates when I need to (not necessarily from the Cutscene Handler script). I find this to be quite an efficient solution, and I am happy with how easy it is to modify it.
The final funny part is that I used AI to paraphrase the dialogue text to make it sound like a knight would say it. It turned out pretty cool, it added old-fashioned English words, which I didn't know existed. Because English is not my mother-tongue, I would never be able to write it myself and letting knights speak like dudes from the 21st century is kinda awkward. AI was very useful in this small task of adding "flavour" to the text.
That seems to be all for my additions.
At this point, I am pretty much finished with this "Tutorial" level 1. Next I will be working fixing some minor issues (for example, camera jiggles when setting follow back to player) and adding new features. In later levels, I am planning to add the mechanic of recruiting other knights so that they can follow the player and help in battle.
That's all for now. If you have any ideas to share, or you found any mistake, please let me know! If you are also learning Unity, good luck on your journey!
Did you like this post? Tell us
Leave a comment
Log in with your itch.io account to leave a comment.