Constellations is a story-driven 2D-platformer, focusing on a cat trying
to find closure after passing away.
The GitHub repository can be found here.
In short, the player has 9 "lives", which function as a marker of progress; you lose a
life every time you get through a level, which represents the player character coming
to terms with a specific stage of grief. Your goal is to "accept your death", as the
player character was already dead by the time the game started
I've focused heavily on programming on this project, all the way from the
first iteration of the player controller script to having finished dialogue-,
data persistence-, and state machine functionality (with adjustments coming to
the state machine to change it into a hierarchical system). I've also added
enemy NPC functionality, with player tracking and somewhat intelligent climbing
and jumping behaviors.
The movement system is also the most complex I've written so far, with a lot
of adjustments to make it as smooth of an experience as possible for the player.
It's implemented with the "new" Unity Input System, which should result in
an easier time with possibly building the game for multiple platforms down the
line. The project also makes use of Unity's Cinemachine package, enabling
complex camera functionality with relatively low effort.
There have been a LOT of changes since the first devlog, but we'll start off from the missing player
states; the player now has attack states, a scream state, and a few other ones too that were missing
previously. All enemies have also been refactored to be based on the StateMachineCore base-class.
Our artist also did a lot of work on the UI side of things, and she added the first few levels to
the game, which then needed some logic to be added. I first added logic to our "star room", which
keeps track of the player's progress. As you complete levels and "lose lives", that progress is
then reflected on the sky of the "star room", where your lost lives turn into stars in the constellation.
The logic was fairly simple to add in practice, but required some fine tuning with the SceneTransition
script, which I started work on next.
I figured I'd tackle scene transitioning by fading in a black UI cover, and fading it out after a scene
has finished loading. But that approach felt a bit "too obvious" to me, so I decided to ask for some
advice from our teachers, who told me to check out Unity's async operation system. After some investigating,
I implemented asynchronous scene loading, which made the transitions smoother, and allowed some
more precise management of the scene loading. I also created a SceneTransition prefab to aid with any
future scene transitioning needs, as you can simply drag a ScriptableObject into the prefabs's script,
and the prefab will handle loading your desired scene, along with setting the background music, reducing
a star from the player if the past scene was cleared for the first time and everything else that needs
to be done.
Our first level didn't need too much logic, as it was more of a tutorial level. But a tutorial level
does need tutorials, so I whipped up a prefab, which fades out after touching its trigger collider.
The collider's position and the text are easily customizable, and I set an editable string on the
script, which saves a value to indicate if the tutorial has been faded out once or not. I also added
a button to the main menu to reset all the tutorials, which would ease future demonstrations of the game.
Our lead also thought it'd be good to have some actual gameplay elements in our demo builds, and I got
tasked with implementing a gate and a key-system for said gates. I decided to go for a very simple route
with regard to the keys, simply adding a string to an "inventory" list in the PlayerController-script
when the player colliders with a key's triggerbox. After the key with the string corresponding to a gate's
key-string has been picked up, the player can open the gate by interacting with it. There'd be many other
simple additions later, but those are still in the planning-phase.
I also refactored, optimized, and fixed a TON of problems, like:
For now that's about everything I've done with regards to programming, but to finish off this devlog, I'll
talk a bit about our Bit1 venture!
Bit1 is a competition, where students pitch their game projects, and the winner gets a pretty hefty prize.
Our team lead decided to enter our project, and that kind of lit a fire under the development progress. In
a very short amount of time we got 2 levels to a playable state, got testing done by two entire classes of
XAMK Game Technologies students (thank you to Oiva Kahri for making that happen!), and added most of the
things described in this devlog. The event was still a fairly stressful prospect, as neither of us had ever
really pitched anything before. Either way, we got our pitch ready, our demo ready, and a video of our game
ready, and went in expecting the worst (or at least I did). There were 4 other teams in our side of the event,
as there were so many attendees that the event was split into two separate ones. We went first, and our pitch
went about as well as it could have! But, the event also had other incredibly talented people, and so our Bit1
journey was cut short. We still learned a lot, got to talk to the obvious winners and received a load of
good tips, and got a decent score overall, even when our project was still in fairly early stages of development.
We'll probably go back next year and give it another shot!
The most important component of any platformer is the movement system, so that's where
development began. I first implemented a simple system, which could be expanded later,
and after being prompted by the Unity engine to use the new Input System, I decided to
give it a try. After a short period of banging my head against a wall, I managed to get
it implemented in a way where I could easily use it for all player movement and actions.
Using the Input System also means that player inputs are easily swappable from gameplay
controls to UI controls, leading to much less effort on disabling certain inputs at
appropriate times.
The first iteration of the movement system was fairly rudimentary, using simple tech to
set a new player position using the attached rigidbody2d. The idea behind this was to give
the player a feeling of responsiveness, but it resulted in the animations seeming much too
choppy and abrupt. This iteration still had all the frills though, including jumping,
climbing, dashing, running and crouching.
Around this point I implemented a smoother camera system, using Unity's Cinemachine as its
soft zone feature was exactly what was requested of me. The camera system was heavily
inspired by the game Hollow Knight's system, though it isn't nearly as polished at this
point in time. I also implemented a simple multi-purpose prefab to give the level-builder
an easier way to add camera panning or swapping to the level (originally this was meant to
simply be a down-pan around ledges, but I figured we might have similar needs later).
I also added a small lerp on the camera while the player is falling, to give the falls
slightly more juice.
Then came time to redo the entire movement system using AddForce-based solutions. We wanted
to keep the feeling of responsiveness while still adding flair, so I added high acceleration
to the player, while capping the speed at a reasonable number. This resulted in a system with
high responsiveness without the choppiness that came with setting the speed to max right away.
The whole movement system revamp was relatively quick to add, so I quickly moved on to the next
seemingly daunting task of creating a dialogue system.
While I was scratching my head trying to figure out how to add a dialogue system in an
efficient manner while still making it easy for the writer I came across ink - a scripting
language quite literally built to make adding dialogue as easy as possible. Implementation
was a breeze thanks to a Unity package built for that very purpose, and I quickly had JSON
files to parse dialogue from, with choices to boot. Getting all this on screen took a short
while, but eventually I had a fully working dialogue system, and our writer wouldn't have to
deal with Unity, or even a single JSON file. While adding the dialogue system, I cobbled
together a universal interaction system to go alongside it, which enabled running scripts on
virtually any object with the proper tag (this will probably be changed to use an interface
later).
And now was time to add enemies to the game. For now there'd be two: a ghost that can pass
through walls, and a skeleton with the ability to climb and jump. It took a while to add
proper player tracking that wouldn't have to run on every update for optimization reasons,
but I eventually got everything working. The rules for the skeleton's climbing were probably
the biggest timesink, and I figured I'd have to build up a state machine system that could
be used by all characters or this would keep getting worse.
Back to the present, there now exists a functional state machine, though it's only used by
the player for now. It encompasses all current and future player states, but it's not exactly
usable by NPCs for now as it lacks hierarchy, and the majority of the logic is still baked into
the PlayerController script. So for now the agenda is to make the state machine system
hierarchical, and usable by every character, player or not.
Also thanks to Sasquatch B Studios and AdamCYounis on YouTube for their excellent guides, they were a great help when developing many of the systems mentioned above for the first time.