The goal of the game is to complete 100 waves as quickly as possible. To achieve this, the player must control a modular ship equipped with special abilities and survive against hordes of enemies. By eliminating enemies, the player will earn money, which can be used to upgrade its ship.
git clone the repo
npm install
npm run dev
I developed the entire game framework myself, including resource management, scene management, collision detection, UI, and more. Given the time I’m able to commit to this project and the fact that I’m working solo, I designed the game to allow for faster development while maintaining a bug-free application. For instance, I chose to write tests in a text-based table format and perform manual testing rather than implementing automated test cases, as adding automated tests would have significantly increased the development time.
The main way for the entities of my game to communicate with each other is through the Service Locator, which enables sharing values between objects via services. The Service Locator is a static class that retrieves services, which are defined as interfaces and implemented by the classes providing the services.
Most of the drawbacks of the Service Locator relate to automated testing. However, since I decided not to implement automated tests, the benefits of using this straightforward pattern outweigh its disadvantages.
Service Name | Role | Dependency on other Services |
---|---|---|
ImageLoader | Retrieve the image associated with a specific file path | None |
SceneManager | Keep track of the current Scene of the Game. Manage the loading and the unloading of the different scenes | None |
Player | Keep track of the state of the Player | GeneratedSpriteManager, SceneManager |
EventManager | Manages events and notifications | ImageLoader, UtilManager, WaveManager |
WaveManager | Manages the Enemy waves | None |
GeneratedSpriteManager | Manages the projectiles of the player and enemies | None |
CollideManager | Manages collisions between sprites that can collide | WaveManager, GeneratedSpriteManager |
EventManager | Sprites can register for specific events and get notified when triggered | None |
UtilManager | Provides a set of utility functions accessible throughout the code | None |
KeyboardManager | Manage Keyboard Key Listening | None |
GameStatManager | Manage Stat that are useful to keep track of during the game | None |
The Sprite system is simple and easy to use. At its core is an abstract class called Sprite, which stores an image (sprite sheet) and defines animations by specifying their names and corresponding frames. The system includes Update and Draw methods to manage and render these animations. Each sprite has its own manager, except for the Player, that is a service by itself: Enemies are managed by the Wave Manager
service, while Projectiles and Skills are handled by the Generated Sprite manager.
The user interface system is designed with flexibility and structure in mind, centered around a core interface called IUIComponent
. This interface establishes the foundational contract that all UI components must follow, ensuring consistency across the system.
Building on this foundation is the BaseField
class, which is a semi-specialized field representing a generic interactive element on the screen. BaseField
manages important states such as hover and click, and it includes the ability to render an optional outline around the field for emphasis.
The system further extends BaseField
into more specialized components: ImageField
, which is specifically designed to display images, and TextField
, which is optimized for rendering text. These specialized fields inherit the basic functionality of BaseField
while introducing their own unique behaviors to suit their respective content types.
At the top level, the UIManager
acts as the controller for all these components, similar to a div in HTML. It maintains a list of UI elements, all of which implement the IUIComponent
interface. This structure allows UIManager
to efficiently manage and render a diverse array of UI components, ensuring a cohesive and responsive user interface.
The hitbox system is designed to be straightforward and consistent. At its core is the ISpriteWithHitbox
interface, which any sprite needing a hitbox must implement. Once this interface is in place, the sprite will have a hitbox composed of multiple rectangles that together cover the entire sprite, ensuring accurate collision detection and interaction.
When it comes to testing the code, I take a structured, manual approach. For each significant feature I implement, especially those that are more complex, I create a detailed table of test scenarios. Each test scenario is documented with two key columns:
I follow this process for every major feature, carefully stepping through each test case manually. While automated tests could be more efficient in the long run, I’ve opted for this manual approach to speed up development, as I'm working solo and focused on delivering the product quickly.
Testing the visual aspects of the game involves a more iterative, visual approach. When I create a new sprite, I start by pixelating it to ensure the art is crisp and visually appealing. Once the sprite is complete, I test how it fits within the game’s context. This involves placing the sprite in a game scene, either within Figma or directly in the game’s testing environment, to evaluate its appearance and coherence within the overall design.
If the sprite looks good, I proceed to define the animations and any associated data, such as hitboxes. This data, including animation speeds and hitbox details, is stored in a Typescript with a JSON style file. I used this approach because it is easier for Type Checking see src/SpriteStaticInformation
. To ensure everything functions correctly, I use a separate testing version of the game where I can fine-tune and validate these elements before integrating them into the main game.
This dual approach—testing the code through structured scenarios and testing the visual elements through contextual visualization—allows me to maintain a high standard of quality as I develop the game.