Momentum is a light puzzle platformer, set in a strange place where the technology is run on the forces of time. In order to sustain it, time has been harvested from several timelines. Due to this large harvest, the time continuum has started to fall apart.

It is up to you to to find the time vault and release time back into the world. You will face challenges as you traverse this strange world in your search for all of the locks which are keeping the vault closed. They will test your ability in timing and planning.

To your aid, you have a tool which can directly control the time energy the technology is run on. But the tool’s power is not unlimited, it can only control one object at a time, and due to its restrictions, you need to carefully time and plan in order to progress.

Act quickly, but not without consideration, as any misstep might bring you to your doom, and when you fail, so will everything that ever existed. As history crumbles, so will the future it supports.

What is this?

This game was a school project that I worked together with 11 other people on over the course of 8 weeks. I mainly worked with creating tools with and for our level designer.

The game idea wasn't fully fledged out until a couple of weeks into the project, this required the programmers in the group to work on features in a way where a change of mechanics or simmlar wouldn't invalidate our work. This lead to us creating all the features, mechanics and tools in a highly modular and event-based way. This way, our designers could play around with the features and use them in multiple ways to help them decide on the final goal of the game.

An example of a feature built this way was the Character Controller. We didn't have a 3D artist comfortable with animation in our team, this led us to make the game an FPS, this way our 3D artists could focus more on the aesthetic of the game rather than having to worry about animations.

The Character Controller was built entirely modularly. This meant that the designers could attach any function to any controller event in-engine. We built the character controller this way to allow for much faster iteration for the designers, it was also thanks to this that we could find the right type of gameplay for our game much quicker.

Here's how the Input worked in the Character Controller. It is entirely delegate-based, meaning that the designers could attach any function from any script on any object to the input of the player, making iteration much easier as the designers didn't have to write any code themselves.

 
    public class InputComponent : MonoBehaviour
    {
        public InputProfile inputProfile;
    
        public UnityEvent OnJump;
        public UnityEvent OnInteract;
    
        [Header("Triggers")]
        public UnityEvent OnPrimaryFire;
        public UnityEvent OnPrimaryFireDown;
        public UnityEvent OnPrimaryFireUp;
    
        public UnityEvent OnSecondaryFire;
        public UnityEvent OnSecondaryFireUpDown;
        public UnityEvent OnSecondaryFireUp;
    
        [Header("Axis")]
        public UnityEventVector2 OnLookAxis;
        public UnityEventVector2 OnMoveAxis;
    
        [Header("Other")]
        public UnityEvent OnPauseButton;
    
        void Update()
        {
            if (inputProfile.GetJumpButton()) OnJump.Invoke();
            if (inputProfile.GetPrimaryFireButton()) OnPrimaryFire.Invoke();
            if (inputProfile.GetSecondaryFireButton()) OnSecondaryFire.Invoke();
            if (inputProfile.GetPauseButton()) OnPauseButton.Invoke();
        }
    }
                    
                    
    public class InputProfile : ScriptableObject
    {
        public virtual Vector2 GetInputVector() => Vector2.zero;
        public virtual Vector2 GetLookVector() => Vector2.zero;
        
        public virtual bool GetJumpButton() => false;
        public virtual bool GetPrimaryFireButton() => false;
        public virtual bool GetSecondaryFireButton() => false;
        public virtual bool GetInteractButton() => false;
        
        public virtual bool GetJumpButtonDown() => false;
        public virtual bool GetPrimaryFireButtonDown() => false;
        public virtual bool GetSecondaryFireButtonDown() => false;
        public virtual bool GetInteractButtonDown() => false;
        
        public virtual bool GetJumpButtonUp() => false;
        public virtual bool GetPrimaryFireButtonUp() => false;
        public virtual bool GetSecondaryFireButtonUp() => false;
        public virtual bool GetInteractButtonUp() => false;
        
        public virtual bool GetPauseButton() => false;
    }
                    

Tools

As I said before, one of the main things I worked on in this project were different tools for the designers. One of these tools were the launchpads and their visualizer.

The green line represents how the player will fly after stepping on the launchpad. The target is movable in the editor and the fly-path updates in real-time according to the position of the target and the desired height set by the user. In this project we decided to make our own physically driven character controller, since the default character controller and physics component didn't work the way we needed it to. This also made it much easier to do the physics calculations for the launchpad. The maths behind figuring out what force to add to the character in order for it to follow the desired path was a traditional trajectory calculation. That looked like this:

 
    LaunchData CalculateLaunchData(Vector3 from, float gravity)
    {
        float displacementY = target.y - from.y;
        Vector3 displacementXZ = new Vector3(target.x - from.x, 0, target.z - from.z);
        float time = Mathf.Sqrt(-2 * height / gravity) + Mathf.Sqrt(2 * (displacementY - height) / gravity);
        Vector3 velocityY = Vector3.up * Mathf.Sqrt(-2 * gravity * height);
        Vector3 velocityXZ = displacementXZ / time;

        return new LaunchData(velocityXZ + velocityY * -Mathf.Sign(gravity), time);
    }
                
 
    struct LaunchData
    {
        public readonly Vector3 initialVelocity;
        public readonly float timeToTarget;

        public LaunchData(Vector3 initialVelocity, float timeToTarget)
        {
            this.initialVelocity = initialVelocity;
            this.timeToTarget = timeToTarget;
        }
    }
                

Where LaunchData is a struct containing initialVelocity and deltaToTarget which are used to update the characters position in the character controller.

Some of the other tools I worked on looked like this:

Lessons Learned

The biggest challenge we faced during this project was that we struggled "finding the fun" in the game, whilst also having somewhat a confilct of intrest amongst the members.

Why did we struggle finding the fun?

The main reason for this was the fact that we were building too many things in too many directions at once. Basically, we weren't completley on the same page with what the game was supposed to be. One example of this was how one of our designers really wanted to incorporate much more in-depth storytelling then what we had time for. This lead to one of our programmers spending almost a week at developing a fully fledged AI-dialouge system. The idea behind this was for the tool you use in the game to be a companion that sometimes talked to the player, but we simply didn't have enough time to fully develop this feature, and therefore had to completley scrap it.