Iris

Restore the lost colors of the world as the final spirit of the forgotten lands.

2025 • 13 people
C++FMODMusic CompositionScripted EventsWorld InteractionsDouble buffered renderingInput System

Key Contributions

  • World interactions
    • Camera trigger zones
    • Cinematic events
    • Scripted events system
    • Checkpoints system
  • Double buffered rendering
  • Universal input system
  • User interfaces
  • Audio via FMOD
  • Music composition and implementation

Jump to Conclusion

Overview

Iris is a 2D platformer based off Sheepy: A Short Adventure, built in C++ with The Game Assembly’s in-house engine, TGE. We still used Unity for level design. I set up the project, built several of the game’s core systems, and composed all of the music.

You play as the spirit of vibrance who must restore the colors of the world. Each time you do, a new ability is unlocked!

World interactions

In order to ensure we fulfilled the requirement of having scripted events in this project, we added a bunch of different world interactions. Many small camera changes and scripted events are present throughout the game.

Camera trigger zones

I was really looking forward towards making our camera more than just an object which follows the player around. We all love when a game does cool stuff with the camera, and with this system, our level designers could easily adjust the camera’s position and zoom.

Camera Trigger options in the inspector

Cinematic events

In addition to the camera trigger zones, we also had so-called cinematic events. These triggers really only lerped the cinematic bars in and out of view, but it helped make the game feel more immersive!

Scripted events system

Lastly, We felt that simply moving the camera around and adding cinematic bars wasn’t enough to count as scripted events, so I built another system which let us create sequences in JSON. These sequences were also triggered by trigger zones in the world.

The scripted events system let us define a sequence of events which plugged directly into our event system. The files could get pretty verbose, but it allowed others to create new sequences without involving programmers! It was possible to play sound effects, change camera settings, and just about anything as long as there was an event for it.

Here’s an example from the JSON file for the falling sequence at the beginning of the game:

json
[
  {
    "event": "AudioParameterChange", "at": 0.0,
    "data": {
      "event": "Soundtrack",
      "parameter": "Lv1FakeFall",
      "value": 1.0
    }
  },
  {
    "event": "CinematicCommand", "at": 0.0,
    "data": {
      "speedFactor": 1.0,
      "targetHeight": 128.0
    }
  },
  
  // ...
]
Don't worry, Iris is fine.

Checkpoints system

Checkpoints are necessary to ensure players don’t have to replay an entire level when defeated. Like all other world interactions, checkpoints were also triggered by trigger zones. Each checkpoint has a set respawn position, so if the player dies after reaching a checkpoint, they would respawn at that position.

Iris will respawn on the red point if defeated

Double buffered rendering

Right before this project began, we had an assignment where we had to implement double buffered rendering in our engine. The way it works is by storing current render data in two separate buffers, and then swap at the end of each frame. Since it worked well and came with performance gains, we decided to use it for this project as well!

For this, I implemented a Barrier class, which allows us to mark threads as “finished”. When a thread reaches the barrier, it waits until all other threads are finished. It simply makes sure that gameplay and render threads only begin executing when a new frame begins:

Frame f0
Update
(no data)
Frame f1
Updateidle
Render f0
Frame f2
Updateidle
Render f1

We also implemented a thread pool to save on the overhead of creating and destroying threads every frame.

Universal input system

One of our requirements for this project was to have support for both keyboard and gamepad. The input system I made as part of an assignment was extended to also support gamepads. I really didn’t want to see a bunch of checks for different input devices everywhere, so I designed it in a way where you check for actions instead of specific buttons or keys.

c++
// Once action is registered, check like so:
if (UniversalInput::IsActionPressed(GameAction::Jump) == true)
{
	// ...
}

I built a new events-based input system in a later project!

User interfaces

Our UI system was built with a state stack, which made it easy to create and navigate different views. The state stack also triggered events on push, pop, focus & blur, which made it especially easy to set states and play sound effects when navigating.

We had to present our tech to the rest of the class, and were asked why we named it View Controller & Views instead of just “state stack”. Personally, I think the name reflects exactly what it does. It’s a stack of views, and the view controller manages it!

Since we didn’t have a visual editor, all UI had to be created via code. This means that we had to keep our interfaces simple and straightforward.

Not too shabby!

Music composition

If there’s one thing I dislike, it’s having to use stock music. It feels cheap and lazy, no matter how good the music is. So for this project, I decided to compose all the music myself.

The track for level 2 was initially meant to be for the cave level 1, but it felt too happy for a colorless cave. Due to time constraints, I didn’t have too much time to make a new track, so I quickly put together a simpler and darker track for level 1.

We also needed a track for the chase scene, which was our main scripted event. I just happened to have recently made a little DnB track while experimenting with some new plugins, and thought “this would go really well with the chase scene”. I showed it to everybody else, and they agreed, so we ended up using it!

I did have to make some small adjustments to make it feel more dynamic. For example, the original started with a long intro, but the chase scene starts quickly, so I had to cut that out. I also played around with FMOD’s effects to make loops less repetitive in case the player got stuck.

Chase scene, captured from in-game:
--:--

I also recorded some piano sounds for our menu navigation sounds, which made the menu feel a lot more polished.

Menu navigation sounds:
--:--


Conclusion

This project was fantastic to work on! The systems I built were really fun to make, and composing the music was a nice change of pace from programming. Making an entire game in C++ was a great experience, and I feel it allowed me to use my skills a lot more than if we had made it in an engine like Unity.

Denis
Codreanu