CONCEPT
Spark & Kling is a stylized couch co-op puzzle platformer for all ages, you and your friend play as:
-
Spark, the electric bug, that can possess different electronics, also carries baby sparklings to shoot at generators and magnets!
-
Kling, the metal gel blob, shoots the metal gel to put weight on objects in order to make them heavy!
​
Cooperation is key, you will need to work together and use their abilities to progress through the level:
-
Kling can shoot metal gels in a line which Spark can jump into and travel through the level!
-
Spark can shoot sparklings at Kling for him to do his double jump!
​
Overcome obstacles and puzzles by working together to gain your freedom!

GAME PILLARS
Symbiosis
The characters are designed with symbiosis in mind regarding the different mechanics and how they interact with each other
Co-op Puzzles
Co-operation is needed for completing the puzzles
Goofy
A goofy and fun feeling is instrumental to our game
CORE LOOP
ANALYSE
Check the interactable elements in the level
ACT
Use the two characters abilities to solve the puzzles
TEST
Understand what the different elements in the level do
PROJECT ONBOARDING
Spark & Kling began as a 4-week school project, but after receiving positive feedback from various mentors, it became my main side-project.
I am in charge of the re-design of the game's 3C in order to submit it to the Swedish Game Awards.


Understanding how to re-design the 3C
The first thing I did after joining the project was playtest the game and analyse the current state of the camera and character movement.
​
The camera felt shaky and out of place for the type of game. Furthermore, the movement of the two characters was nearly identical. As a result, neither of the two friends was characterised.
I researched the current state-of-the-art for similar games, taking into account the game inspirations and the ultimate goal in terms of 3C, and then pitched my ideas to the team.
After receiving approval from the Product Owner, I began the prototyping phase.
CAMERA PROTOTYPING AND IMPLEMENTATION
Camera changes
The game's first iteration used a simple Cinemachine Virtual Camera that followed the characters. After a thorough examination of the tool, I determined that a Cinemachine FreeLook Camera would be a better fit for the game.
​
The reasons behind this choice were:​
-
It gives the player more control over the camera in-game
-
Improved compatibility with other Cinemachine components

Previous state of Spark 3C

Previous state of Kling 3C

Current state of Spark 3C

Current state of Kling 3C
Camera obstacle behaviour
The obstacle behaviour was the second thing I worked on. I used the Cinemachine Collider component and tweaked different parameters to achieve the desired effect.
I wanted the camera to behave differently depending on the object it faced. To get this result, I created a Unity layer called Ignore Camera Collider.
Specifically, to get the right feeling:
-
Small and non-obstructive objects should not collide with the camera
-
Large and structural objects would make the camera zoom in when colliding

The gif above shows how the camera collides in the game. The camera initially moves behind a pile of post-it notes, and since that object is layered as IgnoreCameraCollider, the camera does not attempt to pan it. Instead, when the camera moves behind the dog toy (that, as intended, is not layered) it pans closer to Kling.
Camera environmental system
More cramped and contained areas (such as areas inside vents) were added during development. In these situations, the camera frequently collided with the walls or the vents.
​
As a result, I developed a system that, using invisible colliders, can change the rigs' distance of the cameras at runtime.

Camera environmental switch (zoom in)
The image on the right shows the invisible collider used to zoom in when the player enters the area.


How does the system work?
​
I created a clone of the main camera for each character, and the game switched between them as needed (for example, when entering the vents).
​
In addition, I made the system modular by exposing the rigs' variables in the editor. This allowed me to change the camera settings at runtime in different ways to fit the structure of the level.
​
The IsResettingToDefault toggle is used to reset the camera to its default settings.
​
I could have created a custom solution to address these behaviours but I preferred the built-in system of Cinemachine because:
-
Make the camera transitions smooth enough so the players don't notice (this was one of my main goals for the camera system)
-
Eliminate potential unknown issues with a custom solution
-
Time constraints of the project.
CHARACTER MOVEMENT IMPROVEMENTS
Character movement changes
I realised from my own playtesting and feedback from others that Spark's (the little bug) movement needed a lot more work.
The gifs below show the distinction between the two versions of the character. The old Spark was floaty, and the players didn't have a sense of control over their movement. Spark is supposed to be quick and agile. To achieve this, I worked on the speed and acceleration settings when on the ground, while I focused on the acceleration when in the air.

Old version of Spark movement

New version of Spark movement
I also iterated Kling's movement to emphasise the difference between the characters. The idea is that Kling should be slower, chunkier and more goofy.

Old version of Kling movement

New version of Kling movement
INPUT
IMPROVEMENT
Controller disconnection
When disconnecting and reconnecting controllers, Unity has two problems with co-op games:
-
It does not automatically change the input type and/or control scheme
-
It assigns one of the two characters to the reconnected controller at random.
​
I wrote a script that solves both issues.
Runtime sensitivity change
If the gamepad sensitivity settings were used for the mouse and keyboard, it would be nearly impossible to play because the sensitivity would be far too high.
​
I wrote a script that adjusts the camera's sensitivity based on the input (Mouse and Keyboard or Gamepad).
The script is executed not only when the game starts, but also when an input is disconnected or reconnected.