Welcome to Snookerina, a fast-paced arena shooter with snooker physics! Pilot a coyote cue ball and SMASH your way through waves of mecha livestock in brutal, gladiator-style combat. Pull off STYLISH TRICK SHOTS, rack up DEVASTATING COMBOS, and earn the attention of BLOODTHIRSTY SPONSORS hungry for a show. Only the most ruthless, most precise, and most entertaining will rise to the top. Do you have what it takes to become the ULTIMATE SNOOKERINA CHAMPION?
Snookerina was developed during my third year at DigiPen as part of a collaborative game project. I led a team of 3 designers and 10 technical programmers to create a fast-paced arena shooter built around snooker-inspired physics. As the director and design lead, I drove the overall design vision and guided the development of our custom game engine, shaping engine systems and editor tools to align with the specific needs of our production.
Building on the strong collaboration from BoneFire Mayhem, we reunited with many familiar teammates from both design and tech, confident in our ability to execute the same strategic approach. Our goal was to first develop a refined gameplay prototype in Unity, allowing us to rapidly iterate on mechanics and identify the essential systems needed for our custom engine.
Drawing from past experience, we understood that “recreating Unity” was an monumental yet achievable task, especially with a larger, more capable team and a careful consideration of scope. From the outset, our vision was clear, our goals aligned, and we knew exactly what it would take to bring Snookerina to life.
When conceptualizing our game, I established a set of core design requirements that the final idea needed to fulfill:
Leverage the 3D space meaningfully, avoiding 2D gameplay masked by 3D visuals
Deliver high-octane, kinetic gameplay that is easy to pick up but difficult to master
Feature a tight, highly replayable gameplay loop
Maintain a lean production scope by minimizing technical complexity
Through research and experimentation with various genres and mechanics, I discovered a compelling niche that met all of these needs: a physics-driven arena shooter with snooker-inspired movement and combat.
Arena shooter mechanics naturally emphasize spatial awareness, verticality, and dynamic positioning, all of which encourage full use of 3D space
Snooker-style movement and attacks provide an intuitive, familiar control scheme with a surprising amount of mechanical depth and room for mastery
The genre supports multiple game modes and iterative gameplay variations, enhancing replayability
From a production standpoint, using spherical characters and a snooker-table-like arena drastically simplified animation, rigging, and collision setups, allowing us to focus on gameplay over technical overhead
This concept struck the right balance between creativity, fun, and feasibility, making it an ideal foundation for our project.
During the prototyping phase, we developed three distinct prototypes, each with a focused purpose. By separating gameplay and art into individual tracks, we were able to iterate more efficiently, leveraging the team’s specialized design roles and skillsets.
Gameplay prototype (Unity): Focused on defining the core gameplay loop and identifying the engine features required to support it. This allowed us to quickly test mechanics and refine the overall player experience.
Visual prototype (Unity & Figma): Aimed at establishing the visual direction for characters, environments, and UI. It also helped us pinpoint the technical requirements needed for visual rendering and UI systems in the custom engine.
Production prototype (Unity): Combined finalized gameplay with visuals, audio, and game feel, implemented with production-ready code. This prototype served as the bridge between Unity and our custom engine, ensuring a smooth transition into full development.
Fun fact: The original gameplay prototype only lasted for two weeks before it had already fulfilled its purpose. Despite the short timeframe, it proved the concept was solid, delivering a surprisingly satisfying movement and combat system that laid a strong foundation for the rest of the game. A key factor to consider was the physics properties such as friction and bounciness of different physics materials. The scale of objects also mattered, directly influencing how speed, force, and gravity were perceived in-game.
A true-to-life 1:1 scale of a snooker table turned out to be far too fast-paced. Players would zip around uncontrollably, making precise movement nearly impossible.
Scaling the entire environment up by 8x drastically slowed things down, making movement much more manageable.
After several rounds of fine-tuning, I settled on a 4x scale that struck the right balance: it gave players enough control to navigate confidently, but still allowed for the possibility of losing control at higher speeds.
At the heart of Snookerina lies a unique core mechanic that combines precision cue ball striking with dynamic arena combat. Players can strike in a variety of ways, applying spin to maneuver their way carefully through the arena all while causing damage to enemies based on the force applied to them. Players can chain damage by knocking enemies into each other, pull off aerial assaults with jump shots, or even skillfully pocket enemies into portals for stylish eliminations. Additionally, a slowmo mechanic was added to provide players with the time to line up their shot, and a shockwave ability that charges up based on distance traveled. This prototype focused on refining that core loop, balancing skillful execution with chaotic fun.
Physics Based Combat
Spin-Dependant Rebound
Slowmo
Shockwave
This core system creates a gameplay experience that is easy to pick up yet deeply rewarding to master. Basic movement through aim-based striking is intuitive and immediately accessible, allowing new players to jump in and start experimenting. As they grow more confident, players can begin chaining jump shots to bunny hop across the arena, using momentum and spin to maintain fluid movement. Launchpads add another layer, encouraging players to position themselves strategically in the air for precision strikes or evasive maneuvers. These layered mechanics come together in the final boss fight, where players are challenged to apply all their skills in a high-stakes, high-speed showdown, showcasing how mastery of movement and timing separates the good from the great.
Aim Based Movement
Bunny Hopping
Aerial Positioning
Boss Fight
The character designs draw inspiration from the W-Engines of Zenless Zone Zero, merging industrial sci-fi aesthetics with bold, expressive silhouettes. Each mecha livestock enemy is color-coded according to the classic snooker ball palette, enhancing both visual clarity and thematic consistency. Models feature distinct shapes and details that allow players to quickly identify enemy types by silhouette alone. The environments and visual effects embrace a strong retrowave influence, echoing the neon-drenched style of The Finals to craft a striking, high-energy atmosphere.
Character animations were achieved by offsetting the UVs of their textures, allowing for expressive visual feedback without the need for traditional rigging or skeletal animation. Emissive elements were color-coded to reflect enemy health, serving as an intrinsic health indicator and eliminating the need for extra HUD elements. Combined with the natural physics-based movement, where characters roll, bounce, and collide dynamically, this approach delivered lively and readable character interactions while keeping production streamlined and efficient.
The UV offset technique was also extended to environmental elements, most notably the spectator stands. Rotating screens and spotlights enhanced the illusion of a bustling virtual gameshow without relying on complex animation systems. This consistent, lightweight approach helped maintain visual cohesion while reinforcing the high-energy, televised atmosphere of the arena.
During the prototyping phase, a recurring issue became apparent. Players consistently avoided using the launchpads placed around the map. The threat of falling off the edge made vertical movement feel too risky, leading most players to stay grounded and adopt safer, more conservative strategies. This went against the core vision of the game. I wanted to encourage players to test their limits and fully embrace the speed and chaos of the combat.
Snookerina’s fast-paced gameplay naturally makes precision and positioning more difficult at higher speeds. Mechanics like launchpads were intended to expand movement options and open up advanced tactics, but players often ignored them because the risk outweighed the reward. If they could defeat enemies safely from the ground, why take the chance on a jump shot?
To solve this, I designed a style system inspired by Ultrakill, aimed at rewarding bold, high-skill plays and encouraging players to take advantage of the full arena.
Style Points: Earned by performing specific actions when damaging enemies, with harder and more stylish actions granting higher points. Bonuses like multi-kills, multi-hits, and air combos stack for greater rewards.
Style Meter: Points feed into a style meter with rank tiers, granting stronger bonuses at higher ranks. The meter decays at a fixed rate, with a faster decay at higher ranks, and resets upon death.
Style Multiplier: Boosts points based on movement state. Faster movement, airborne actions, and interactions with map elements like launchpads increase the multiplier, maxing out at 400% when using launchpads until touching the ground again.
To implement this system, I track data from the player’s last shot, including position, shot power, spin, and enemies hit. Style actions are triggered via event calls, such as damaging enemies or scoring kills. Using this data, I award players with the bonuses listed below:
The style system feeds back into the core gameplay loop. Reaching higher style ranks charges the player’s shockwave ability faster, allowing for more frequent combos and momentum-building moments. The style meter’s decay rate increases as it fills, adding a layer of tension that encourages players to keep moving and stay aggressive. This dynamic structure naturally scales difficulty with the player's skill, pushing them to constantly improve and take more creative risks.
Ultimately, the style system turns underused mechanics into opportunities for expression and mastery. It motivates players to explore all aspects of the game, from vertical mobility to advanced combo routes, and turns Snookerina into a high-intensity experience where flashy plays are both satisfying and deeply rewarding.
FMOD has been a pleasure to work with, streamlining the process of iterating on audio events and implementing them across both Unity and our custom game engine. One of the key factors behind this smooth integration was maintaining proper documentation. We established a consistent naming convention for our audio events as follows:
Audio files: snake_case [category]_[thing]_[action][variant], exclude [thing] if its a shared sound effect
Eg: enemy_sheep1_hum1, boss_hit1
Events: PascalCase based on event type
Play2D: 2D Action events for playoneshot local sounds
Play3D: 3D Action events for playoneshot positional sounds
Loop2D: 2D Timeline events for looping local sounds
Loop3D: 3D Timeline events for looping positional sounds
Parameters: FULLCAPS
Eg: PlayerBallImpactPlay2D, EnemySheep1HumLoop3D
Coding: camelCase [action][EventType]
Parameters: FULLCAPS
Eg: PlayerBall.rollLoop2D
Our custom engine was designed to retrieve all audio events directly from FMOD bank files. This meant we could iterate on our audio freely in FMOD, tweaking, rebalancing, or restructuring events, before simply rebuilding and importing the updated bank file. Our engine ran these events through string checks, and having well-documented and consistently named audio events saved us from a lot of potential headaches during integration.
One important consideration was the placement of our audio listener. Since Snookerina is a third person game, we experimented with different listener positions to find the most natural sounding setup. Placing the listener directly on the character felt disorienting, while attaching it to the camera caused sounds directly behind the camera to be overly amplified. Ultimately, positioning the listener at a midpoint between the character and the camera struck the best balance, providing spatial audio cues that felt both immersive and accurate to the player's perspective.
FMOD parameters allowed us to scale audio events dynamically in response to in-game changes. For example, the audio event "AmbienceCrowdLoop2D" uses parameters such as "CROWD" and "CHEER," which are updated based on specific gameplay events like clearing a wave. The seek speed and velocity settings for these parameters help the crowd audio transition smoothly, creating a more immersive experience. Additionally, we used a global parameter called "TIMESCALE" that affects all sound events under the SFX group, allowing us to modify audio characteristics like EQ and frequency in real time based on the current in-game timescale.
Since we composed the music for the game ourselves, we had the flexibility to separate the reverb tails from both the intro and loop sections of our music tracks. This gave us more control over how the transitions felt. We noticed that letting the reverb continue after the intro ended helped preserve the atmosphere of the piece. By isolating that reverb and layering it into the beginning of the loop, we were able to create a seamless transition that retained the musical identity without relying on crossfades. Because the reverb tails differ between the intro and loop, we structured the music event into three separate tracks, as shown above.
Much of our custom engine development focused on enabling C# scripting within our engine. This was a crucial decision, as it allowed the scripting logic from our Unity-based production prototype to be easily ported into the custom engine with minimal friction. Having a deep understanding of our engine’s inner workings allowed me to bridge the gap between design and tech, facilitating smoother collaboration and more informed design decisions. The engine architecture is outlined in the diagram below:
In order to get C# scripting logic to work in our C++ engine, our Mono system acts as a bridge between C# scripts and engine systems and components. A script component can be added to entities, which can hold multiple script instances. Each of these instances inherit from a base MonoBehaviour script which contains the base callback functions of Awake, Start, Update, and FixedUpdate. These functions behave and are used like how they are used in Unity. Awake for initialization logic, Start for a one-time call function for game logic, Update for the bulk of the gameplay loop and FixedUpdate for physics and timestep-sensitive gameplay logic.
Scripts can inherit additional interfaces as needed, such as ICollisionHandler for its OnCollisionEnter, OnCollisionStay, and OnCollisionExit functions. Public variables can be declared to be viewable and editable in the inspector. Other script instances and marshalled components can also be declared, with them getting their references through the entity's entityID. Declaring them as public also makes them visible and assignable in the inspector, allowing scenes and prefabs to be setup with OOP style referencing in an ECS framework.
Additional scripting functions were also added to our engine for extra functionalities, such as using coroutines and tweening for transitional logic, allowing scripts to be written in a clean and readable manner.
Below are some examples of scripting usage and marshalling.
In this script, PeanutGallery, we want to be constantly rotating the "Screen" object, which is a child of the object that the script is attached to. The "Screen" object is referenced through its entityID 385, allowing the script to access the marshalled components under it.
In the PeanutGallery script, a public reference "screen" is declared, allowing it to be shown in the inspector and be assigned a reference in the editor.
In the Update loop, the screen's local Y rotation is rotated over time based on the declared scrollSpeed variable.
The Transform component has a property of localRotation, with getter and setter methods to call their respective marshalled internal call methods.
Back in the C++, the internal call functions then run the Transform component in our engine, rotating the entity's transform component.
In this function, the player performs a shockwave attack. We want to be able to get reference to all overlapping rigidbodies of a specified layermask before performing their relevant logic.
There are some overloaded functions, but essentially I'm passing in an array of integers for the specific layers I need, converting them into a bitmask integer, and then passing it over to the internal call. This saves some overhead running the check once with a layermask instead of running a check for each layer.
In C++, the internal call function runs within our physics system, handling the necessary logic and returning an array of entityIDs for any entities with rigidbodies overlapping the specified physics layer. This part is typically handled by our tech programmers specializing in their relevant fields, because I’m definitely not writing all that myself. Unlike Unity, our overlap functions only support sphere and box colliders, not mesh-based ones, but that’s perfectly sufficient for our use case.
Creating UI buttons that updates its colours for both text and image texture is a hassle even in Unity. In our custom engine, this functionality could have been handled either entirely on the backend or through scripting logic. I chose to implement it in C# scripting, where each button holds a reference to its current state and assigned color, managed by the button component itself. In our setup, UI buttons automatically update their visual state based on interactions like hover or click, and invoke OnClick and OnHover events that other scripts can easily subscribe to.
An interface is created to for button handlers.
A base button script is made, inheriting the button interface. The script calls the OnClick and OnHover events in its interface methods.
A text button script inherits the base button script, adding more functionality to change a referenced text component's colour when the button changes its state.
The interface method is called from the button component directly in C++, passing in the relevant parameters.
These events are invoked elsewhere in the render system, managing the different buttons shown on screen as well as their state management.
We originally set out to build three distinct levels, each with its own theme and boss encounter. However, during production, it became clear that this scope was too ambitious for our timeline. To stay on schedule, we made the difficult decision to narrow our focus by reworking the three planned levels into a tutorial, a main level with a boss fight, and an endless mode, all built within a shared map. One of the biggest cuts was Level 2, a pinball-themed arena featuring a drone boss inspired by IBIS from Armored Core 6. It was a visually striking design and a favorite among some members of the team, but ultimately had to be shelved. This section is dedicated to honoring what could have been, a small tribute to a level that never made it to the final game. Press F to pay respects.
The level was set in a fully enclosed arena filled with bumpers, launchpads, and large rectangular pockets. As a player's instability increased, they would take more knockback from attacks, eventually losing control and ricocheting wildly around the map, and eventually straight into the pockets. The boss was composed of twelve individual drones, each positioned and rigged to shift dynamically based on its attack patterns. It zipped rapidly around the arena, forcing players to stay constantly on the move and chase it down in order to land damage.
Snookerina was built on a strong foundation. Our core mechanic of snooker-inspired movement and combat proved both fun and unique from the very beginning. This early validation gave us the confidence to move forward with full production, knowing we had something special.
The project pushed us technically and creatively. It strengthened our ability to communicate across disciplines, rapidly iterate, and adapt when challenges arose. We learned how to pivot wisely, when to cut scope, and how to balance vision with practicality. Most importantly, Snookerina reaffirmed that strong design fundamentals paired with a clear creative direction can drive an entire production.
I’m incredibly proud of what our team accomplished. The lessons learned here will stick with me in every project going forward.
Damm all this talk about snooker mechanics and i still suck at it in real life...