An endless 2D Asteroids-style survival game built with Unity 2022.3.9f1 and C#.
The project is a graduation work and gameplay-programming portfolio sample focused on explicit architecture, custom physics, data-driven configuration, pooled runtime objects, desktop/mobile controls, platform adapters, and lifecycle-safe Unity code.
Status: feature-complete; final Android and release validation in progress.
Portfolio: https://tokarevdev.github.io/
The player pilots a ship inside a responsive toroidal arena and earns as many points as possible while avoiding asteroids, fragments, and pursuing UFOs.
- Movement uses acceleration, braking, inertia, rotation, and wrap-around world bounds.
- Large asteroids split into two faster fragments when destroyed by a bullet.
- Bullets destroy fragments and UFOs.
- The laser destroys every intersected enemy, has limited charges, and recharges over time.
- Asteroids spawn outside the visible world and travel with randomized velocity.
- UFOs spawn periodically, pursue the player, and select a random blue, orange, or red visual on every spawn.
- The player has three health points. Enemy contact causes damage and an elastic bounce.
- After a hit, the player receives three seconds of invulnerability, loses control temporarily, passes through enemies, and displays collision and invulnerability feedback.
- Large asteroids rotate smoothly, fragments use frame animation, collision sparks are pooled, and thruster particles react to thrust and current speed.
- The HUD shows health, score, survival time, position, rotation angle, speed, laser charges, and recharge time.
The base world configuration is 32 x 18 Unity units. Runtime camera bounds synchronize the toroidal playfield with the current aspect ratio, so the arena remains responsive across supported landscape resolutions.
| Action | Input |
|---|---|
| Thrust | W or Up Arrow |
| Brake | S or Down Arrow |
| Turn left/right | A / D or Left / Right Arrow |
| Fire bullets | Hold Space or Left Mouse Button |
| Fire laser | E or Right Mouse Button |
- A virtual joystick controls movement direction.
- Dedicated touch buttons fire bullets and the laser.
- Mobile controls are shown only on mobile platforms.
- Desktop and mobile input are separate
IPlayerInputStrategyimplementations.
The project intentionally uses Unity's classic Input API. The New Input System is not used.
Project code is divided into four high-level assembly definitions:
| Assembly | Responsibility |
|---|---|
Game.Core |
Pure models, contracts, configuration DTOs, custom-physics primitives, input contracts, navigation facade, and factories |
Game.Infrastructure |
JSON loading, classic input strategies, scene loading, bootstrap, Firebase, AdMob, application services, and project-level bindings |
Game.Gameplay |
Player, enemies, weapons, collision orchestration, pooling, score, session, world synchronization, signals, and gameplay views |
Game.UI |
Main menu, HUD, mobile controls, game-over flow, Views, and ViewModels |
ProjectContext, scene installers, and UI installers are composition roots. Zenject constructs services and exposes scene components through explicit bindings; runtime gameplay code does not search the scene with FindObjectOfType, GameObject.Find, or tag lookups.
Pure gameplay state and algorithms are regular C# classes. MonoBehaviour components are limited to Unity-facing views, scene references, pools, spawners, and lifecycle adapters. MVVM is used only for UI.
Gameplay movement and collision do not use Physics, Physics2D, dynamic Rigidbody, or Rigidbody2D simulation.
The custom physics stack contains:
CustomPhysicsBody2Dfor position, velocity, radius, and mass;CustomPhysicsIntegrator2DandCustomPhysicsWorld2Dfor fixed-step integration;- circle-circle and segment-circle intersection tests;
- elastic player/enemy collision resolution;
- registries and synchronizers that separate logical bodies from Unity views;
ToroidalWorld2Dfor wrap-around coordinates.
Enemy-enemy collision is intentionally disabled. Collision queries operate on centralized registries without allocating temporary collections in hot loops.
Runtime balance is loaded from JSON in Assets/StreamingAssets/Configs/ through Newtonsoft.Json:
player.json— health, acceleration, braking, speed, turn rate, bullet parameters, laser parameters, and invulnerability duration;enemy.json— asteroid, fragment, and UFO parameters, rewards, spawn timing, and fragmentation settings;world.json— world size, maximum enemies, spawn offset, and initial pool sizes.
GameConfigLoader loads the three files during bootstrap, and GameConfigValidator rejects invalid values before gameplay scenes open. ScriptableObjects remain only where Unity asset references are appropriate, such as asteroid animation variants and advertising identifiers.
The non-MonoBehaviour generic ObjectPool<T> owns all created instances and reusable entries. It uses List<T>, Queue<T>, and HashSet<T> to expose created objects, provide FIFO reuse, and reject duplicate returns. Asteroid, UFO, projectile, enemy-entity, and collision-VFX lifecycles build on pooled storage to avoid repeated runtime Instantiate/Destroy churn.
GameNavigationFacade provides the UI with a small navigation API for start, restart, and main-menu transitions. It centralizes the in-progress guard and prevents double-click scene requests without exposing scene-loader details to Views or ViewModels.
IPlayerInputStrategy separates KeyboardMouseInputStrategy from MobileInputStrategy. Gameplay consumes the same PlayerInputState regardless of platform, while each strategy owns its platform-specific input source.
EnemyEntityFactory and ProjectileEntityFactory create logical runtime entities with their required state. Initial creation is separated from pooling and lifecycle orchestration, keeping construction rules out of spawners and collision systems.
C# events and Zenject SignalBus propagate health, score, enemy-death, and player-death changes. PlayerDeathSignalService observes PlayerHealth.Died and fires PlayerDiedSignal; session logic, analytics, and UI can react independently with symmetric IInitializable/IDisposable subscriptions.
- Asteroids and UFOs share centralized logical enemy storage and lifecycle services.
- Large asteroid variants are selected without immediate repetition.
- UFO visuals are randomized on each pooled spawn.
UfoPursuitMovementfollows the player without coupling the logical entity to a Unity component.EnemyRewardServiceusesDictionary<EnemyType, int>and grants score only for player-caused deaths.
- Bullets use custom bodies, pooled views, a centralized registry, lifetime control, and world-exit cleanup.
- Bullet impacts distinguish large asteroids, fragments, and UFOs.
LaserTargetQueryperforms segment-circle tests and returns every intersected enemy.LaserChargeMagazineandLaserRechargeControllerown charge consumption and recovery.- A short-lived
LineRendererview presents each laser shot.
- HUD ViewModels observe gameplay state and expose presentation-ready values.
GamePauseServiceowns time-scale changes;GameSessionowns session completion.GameOverViewModelcaptures the final score and delegates navigation toGameNavigationFacade.- Main-menu quit behavior is isolated behind
IApplicationQuitService. - Scene transitions use UniTask; gameplay coroutines and
Taskare not used.
Firebase Unity SDK 13.15.0 is initialized during bootstrap after JSON configuration has loaded. Dependency checking uses CheckAndFixDependenciesAsync, is awaited through UniTask, and fails softly except for external cancellation.
The analytics adapter implements IAnalyticsService and reports:
| Event | Parameters |
|---|---|
game_started |
none |
game_ended |
score, duration_seconds |
Android package name: com.tokarevdev.asteroidssurvival.
To validate events with Firebase DebugView on a connected Android device:
adb shell setprop debug.firebase.analytics.app com.tokarevdev.asteroidssurvivalPlay a session, end the game, and verify game_started and game_ended in Firebase DebugView. Disable debug mode afterward:
adb shell setprop debug.firebase.analytics.app .none.The Editor warning Database URL not set in the Firebase config is expected: this project uses Firebase Analytics and does not use Firebase Realtime Database.
Firebase native binaries are tracked through Git LFS.
Google Mobile Ads 11.2.0 is isolated behind IAdvertisementService.
AdMobInitializerowns SDK initialization.BannerAdvertisementServiceowns banner creation, display, callbacks, and destruction.AdMobConfigurationstores platform-specific banner unit IDs outside service logic.- The current configuration uses official test banner identifiers.
- Banner lifetime follows main-menu presentation and is cleaned up on disposal.
- Package:
com.tokarevdev.asteroidssurvival - Minimum SDK: Android API 23
- Target/compile SDK: Android API 34
- Orientation: landscape
- Google Mobile Ads:
11.2.0 - Firebase Unity SDK:
13.15.0
- Frequently spawned enemies, projectiles, and collision effects are pooled.
- Event and SignalBus subscriptions are paired with deterministic cleanup.
- UniTask operations use owner cancellation where applicable.
- Input is read once per frame by the mobile strategy and cached for all consumers.
- Hot loops avoid LINQ, temporary arrays, closures, and repeated component lookup.
- Runtime entities and Unity views are synchronized explicitly instead of sharing hidden state.
- Particle systems provide pooled collision feedback, invulnerability feedback, and speed-dependent twin thrusters.
Bootstraploads and validates JSON configuration.- Firebase dependencies are checked and initialized.
MainMenuopens and requests the test banner.Gamestarts the survival session and logsgame_started.- Player death fires SignalBus events, ends the session, pauses gameplay, logs
game_ended, and opens the game-over UI. - Restart and main-menu transitions pass through the guarded navigation facade.
Enabled build-scene order: Bootstrap, MainMenu, Game.
-
Install Unity
2022.3.9f1with the required desktop or Android build support. -
Clone the repository with Git LFS enabled:
git lfs install git clone <repository-url>
-
Open the project with Unity
2022.3.9f1. -
Open
Assets/_Project/Scenes/Bootstrap.unity. -
Enter Play Mode.
Do not start from Game.unity when validating the full application bootstrap, Firebase initialization, advertising lifecycle, or scene navigation.
- Unity
2022.3.9f1 - C#
- Zenject and SignalBus
- UniTask
2.5.11 - Newtonsoft.Json for Unity
3.2.2 - UGUI and TextMeshPro
- Firebase Analytics
13.15.0 - Google Mobile Ads
11.2.0 - Git LFS
- Four project-level Assembly Definitions
The repository does not use Unity Asset Store content.
Asteroid animation frames are based on “Asteroids” by phaelax, licensed under CC BY-SA 3.0. The original 16-frame sequences are imported and configured as large-asteroid and fragment visual variants in this project.
The player ship and UFO sprites are based on “Ufo's and spaceship” by dravenx, licensed under CC BY-SA 3.0. The images were upscaled and adapted for this project. These derivative sprite files remain available under CC BY-SA 3.0.
SpaceNebula.png, SpaceStars.png, and futuristic-moon-background.jpg were generated specifically for this project with OpenAI ImageGen on August 20, 2026. They replace earlier web-search images whose provenance could not be verified; no unidentified stock background remains in the project.
Third-party SDKs, plugins, and fonts retain their respective vendor or open-source licenses.
Oleksandr Tokarev
Unity Developer | C# Gameplay Programmer
Email: otokarevdev@gmail.com
Portfolio: https://tokarevdev.github.io/