GUI in the game World of Tanks. Part Two: Overview of the GUI Structure and Future Plans


    Today we continue the story begun a week ago about the interface of the game World of Tanks.

    Current status of the project

    Refresh the information from the first part of the article.

    Currently, the project uses Autodesk Scaleform technology to render the GUI, which allows you to use Flash as a development environment.

    Anyone familiar with Flash knows that the programming language in this environment is ActionScript. This language has several versions, but the most widely used are ActionScript2 (AS2) and ActionScript3 (AS3).
    Our project currently uses both versions. This is due to the fact that development began on AS2, since Scaleform at that time did not yet support AS3. Over time, when AS3 support in Scaleform appeared and its implementation became quite reliable, the transfer of service (non-combat) interfaces to AS3 began.

    Why did the transition to AS3 begin with the migration of service interfaces to it? Everything is very simple. A vigorous tank chopper is our everything. Any interference, lags or bugs in the combat interface can ruin the user experience during the game. We could not take such a risk and began the migration from a less demanding in terms of resources and not so critical part of the game process. This approach has fully paid off. We came across performance and memory consumption issues. We decided most of them before the release of the updated hangar, some were caught and corrected already in production, relying on bug reports from players. There were no serious punctures, which cannot but rejoice.

    In a previous article, I gave a list of problems that we had to live with in AS2. I’ll refresh this list:

    • problems with different versions of code in different SWF files;
    • communication problem Flash <—> Python;
    • lack of standardization and unification in the code;
    • lack of clear rules and procedures for adding new functionality;
    • lack of automation of project assembly.

    These problems made a considerable contribution to the labor costs when adding new functionality to the project, and this is another reason why the transition to AS3 began with service interfaces. From the point of view of the GUI, the hangar is part of the project with the greatest concentration of new functionality on the release, and, accordingly, the most expensive in terms of human resources. In turn, the combat interface is already working on AS2, it is updated not so often and does not cause big complaints. Why break something that already works?

    Architecture and development overview

    But not a single Flash-based live GUI developer WoT. The script language in the project is Python. All the beauty that we made in Flash needs to be connected in the game, filled with data, processed and translated user input into real actions in the game. All this is done in Python.

    Here is a simplified diagram of the main GUI elements in the client:


    Let's take a closer look at what is behind these colorful rectangles.

    AS2 and its interaction with Python

    When you enter the battle, the battle.swf download starts. This file contains almost all the basic elements of the combat interface (HUD): command lists, chat, tank status panel, mini-map, consumables panel, etc.

    Almost every element present in battle.swf has one of the classes in When the download is complete, the contents of the file are stretched to the entire viewport of the game client and drawn on top of the 3D scene. In addition to battle.swf, SWF files are also loaded for displaying markers and sights. They are placed under battle.swf so that markers and sights do not overlap the HUD elements, but hide under them.

    Also, for systems with several monitors, SWF files with sights and markers are stretched to all monitors, and battle.swf is drawn only on the main monitor (so that the HUD does not fly apart at different angles of these monitors and it remains possible to read combat information adequately).

    The state of the markers and sights is monitored by Python. It processes game events and data, creates and destroys markers and sights. Each type of sights has its own Python class. Markers are monitored by VehicleMarkersManager. For the location of markers and sights on the screen, the client C ++ code is responsible.

    Two approaches are used to communicate Flash and Python in battle:

    1) The most common is the transfer of data by an array of scalar data through the API provided in Scaleform. In a previous article, I described this method and explained its shortcomings (it is inconvenient to read code, refactor, and maintain).

    2) The second approach is the Direct Access API (DAAPI). I just mentioned it, and now I will tell in more detail.

    This approach has two main advantages: simplicity and speed.

    The simplicity lies in the fact that you can transfer complex objects in both directions without thinking about serialization / deserialization - everything happens automatically in C ++. The speed of work is achieved due to the fact that method calls in both directions occur “directly”. That is, having a reference to the Flash object in Python, you can call its method and pass arguments to it as if it were a Python object. Without DAAPI, methods are called by specifying the path to the Flash object in the tree of visual components and searching for this component in the hierarchy, which is expensive.

    In addition to these obvious advantages, there is one more, but very important one - you can designate a Python object as a handler for a Flash object.

    I will draw and explain what is happening, step by step.


    • In Flash, we create a class with the declaration of public fields of type Function. The values ​​of these fields are not initialized, i.e. they are == null.
    • In Python, we create a class with an implementation for these methods. The naming of methods must match the naming of fields in the Flash class.
    • Create instances of these classes in Flash and Python.
    • We pass the link to the Flash instance in Python.
    • We assign an instance of the Python class as a handler for an instance of the Flash class (we establish a DAAPI connection).
    • We call the method from the first item in the Flash class.
    • Thanks to DAAPI, a method call is implemented in Python. Moreover, for Flash it looks like a call to a regular method in Flash. Parameters can be scalar data types or complex objects.

    This opportunity lies at the heart of the new architecture used in the AS3 part of the project.

    How the AS3 part of the project works

    First of all, it must be said that the project is built using Apache Maven and Apache Ant. Logically, the project is divided into several layers:
    • Common - SWC-library with a set of shared files used in the project. This includes:
      - the Scaleform CLIK library (a set of UI components and managers);
      - base classes and interfaces of the infrastructure part of the project;
      - code of third-party libraries used in the project.
    • GUI - SWC-library, which contains the code of all views, windows and other visual objects.
    • App - contains the implementation of all managers and infrastructure classes. Building this project gives application.swf the output.

    The entry point to the AS3 application is application.swf, which contains the class This file is always loaded when the client starts. He solves three main tasks:

    • creation and initialization of infrastructure parts of the project in AS3 (managers, containers for views, etc.);
    • loading all class definitions used in the application (this helps to solve the problem with the dynamic loading of different versions of the same class, which I talked about in the previous article);
    • loading and unloading resources for views.

    The Application class from AS3 has a soul mate in Python - the AppEntry class. Objectives of this class:

    • creation and initialization of the infrastructure parts of the project in Python (everything is absolutely symmetrical to the first point from the AS3 part);
    • loading a configuration describing the relationship between AS3 and Python parts of the project (more on this in just a few paragraphs);
    • initialization of the DAAPI connection for the Application itself and all infrastructure classes.

    In the process of building Common and GUI libraries, we use YAML to generate the code. Here is a list of tasks that we solve using YAML:

    • descriptions of the interaction interfaces of the Flash and Python halves of one module (a module or manager can be considered a module);
    • creating constant classes common to Flash and Python;
    • creating constant classes for localization messages;
    • creating classes of constants for images loaded on runtime;
    • creating ClassLoader - a class containing imports and links to all classes used in the AS3 application. ClassLoader is used to package all project code in application.swf.

    Any class in AS3 (whether it is a class that is responsible for some kind of view or a manager without a visual representation) that needs to communicate with Python must be created using the description in YAML. Let's look at an example of the post-battle statistics window:


    type: window									# определяет базовый класс для Flash классов
    python:										# перечень Python методов, доступных для  вызова из Flash
      - BattleResultsMetaPy: [ eventBus : ] 		# конструктор
        constructor: eventBus  
      - saveSorting: [ iconType : String , sortDirection : String , bonusType : int ] # сохраняет предпочтения по сортировке
      - showEventsWindow: [ questID : String ] 					# вызвает показ окна боевых задач
    flash:										# перечень Flash методов, доступных для вызова из Python
      - BattleResultsMeta: [ ]							# конструктор
      - as_setData: [data : Object]							# передача готовых данных с результатами боя во Flash

    After processing this YAML file, the following set of classes and interfaces will be generated during the assembly of the project:

    BattleResultsMeta - the base Python class. It declared a set of all methods from the pyramid section of YAML for their subsequent overloading in the successor class and a set of methods from the flash section to be able to access them from Python using the signature declared in YAML.

    class BattleResultsMeta(DAAPIModule): 
    	def saveSorting(self, iconType, sortDirection, bonusType):
    	def showEventsWindow(self, questID):
    	def as_setDataS(self, data):
    		if self._isDAAPIInited():
    			return self.flashObject.as_setData(data)

    BattleResultsMeta is the base AS3 class. It declared a set of all methods from the pyramid section of YAML, for access to them from Flash by the signature declared in YAML. It is these methods that are declared as fields of type Function, and they will be replaced by the Python methods from the previous paragraph, after the DAAPI connection is established.

    public class BattleResultsMeta extends AbstractWindowView {
    	public var saveSorting : Function = null;
    	public var showEventsWindow : Function = null;
    	public function saveSortingS(iconType : String, sortDirection : String, bonusType : int) : void {
    		saveSorting(iconType, sortDirection, bonusType);
    	public function showEventsWindowS(questID : String) : void {

    IBattleResultsMeta - AS3 interface. It declares a set of all methods from the python and flash sections of YAML. A class that implements the functionality of the post-battle statistics window should implement this interface.

    public interface IBattleResultsMeta extends IEventDispatcher {
    	function saveSortingS(iconType : String, sortDirection : String, bonusType : int) : void;
    	function showEventsWindowS(questID : String) : void;
    	function as_setData(data : Object) : void;

    Now, to integrate a new window into the client, you need to perform several steps.

    • Create a FLA file that will contain the visual part of the window.
    • In Flash, create a BattleResults class that inherits from BattleResultsMeta and implements the IBattleResultsMeta interface.
    • In Python, create a BattleResults class that inherits from BattleResultsMeta.
    • In Python, create a constant used as the unique identifier for a new component.
    • Add configuration data in Python, where the correspondence will be indicated between:
      - the unique identifier of the new component,
      - the type of the new component (in our case this is a window),
      - the file with the visual representation,
      - the Python class responsible for the new component.

    Some of these steps can also be automated as part of YamlTask ​​execution. I chose the simplest approach so as not to complicate the story.

    Dances with a tambourine are over. To show a new window in the client, it is enough for the Python part of our Application to call the loadView method, into which the unique identifier of the new component is passed.

    What happens after we called our window to load

    Here is a simplified algorithm for loading and initializing the module (steps in Python are marked in blue, Flash in blue):


    All these steps are performed by various parts of our infrastructure layer, which isolates the developer of the functionality from having to do these steps by hand. This layer solves the following tasks:

    • management of loading SWF files;
    • managing the distribution of views across layers and controlling the transition of focus between them;
    • tooltip management;
    • work with localization strings;
    • sound event management;
    • context menu management;
    • animation management;
    • management and work with color schemes;
    • other tasks.

    Future plans

    The largest task for the foreseeable future is the transfer of HUD to AS3. We are currently conducting research and preparing a project for such a transition. The main thing that we want to achieve as part of this transition is to unify the mechanisms for adding and developing new functionality and get rid of the variety of approaches that we have now.

    We also want to at least not degrade the performance and memory consumption of the new HUD, and in the best case, achieve performance gains and reduce the amount of memory required for the HUD to work.

    In addition, we have a number of activities aimed at simplifying and improving the process of interaction with related departments during the development and development of new functionality.

    The bottleneck is the interaction with the User Experience (UX) department. Now when developing new game concepts or new functionality, it is often necessary to create a prototype, on the basis of which it is possible (before transferring the task to development) to evaluate the convenience or ease of understanding of basic mechanics by simple players.

    Creating such prototypes outside the client is expensive and may not always give the desired effect. Without the context or environment that we have in the client, we cannot achieve the desired level of immersion in the game. It remains to create prototypes inside the game client. This is also very expensive, because for this it is necessary to distract the developers from their main occupation and redirect their efforts to doing work that may be completely forgotten and not in demand after the test.

    Even if the test was successful, then there is a chance that everything will have to be redone again. In pursuit of cost reduction and due to tight deadlines for the prototype, we often have to resort to the use of “crutches”, coarse “files” and patches.

    To solve this problem, we plan to:

    • create a set of standard components (buttons, indicators, lists, etc.);

    This step is due to the fact that until recently, almost every major new task brings, in addition to the newest functionality, a set of new components. They have to be made on the basis of the old ones, changing their appearance and making changes to the logic of their work, or to implement these components anew. The UX department is committed to creating a list of standard components and formulating requirements for them.

    • Create rapid prototyping tools.

    Based on the library of standard components, we plan to create a separate tool or mode in the client, in which UX specialists can create new interfaces and configure simple rules for their interaction without the intervention of programmers. Such prototypes will be built only from standard components. They can be saved and downloaded for execution in the client and tested at the lowest cost.

    That's all I wanted and could tell you about the GUI in World of Tanks. Ask questions in the comments. Thanks for attention.

    Also popular now: