
A year ago we had one project in our company - mobile shooter War Robots with relatively slow but colorful and intense battles. The game continues to evolve, it has tens of millions of installations and players around the world, updates are constantly being released. At some point, we wanted to make a dynamic Unity shooter with speeds comparable to Overwatch, CS: GO or Quake. But to realize our plans for mobile platforms (primarily iOS and Android) based on War Robots with current architecture and approaches was almost unreal.
We understood how to do this in theory - there are many articles, presentations on YouTube telling in detail how to write a shooter,
how to work with the network , what problems arise and how to solve them. Rocket Science is not here, all these approaches were invented 30 years ago and during this time they have not changed much. BUT: we had no practice.
')
Looking ahead, I will say - we managed to realize our plans. We have created a dynamic fast shooter for mobile platforms, which is currently in beta testing and is actively being finalized. And I would love to share all of this. This is the first review article listing and briefly describing virtually everything we use (please do not be confused with our
other project in development, technologies and approaches in which are similar, but differ in details).
Simulation
Let's start with the gameplay simulation. We decided to write it on
ECS - this is a data-oriented approach, in which data is separated from logic. Data is represented as entities (Entity) and components (Components) belonging to entities. The logic is described in Systems (Systems), which are usually traversed by the components of entities and change them, creating new components and entities.

We quickly wrote our ECS, because the current solutions did not suit us. ECS allowed us to separate logic from data, as well as quickly serialize data about the world and exchange it between client and server. We have a separate project for generating ECS-code in the common library, since many methods in it are duplicated from component to component, for example, data serialization, their copying, comparison and delta compression.
The same simulation code runs simultaneously on the client and the server. This gave us the following benefits:
- The client programmer can fully write the game features - the same logic will work on the server.
- There is no delay between the player’s action and the result of the action in the game, since the client instantly processes the command locally (prediction). And only if the result of the local simulation is sold with the server one, the client takes the server state of the world as a basis and rolls it.
- Learning the player (tutorial) we can simulate locally on the device, without connecting to the game server.
- When developing a feature on a project, a constantly running server is not needed. To test logic, graphics, game design, etc. - You can run a match locally.
- Using the common library, we were able to quickly write bots for load testing, which in time revealed various performance and memory problems on the game server.
Customer
In addition to the prediction and rollback on the client, we use interpolation. But not in the usual sense, because we simulate and draw in one frame, 30 times per second, and as a matter of fact, we do not need classical interpolation.
Whenever possible, we interpolate the status from the server in the buffer, since On mobile platforms, packet loss can be significant, and we want to have the state of the world to draw here and now.
Client architecture
In client code, dependency injection is actively used. We use
Zenject as the DI framework. Specific binding settings are described in small Composition Root, which in Zenject are called Installer. In most cases, we try to write in such a way that by turning off the specific Installer we disable the feature / view / network interaction.
The game has several contexts and the objects there belong to a specific context, they live with it:
- ProjectContext - objects that live the entire lifetime of the application;
- MetaContext - character selection, equipment, purchases, etc .;
- MatchContext is the context of PvP combat.
Fight contextIn the context of MatchContext at the presentation level of game mechanics, we use
MVP . The model is the data from ECS, the presenters work with them and the Unity-view part for display. We have presenters for entities and components. At the presentation UI level,
MVVM is used, described below.
For the transport of data between the client and the server, a low-level library
Photon comes out, from which we use a protocol based on udp. Serialize the data yourself: we wrote custom delta compression, because The world of the game is large, and the volume of traffic is critical for mobile devices and it would be good for us to meet the
MTU so that the state of the world fit in one physical package.
Meta contextIn MetaContext, for the display layer, we use
MVVM and
DataBinding based on the
Unity-Weld library. As a result, we do not have a custom MonoBehaviour-class for each window with a bunch of settings and UI logic, as is usually done on Unity-projects. Instead, the UI logic is described in the ViewModel of a specific window, and the View level is represented by just one class, View (inherits MonoBehaviour) and several standard classes for data binding. In our case, the programmer writes only logic and outputs the required data outward, and the UI designer imposes and sets up data binding for the display.
In the context of meta, we also use the Signal-Command-Service approach, partially borrowed from the
StrangeIoC and
IoC + libraries. Now it is based on Zenject signals, but it is written in such a way that at any moment it can be rewritten to any other variant. Thus, the
event-action / service connection is now configured at the Installer level and is described in one line.
The protocol of communication with meta-servers is based on
Protobuf ,
WebSockets is used as a transport.
Third Party Solutions
In addition to the above, we use on the client a lot of other third-party solutions and plug-ins to speed up development:
- FMOD - for working with sound. We have a separate sound designer, he knows how to “cook” cool sounds and music in the FMOD editor.
- Volatile Physics - client and server physics written in C #.
- Lunar Console - to view logs on the client, as well as test functionality.
- Helpshift - to communicate with our players and collect feedback.
- AppMetr is our own analytics system.
- Json.net .
- And etc.
Common solutions
We have a separate team that develops common solutions for projects. For example:
- we have our own PackageManager (it's better than in Unity 2018 and appeared long before it), which supports versioning and deletion. Through it, we deliver all our other solutions to the project, as well as third-party plug-ins and libraries. We have no problem removing and updating plugins;
- Your BuildPipeline - the ability to customize the assembly for different configurations and platforms, because usually the steps of assembly and configuration are different + integration with TeamCity;
- client authorization module in the system;
- automatic testing system;
- third-party plug-ins adapted for us (logging, analytics; see above);
- common shaders;
- and etc.
Client optimization
We have a lot of experience optimizing for mobile platforms. In particular:
- We optimize shaders.
- Reduce the build size by times by compressing textures, assembling them into atlases, reducing the number of options in shaders and more.
- We use our MeshMerger to combine static objects with one material into one object, also with merge textures.
- Using the profiler built into Unity, as well as dotTrace and dotMemory, we optimize the code.
- We use memory pools and preallocated memory to minimize garbage collection.
- We use delta compression to reduce the size of the packets sent.
- Much more.
Some of this can be found in our previous articles
“Post-effects in mobile games” and
“Optimization of 2d applications for mobile devices in Unity3d” . The second of these two articles is already outdated, but many tips from there work now.
Game server
The game server on which the battles take place is written in C #. The udp-based Photon Network Library mentioned above is used as the network protocol. Currently, changes to the server are made very rarely, because All game logic is written by client programmers and it is spinning on the server.
We also wrote a special tool that allows you to connect to the server via http and visually view the battles in real time. In addition, you can view all entities, their components and values, as well as record the battle to play it later for test purposes. About the game server, we will write separately in future articles.

Meta server
By meta, we mean a microservice system that allows you to implement such features as: player profile, purchases, matchmaking, chat, clans, etc. All this is written by individual programmers and is used jointly on different projects. Technologies used in the development:
- Java is the main development language.
- Cassandra , postgres - for storing player data.
- Consul - as a service discovery, including the storage of key-value data for game settings and servers.
- RabbitMQ - the message queue.
- Protobuf is a protocol between services and client.
- gRPC - for communication between services and game server.
- As well as netty , akka , logback and more.
Team
The game quickly grows with features and we actively attract new employees, but in general the project team consists of a core and related departments.
The core includes client programmers, game designer, Project Manager, Product Owner and QA (testers). Related departments include:
- graphics programmers;
- programmers of meta-servers;
- team developing common solutions for projects;
- artists;
- animators;
- community managers;
- support;
- marketing, etc.
How we are working
We work on
scrum . But you should understand that everyone has their own Scrum. We have two-week iterations, poker planning, retrospectives, demos, etc. But at the same time releases we are not tied to the end of the sprint and there is an additional stage of release testing.
Store the code on GitHub, do pull requests, and use
Upsource for code review. We write the code in
Rider and Visual Studio +
Resharper .
To build the client, as well as the development and deployment of servers, we use TeamCity and Gradle. Client installation on a mobile device occurs in one or two clicks:
- build the client in TeamCity by clicking Run (you can skip this step, since most of the builds take place automatically);
- installation by QR code generated for the build.
Instead of conclusion
As you can see, nowadays a serious product uses a large number of approaches and technologies, as well as resources and specialists, and not everything is as simple as it may seem at first glance.
In the
next article I can talk about our ECS , how we wrote it and how it works, or write in the comments about what you would like to read about from the above described soon.