TRAINYOURAGENT

Last Signal: a Unity 6 mobile survivor game in 465 C# scripts

We built a mobile survivor game in Unity 6: 465 C# scripts and 177,390 lines under Assets, rendered through the Universal Render Pipeline with custom ShaderLab dissolve and glow effects, backed by a Supabase schema, and packaged with store metadata for both the App Store and Google Play.

The problem

Games are the least forgiving software category a general engineering team can take on, because a game has no 'good enough'. A CRUD application that is slightly slow is still usable. A game that drops to 45 frames per second on a mid-range Android handset is not a slightly worse game — it is uninstalled. The survivor genre compounds this specifically. The core loop is 'hundreds of enemies converge on one player', so the frame budget is dominated by entity counts that grow over the run, and everything — spawning, collision, damage resolution, visual effects, audio — has to be built as if it will be running at ten times the count you tested at. We include Last Signal in this portfolio for one reason: it is the piece that demonstrates the team can work inside somebody else's engine, at scale, with hard real-time constraints, rather than only in the web and services stack where we are obviously comfortable.

Content as data, not as code

The project is organised around ScriptableObjects, which is Unity's mechanism for treating game content as assets rather than as compiled types. Heroes, pets, enemies, hazards, perks, skins and progression curves live as data assets under `Assets/ScriptableObjects`, and the systems that consume them are generic. This matters more than it sounds. In a survivor game the design is tuned by iterating on numbers hundreds of times — damage curves, spawn tables, evolution thresholds — and if each of those iterations requires a recompile, the design simply does not get tuned enough times to become good. Data-driven content is the difference between a designer making forty balance passes and making four. It also has a documentation consequence visible in the repository: because content is data, the design documents (`HERO_DESIGNS.md`, `ENEMY_DESIGNS.md`, `PET_DESIGNS.md`, `PET_EVOLUTION_SYSTEM_SUMMARY.txt`, `HAZARD_SYSTEM_INTEGRATION.md`) describe assets that exist rather than intentions.

URP and two custom shaders that carry the visual identity

Rendering goes through the Universal Render Pipeline — confirmed by `UniversalRenderPipelineGlobalSettings.asset` and a `DefaultVolumeProfile` in the project root. URP is the correct choice for a mobile title: it gives you post-processing and a scriptable render pipeline while staying inside a tile-based mobile GPU's budget in a way the built-in pipeline does not. On top of that sit exactly two custom ShaderLab shaders: `SpriteDissolve.shader` and `SpriteGlow.shader`. We want to be precise here because it would be easy to imply a large shader library. There are two. They are the death-dissolve effect and the emissive highlight, and between them they do most of the visual work of making hits and deaths feel like they landed — which in a game where hundreds of enemies die per minute is not a small responsibility. The restraint is arguably the right answer. Two well-tuned sprite shaders that run everywhere beat a dozen that force a quality tier split.

Scripts partitioned by domain, and build automation in the editor

`Assets/Scripts` is partitioned into domain folders rather than by Unity type — Combat, Enemies, Equipment, GameModes, Camera, Audio, Data, Config, Core, Cloud, Backend, Competitive, Debug and Accessibility among them. That last one being a first-class folder rather than a late retrofit is a choice we would highlight: accessibility in a fast action game means colour-blind-safe damage indication, scalable UI, and reduced-motion options, and none of those can be bolted on after the visual language is fixed. `Assets/Editor` holds the build automation, and it is the part that turns a Unity project into a shippable one: `BuildScript.cs` for repeatable builds, `AndroidPostProcessBuild.cs` and `iOSPostProcessBuild.cs` for the platform-specific manifest and plist manipulation that both stores require, `FirebaseInstaller.cs`, and — tellingly — `CleanStaleDefines.cs` and `FixDefines.cs`. Those last two exist because scripting define symbols drift when SDKs are added and removed, and a stale define produces a build that compiles locally and fails in CI for reasons that take a day to find. Somebody automated their way out of that day. `Assets/Plugins` carries the native layers for Android and iOS plus RevenueCat, which is the subscription and in-app-purchase layer. There is a single Unity scene: content is composed at runtime from ScriptableObjects and prefabs rather than authored as per-level scenes, which is the correct structure for a genre where every run is procedurally assembled.

A backend schema and real store packaging

`Backend/supabase_schema.sql` defines the server side in thirteen tables, and the list makes the intended scope plain: `users`, `cloud_saves` and `transfer_codes` for persistence and device migration; `leaderboards`, `player_rankings` and `ranked_seasons` for competition; `guilds`, `guild_members` and `guild_raid_sessions` for the social layer; `friendships` and `chat_messages`; `analytics_events`; and `deletion_requests`. That last table is worth pausing on. A `deletion_requests` table in the initial schema — rather than added under duress after a store review — means right-to-erasure was designed in rather than retrofitted. Both app stores now require a data-deletion path, and retrofitting one into a schema with cloud saves, guild membership and chat history is meaningfully harder than designing for it, because deletion has to reason about records that other users also participate in. Firebase is wired in as well, with both `GoogleService-Info.plist` and `google-services.json` present. The `StoreListing` directory contains `app_store_metadata.txt`, `google_play_metadata.txt` and a Google Play feature graphic at the required dimensions. This is the least glamorous artefact in the whole portfolio and one of the most diagnostic: the gap between 'a game that runs' and 'a game that can be submitted' is a fortnight of icon sets, ratings questionnaires, privacy declarations, screenshots at five device sizes, and copy that fits the character limits. A project with the store metadata written is a project someone intended to actually ship.

The numbers, and the command behind each one

What is genuinely hard about this

Frame budget under adversarial entity counts. A survivor game's difficulty curve is literally a curve of how many things exist at once, so the performance work is not an optimisation pass at the end — it is the design constraint that shapes every system from the first day. Object pooling, allocation discipline in the update loop, avoiding per-frame garbage, and keeping collision resolution off the main thread where possible are not advanced techniques here, they are table stakes. Cross-platform mobile input and device fragmentation. The Unity Input System configuration has to produce the same feel on a modern iPhone and on a four-year-old Android device with a different aspect ratio, a notch in a different place, and a GPU with a fraction of the fill rate. Most of the tuning work in a mobile game is invisible in a screenshot. And the thing that makes games harder than services work: there is no partial credit. A backend can ship at 90 percent and be genuinely valuable; the missing 10 percent is a roadmap item. A game at 90 percent is not 90 percent as fun. It is not fun. The last 10 percent — the hit feedback, the audio timing, the difficulty ramp — is where the entire product value is, and none of it can be measured by a test suite.

What this entry does not claim

Stack and status

Built with Unity 6 (6000.3.10f1), C#, Universal Render Pipeline, ShaderLab, Unity Input System, ScriptableObjects, Supabase (Postgres), Firebase and RevenueCat. Relationship: Internal build. Stage: Built, not launched. Period: 2026. Our role: Gameplay systems, rendering, backend schema, store packaging Figures re-derived on 2026-08-23.

Where a supplied figure was wrong

Corrected on 2026-08-23: '471 C# scripts' is the repository-wide count; 465 of those are under Assets/. 'URP/ShaderLab shaders' overstated the shader work — there are exactly two custom .shader files.