Enemies were designed with multiple behaviours: patrolling when unaware, chasing when the player was visible, attacking when in range, and checking their surroundings if the player was lost. These state changes were handled through Unity’s NavMesh system and custom C# scripting, forming the foundation of the game’s stealth mechanics.
University – Second Year
This project was made in my second year of university.
-
AI Detection and Player Search
To make the AI more dynamic, I implemented a “last known position” mechanic. When enemies lost sight of the player, they would move to the last location, rotate 360 degrees, and only return to patrol if the player was not found. Early versions had issues with spinning too fast, which I solved by adding a rotation speed multiplier.
-
Enemy Communication System
Enemies could alert each other when spotting the player. Initially I attempted a constant update check across all enemies, but this was inefficient. I replaced it with Unity’s physics overlap sphere, which collected all nearby AI within a radius and called their listening function, making enemy collaboration smooth and efficient.
-
Player Combat and Stealth Mechanics
The player could engage enemies directly or attack from behind for double damage, encouraging stealthy play. This combined with enemy patrols and communication created a layered challenge where players had to balance speed with strategy.
-
Persistent High Scores Across Scenes
The game included a timer for players to complete the level as quickly as possible. To carry times across scenes and display best scores on the main menu, I implemented a singleton object that preserved variables between scene loads.
-
Unity Version Control for Cross-Device Development
During development I had to switch between PC and laptop, which created issues transferring files. Using Unity’s version control system solved this, allowing me to reliably sync project changes and avoid data loss.
-
Planetary Simulation & Camera System
The simulation features nine planets and their moons orbiting around a scalable sun. To make the system explorable, I implemented two camera perspectives: a static overhead view and a first-person camera that can be controlled with WASD input. The first-person camera made use of Euler angles and custom vectors from my maths library to handle rotation and movement. A switching system allowed the player to move seamlessly between the two perspectives, making it easier to observe the orbits both as a whole and from within the system itself.
-
Programming & Maths Implementation
The project required a custom maths library, which I built to include vectors, 4×4 matrices, trigonometry functions, and quaternions. I created a generalised transform function that could apply scale, rotation, and translation to objects in a single step, which became essential for calculating planetary orbits. Each planet used the same script, made editable in the Unity inspector through the use of [System.Serializable], which allowed me to efficiently customise parameters for each body. Planets orbited the sun using quaternions and modified vector rotation formulas taken from lecture material, while moons reused the same system with added retrograde functionality and bug fixes. Together, these elements formed a modular and reusable framework for the entire simulation.
-
Scale & Customisation Options
A significant issue in creating the simulation was how to represent scale accurately while keeping the system observable. Real-world distances made the planets far too small and spread apart to be useful for demonstration. To address this, I created a toggle system that allowed switching between a to-scale model and a non-scaled, customisable version. I also included a slider to control the speed of the simulation, achieved by multiplying a global time variable across the system. Every planet and moon was linked to a central controller object, which handled orbit calculations and transforms, ensuring the entire simulation could be adjusted efficiently from a single script.
-
Challenges & Problem Solving
One of the largest challenges in the project was resolving scaling issues. My first attempt was to multiply real-world values down, but this caused planets to either disappear or remain unreasonably far apart. The final solution was to include a toggle between scale and non-scale models, combined with manually editable values for planet sizes when not to scale. Another issue arose when calculating planetary distances: by basing positions on object centres, planets overlapped. This was corrected by accounting for radii in the distance calculations. I also considered implementing a line-trace system for planet selection but was unable to do so within the timeframe given for this project.
-
Development Environment & Documentation
Working with the PS5 devkit presented a unique challenge, as we had no prior experience with the hardware or SDK. Our learning resources included official SDK documentation available only on university machines, a small set of sample projects, and a series of Moodle guides tailored to console programming. However, much of the PS5 documentation was written with the assumption of prior expertise, which made navigation difficult. To contextualise our experience, we compared the PlayStation documentation with that of the Nintendo Switch. The PS5 system offered more sample projects and stronger in-line code comments, but its documentation search system was poor, and developer forums were inaccessible.
-
Game Implementation
We began the project with a sample file included with the devkit, which demonstrated controller input by moving simple squares on-screen. We modified this foundation to represent Pong paddles, adjusting size, colour, and position. The ball was implemented using the same sprite-drawing utilities, with research into the graphics API revealing that ‘fillOval’ could be used to create a true circular ball rather than a placeholder square. Once the core objects were drawn, we wrote the Pong logic in standard C++, handling ball velocity, collisions, and paddle interaction through basic mathematics. This ensured the game behaved as expected, with responsive input from the DualSense controller driving the core gameplay loop.
-
Libraries, Menus & Data Handling
A major technical hurdle was our attempt to implement the PS5 SaveData library. Following the SDK’s threading-based documentation, we attempted to link and use the library for persistent data but found the examples difficult to replicate. Despite extensive troubleshooting and consultation of unviersity threading guides, we failed to achieve a working implementation before deadlines, a valuable lesson in recognising when to pivot. Instead, we implemented the MsgDialog library, which provided a simple in-game message system using the PS5’s native UI. This allowed us to display a custom message when the player pressed the triangle button, prompting them with a confirmation dialog before the game closed. Closing the application itself required research beyond the SDK; we eventually solved the problem by using the ‘std::quick_exit’ utility documented on cppreference.com. While not our original plan, this shift gave us a functional user flow and highlighted the importance of adaptability in console development.
-
Optimisation & Profiling
To evaluate performance, we used a profiling tool available on the devkit. Our baseline sample project included unnecessary systems, such as audio, which we removed to streamline performance. By capturing profiling snapshots of both the unoptimised and optimised builds, we could quantitatively compare their CPU usage. While improvements were modest, they were measurable: a small reduction in system and user core utilisation, fewer active threads, and a decrease in ‘syscall’ activity. The optimised code ran on fewer cores and threads overall, demonstrating how even simple adjustments – such as stripping unused features – can lead to measurable efficiency gains. Although our analysis could have been deepened with additional snapshots, the exercise gave us experience in methodical optimisation and reinforced the need for profiling tools in professional development.
-
Procedural Generation System
The dungeon generates rooms, corridors, floors, and then populates them with collectibles (coins, keys, doors) and enemies. Rooms are positioned randomly within bounds, with collision checks to prevent overlaps. Corridors connect room centres horizontally or vertically, maintaining uniform width. Keys always generate in the last room, while the player spawns in the first. Each playthrough produces a different layout, ensuring replayability.
-
Coding Practices & Design Patterns
I adopted industry-standard programming practices, including:
- Singleton pattern for managing shared data across scenes.
- Lists for storing rooms, corridors, and objects to allow batch operations.
- Modular functions for expandability and maintainability.
- Proper naming conventions, comments, and version control.
These practices made the code reusable and expandable, setting up a strong foundation for future projects.
-
Gameplay Loop & Shop System
The gameplay loop challenges the player to collect coins, find a key, and unlock the door to progress. Each new level increases dungeon difficulty. Coins can be spent in a shop to buy power-ups, introducing progression and strategy. A singleton tracks coins, shop items, and variables across scenes, ensuring consistency.
-
Challenges & Solutions
Corridor Generation: Initially corridors failed to connect rooms due to incorrect centre calculations and list-clearing errors. Debugging fixed the issue by recalculating room centres and resetting lists for each iteration.
Tilemap Wall Gaps: Corners sometimes misaligned when rooms and corridors overlapped, leaving gaps. This remained partially unsolved but identified as an area for future improvement.
Version Control Issues: Unity’s version control failed to transfer scenes correctly between devices. The solution was to wipe and re-create the repository.
