Technical preparation of one game created by independent developers

    Hello, harsh but fair habr!

    I want to dissect together with you one game written by me together with my good friend. In mechanics, a game is a real-time battle between two players, each of which has a deck of cards. And the cards, in turn, generate fighters who already independently twist on the enemy’s bunker, simultaneously crumbling the enemy soldiers. In addition to the battle, the game has a store with maps; headquarters, where you can form a deck and download characters; an arena where you can run a quest or a real battle; Well, a bank where you can get game currency. Let me remind you that we are independent developers, therefore, are limited in resources and many solutions are not ideal.
    How did you start to come up with a game here: habrahabr.ru/post/142490

    Let's start the preparation.


    Social platform

    Following the KISS principle, we decided to immediately make a game for social networks because there is a ready-made authorization mechanism and wide distribution possibilities. In general, the easiest way to be heard in them. They chose Vkontakte because of a younger audience and more experience working with this network specifically with us.
    All interaction with the social network was carried out in a separate module with a singleton, in order to further change social networks, like gloves. For this, we have a single interface that each concrete class must implement for each social network. The game interacts with the social network through a singleton with this interface.

    Flash platform

    Choosing Vkontakte, we have two technologies left to choose from: flash and iframe (html + javascript). To be honest, I don’t digest html games, besides, I’ve been a flash developer for many years. Therefore, there was not even a discussion: they immediately chose flash.

    Flex

    Inside the flash technology, there was a choice: whether to write in pure actionscript or use a flex framework. Pure actionscript provides faster code, while flex has the advantage of quick and flexible development for interfaces. For example, adding a window with statistics in the flex architecture is faster, easier, and more reliable than with pure actionscript. We chose the Solomon solution: all interfaces and the game environment were done on flex, and the battle itself (the main action in the game) was done on pure actionscript.
    They didn’t look towards the 3d engines, they decided not to complicate, but it’s very interesting, of course.

    The following information is for flash players. (It’s the flash player, for a flasher in jargon is a dude who walks naked in a raincoat in parks ... and then you know). If you are not a flash player, you can proceed to the next section.

    When building interfaces on flex, we immediately made skinned components. To do this, each component must inherit from SkinnableComponent.
       /**
    	 * Представление карты.
    	 * */
    	public class Card extends SkinnableComponent
    	{				
    		/** Модель данных для кнопки. */
    		private var _model:CardModel = new CardModel();
    		public function set model(value:CardModel):void
    		{
    			_model = value;
    			modelChanged = true;	
    			this.invalidateProperties();
    		}
    		public function get model():CardModel
    		{
    			return _model;
    		}
    		/** Флаг, показывающий была ли изменена модель. Нужен для оптимизации. Используется в commitProperties */
    		private var modelChanged:Boolean = false;
    		[SkinPart(required="true")]
    		/** Навзвание юнита. */
    		public var nameLabel:Label;	
    		public function Card()
    		{
    			super();			
    		}
    		/**
    		 *  @private
    		 */ 
    		override protected function partAdded(partName:String, instance:Object):void
    		{
    			super.partAdded(partName, instance);
    			if (instance == nameLabel)
    			{
    				if(model)
    					nameLabel.text = model.name;
    			}
    		}
    		/**
    		 *  @private
    		 */ 
    		override protected function commitProperties():void
    		{
    			super.commitProperties();
    			if(this.modelChanged)
    			{
    				if(model)
    					nameLabel.text = model.name;
                }
            }
    


    Here you should pay attention to the following pieces of code:
    [SkinPart(required="true")]
    /** Навзвание юнита. */
    public var nameLabel:Label;
    

    This is an interface element that will be in a specific skin. This fact is noted by the SkinPart meta tag. The required meta tag parameter indicates whether this particular component should be implemented in the skin or can be omitted.

    This method must be redefined to initialize the elements of our skin. How to do, I think, is clear from the code.
    override protected function partAdded(partName:String, instance:Object):void
    


    It also makes sense to override this method:
    override protected function commitProperties():void
    


    When changing data, it is best to assign new data values ​​to the components in this method. This will affect the performance of these components for the better. The fact is that this method is called only when the screen is redrawn, thus postponing the resource-intensive component change until it is really needed.

    We store data separately from components, in models:
           /** Модель данных для кнопки. */
    		private var _model:CardModel = new CardModel();
    		public function set model(value:CardModel):void
    		{
    			_model = value;
    			modelChanged = true;	
    			this.invalidateProperties();
    		}
    		public function get model():CardModel
    		{
    			return _model;
    		}
    


    Note, when changing data, we only save them, mark the data model changed and call the invalidateProperties () method. This method tells flex that the data has changed and you need to call commitProperties (). You cannot call commitProperties () yourself; it is called, as I said, when the screen is redrawn.

    The data model with us is just a structure with fields describing the model.
        /**
    	 * Модель данных для карты.
    	 * */
    	public class CardModel
    	{		
    		/** Имя карты. */
    		public var name:String = "";	
        }
    


    A skin might look like this:
    
    		[HostComponent("view.components.Card")]
    	


    Here it is worth paying attention to the Metadata block. It indicates the "native" component for the skin.

    Using a scheme with the separation of the component into its mechanics and skin, we refuse to bind (Binding), which is considered bad practice. And now we can now have several presentation options for one component, change them on the fly, and it’s easy to easily alter them painlessly, up to a complete design change.

    Flash XML Graphics

    Another technology we used is FXG or Flash XML Graphics. This is a vector format for graphics developed by Adobe based on XML. It is convenient in mxml. Having a picture in this format, in mxml code it is added with one single tag. Well, using vector graphics, the size of the application we got 3 mb. The truth slows down, bastard. In order not to slow down, we hung everything with the cashAsBitmap value equal to true.

    Architecture

    When building architecture, we set the goal of quickly creating game clones in the future. Therefore, we divided the game into the following levels:
    • Core
    • Logics
    • Data models
    • Representation
    • Skins

    The kernel takes over the communication with the server. Logic stores game mechanics - the basis for clones. Models describe data and nothing more. Representation is the level of interfaces and interface mechanics. And skins are already what is visible.
    The mvc architecture is reviewed here, but we did not use ready-made frameworks (pureMVC, mate) that implement this architecture to simplify development. They decided only to ideologically adhere to the principle of separation of logic and representation.

    The mathematical model of the battle

    Here, the interesting question is the synchronization of the game world in two clients. After all, our game units do not go around cells. They have real coordinates on the plane, different sizes, collide with each other and avoid obstacles that arise.
    For synchronization, we use accurate simulation of the events of the game world, which is achieved by two points:
    1. Any condition mat. patterns described by integer data
    2. Time is cut at fixed intervals of 20 ms, and recounting mat. models are only possible for +20 ms. If you need more - mat. the model is recounted several times. If less, it is not recounted at all.

    Mat. the model works separately for us on its own, in no way connected with the representation of the battle on the screen. Thus, we can quite easily make another presentation on other technologies, and when it is ready to provide the client with a choice. For example, we plan to do sprite graphics in the future through stage3d.

    Server

    We decided to do the server part in PHP. In fact, the choice was the following - to implement the client-server protocol on sockets or on HTTP. And having made a choice in favor of speed of development and guarantees of stable work at the client, you have chosen the latter. Namely PHP - because We have vast experience in this area.

    The transmission of messages from the server to clients, and from the client to another client, occurs through the mechanism of periodic polling of the server. With the growth of gaming online, this gives a large load on the server, so we selected all the polling mechanisms on separate servers. In total, we have 3 types of servers:
    1. Main server. He is so alone with us. It stores the data of all players, processes changes to this data (for example, when buying something in the game in the store), and distributes the players who entered the game to the Lobby and Battle servers.
    2. Lobby server. Each player who logs into the game is immediately assigned the least loaded of the available Lobby servers. Nevertheless, each client receives a complete list of all Lobby servers, and sends the number of their Lobby server to all of them. Thus, if some other client wants to send us (the client) a message, let's say an invitation to join the battle, then from his Lobby server he will find out which server we are on, and after that he will send a message specifically to our Lobby- server. We, in turn, just by polling our Lobby server every 5 seconds, we learn about the incoming message. Similarly, the Main server can send the required message to any player.
    3. Battle server When there are two players on the Main server who want to engage in battle with each other, they are assigned the least loaded of the available Battle servers. Then, through the mechanism of Lobby-servers, they are sent invitations to join the battle, indicating a specific Battle-server, the keys to the already created battle, and everything else. On this Battle-server, players are already quietly exchanging information with a polling frequency of 2 seconds, and absolutely do not interfere with the rest of the server system.


    Servers also communicate with each other via the http protocol. But there is one subtlety - in many cases, sending an http request without waiting for a response is used. For example, when creating a battle: we create a unique guid key, send a command to the selected Battle server to create a battle with this key, without waiting for an answer, we send invitations to the lobby servers of the players to the selected Battle server with the same key, and again without waiting for an answer , complete the processing of the request.

    Thanks to such a system, we are able to dynamically change the performance of the server platform for hot (with the exception of the Main server). We simply commission additional servers if necessary. And thus, we are hosted on cheap VPS-servers, and we don’t have to pay in advance for an expensive and powerful server. In addition, the hoster for a fee can increase the performance of VPS without even turning it off.

    Into the account of the Main server: we hope that we removed most of the load from it by introducing the system from Battle and Lobby servers, and at first one of the most powerful VPS will be enough for it, then we will transfer it to real server hardware. Well, we’ll understand the statistics gathering campaign whether there is a need for further optimization of the server part of the game.

    MMO technology

    In our game there is the possibility of playing on the network either with a friend or with a random opponent.
    A fight with a friend is created through the mechanism of invitations to the battle. I can throw an invite to my friend, he, in turn, can either accept it or reject it.

    What is interesting here is that this time we decided to abandon the tables of the invites themselves, lists of missed invites, and other crap. Already ate with this in a previous project. The fact is that it seems that it is simple, but in practice there are a huge number of unforeseen situations. What if the player is offline? And if it’s kind of like online, but the Internet has just been cut to him and no one knows about it yet? And if he even deleted the application, but remained in the database? And if he clicked to accept an invite, but I managed to cancel and already threw another player? And if I threw an invite and updated the page? Well, etc. There is quite a lot of confusion.

    Therefore, we made everything simpler: in our table of players there are only two fields: state and opponentId. state determines whether the player is free, whether he threw an invite, whether he sees a message about an incoming invite, whether he is in battle. opponentId - I think it’s clear why we need it. You can throw an invite only to free players. And even after a sudden reload of the browser page, the client quickly restores all dialogs regarding invitations to battle.

    The battle with a random enemy is implemented through the selection of the enemy by rating. The player sends a request. The application is in the database for 15 seconds, after which it is processed. Processing takes place with a script that runs every 10 seconds. This script selects pairs of applications with the nearest ratings. But not exactly the closest - it is used by a random in a certain range. And the range is gradually expanding. First of all, the applications with the lowest rating are processed - in order to ensure the best processing of newly arrived customers. Because applications diverge very quickly, this script does not require optimization. Therefore, I will not write about algorithms here - everything is simple and linear.
    A delay of 15 seconds is needed, because without it, every second application will start the battle with the first, and no ratings will be taken into account.

    In custody

    Forgive me, Habr, a sinful head for the lack of pictures, graphs and tables, but all the guts of our game are in front of you. Advise if you can. Yes, I forgot to say. I’ll write the next article about monetization, how much it cost and I will show the first results.

    Also popular now: