0
\$\begingroup\$

I'm trying to implement a simple cutscene system in my Unity game, preferably one that allows cutscenes to play at the start and end of each of the levels.

What I was imagining was pretty similar to A Hat in Time's 'storybooks' from it's time rifts, but full-screen and advanced with a single key. The images are simple and comic-like, and I was wondering how to implement such a feature since it feels like it's a bit different to Unity's pre-existing Timeline system, as there isn't many moving parts.

How do I go about approaching this problem? At least, I don't know how to get the project to a state where a person can press a key to turn to the next page (whether or not there's a visual transition to the next is optional) and once it's done, to start the actual level/scene. Do I still use the Timeline for this? I know I could potentially use an empty game management prefab in order to set up when to play the cutscenes, however.

\$\endgroup\$
  • \$\begingroup\$ This looks like a carousel control: a stack of sprites that reacts to input to navigate to the next sprite. How have you tried writing your input script for this so far? \$\endgroup\$ – DMGregory May 23 at 13:00
1
\$\begingroup\$

Here's a simple script that accomplishes an effect like the one in your video.

enter image description here

Put it on a UI object that's the parent container of your stack of images.

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;

public class CardStack : MonoBehaviour
{
    [Tooltip("What gamepad/keyboard button action ID should trigger the next card?")]
    public string advanceButton = "Jump";

    [Tooltip("Which image should be in front, starting from zero?")]
    public int cardToShow = 0;

    [Tooltip("How fast to flip cards (in cards per second)")]
    public float flipSpeed = 5f;

    [Tooltip("How many degrees should each card behind the top card rotate?")]
    public float rotationIncrement = -5f;

    [Tooltip("How far should the next card be offset from the top card position?")]
    public Vector2 fanIncrement = new Vector2(15, -15);    

    [Tooltip("Where should the top card fly to when we skip past it?")]
    public Vector2 flipAwayOffset = new Vector2(-100, 0);

    [Tooltip("What should happen when we're left with an empty stack?")]
    public UnityEvent OnFinishedStack;

    Image[] _images;    
    Vector2 _centerPosition;    
    float _currentCard;
    bool _hasFinished;

    // Call this to move to the next card - you can hook this up to a UI button.
    public bool TryAdvance() {
        if(cardToShow >= _images.Length)
            return false;

        cardToShow++;
        return true;
    }

    void Start()
    {
        // Collect all the images inside this parent, ordered from front to back.
        _images = GetComponentsInChildren<Image>();
        System.Array.Reverse(_images);

        // Remember where the lead image is.
        _centerPosition = _images[0].rectTransform.anchoredPosition;

        // Update the display of the rest of the stack.
        Layout();
    }

    // Check input, advance/animate if needed.
    void Update()
    {
        if(Input.GetButtonDown(advanceButton)) {
            TryAdvance();
        }

        // Check if we've reached our target card and can stop animating.
        if (Mathf.Approximately(cardToShow, _currentCard)) {

            // Is our target card the end of the stack?
            if (cardToShow == _images.Length && !_hasFinished) {
                _hasFinished = true;
                Debug.Log("Finished stack!");

                // Fire an event - this way you can trigger sounds, scene changes, etc.
                if (OnFinishedStack != null)
                    OnFinishedStack.Invoke();
            }

            return;
        }

        // We haven't reached our target card, so animate toward that position.
        // This gives a linear slide, which can look mechanical; you can use easing for more juice.
        _currentCard = Mathf.MoveTowards(_currentCard, cardToShow, flipSpeed * Time.deltaTime);
        Layout();
    }

    // Update the layout of the cards for the current animation frame.
    void Layout() {
        // Iterate over all cards in the stack.
        for (int i = 0; i < _images.Length; i++) {    
            var image = _images[i];

            // For the top card, t = 0. t = 1 for the next card, etc.
            // t < 0 means it's the card that's being removed from the stack.
            float t = i - _currentCard;

            // Fade out the cards we've removed from the stack.    
            var color = image.color;
            color.a = Mathf.Clamp01(t + 1f);
            image.color = color;

            var trans = image.rectTransform;
            // Rotate cards so the current card is upright, and later cards fan out.
            trans.localRotation = Quaternion.Euler(0, 0, rotationIncrement *t);

            // If this is the card we're removing, slide to the flipAwayOffset.
            // Otherwise, shift it slightly from the previous card to fan it out.
            trans.anchoredPosition = _centerPosition + (t < 0f ?
                Vector2.Lerp(flipAwayOffset, Vector2.zero, t + 1f)
                : Mathf.Pow(t, 0.75f) * fanIncrement);
        }
    }
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for the suggestion! (Apologies for the late response). The script you've written up seems quite good, except right now it doesn't appear to appear in the actual game when the scene is played. There's an existing UnityEvent you've placed in the script that can be called for when the stack is empty to let the game start playing, but I was wondering if there was anything to 'call the stack' in the first place? That is, making the stack of pictures appear on the screen since I'd like to have two sets - one for the start of a level and one for the end. \$\endgroup\$ – Cross May 27 at 10:19
  • \$\begingroup\$ Sure. Set the game object to inactive when you don't want to see it, and active when you do. \$\endgroup\$ – DMGregory May 27 at 11:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.