📜 ⬆️ ⬇️

Technical preparation of one game created by independent developers

Hello, severe, but fair habr!

I want together with you to dissect one game, which I wrote together with my good friend. In terms of mechanics, a game is a real-time battle between two players, each with a deck of cards. And the cards, in turn, generate fighters who already independently rod on the enemy's bunker, incidentally crumbling to the mince of enemy soldiers. In addition to the battle in the game there is a shop with cards; headquarters where you can build a deck and swing characters; an arena where you can run a quest or a real battle; Well, the bank where you can get game currency. Let me remind you, we are independent developers, therefore we are limited in resources and many solutions are not perfect.
How to start inventing the game here: habrahabr.ru/post/142490

Let's start the preparation.
')

Social platform

Following the principle of KISS, we decided to immediately make a game for social networks because there is already a ready authorization mechanism and wide distribution possibilities. In general, the easiest way to be heard in them. We chose Vkontakte because of a younger audience and more experience 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 change social networks like gloves. To do this, we have a single interface that each specific 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 do not digest games on html, besides, I have been a flash developer for many years. Therefore, there was not even a discussion here: we immediately selected flash.

Flex

Inside the flash-technology there was a choice: whether to write on pure actionscript or to use the flex-framework. Pure actionscript gives faster code, and flex has the advantage of quick and flexible development for interfaces. For example, adding a window with statistics in the flex architecture is faster, simpler and more reliable than on pure actionscript. We chose the Solomonic solution: all the interfaces and the environment of the game were made on flex, and the battle itself (the main action in the game) on pure actionscript.
In the direction of 3d engines did not look, they decided not to complicate, but very interesting, of course.

The following information for flash players. (It’s flushists, for the flasher in the jargon is a dude who walks naked in a raincoat in the 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 be inherited 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 marked by the SkinPart meta tag. The required meta tag parameter indicates whether this particular component should be implemented in a skin or can be omitted.

This method must be redefined to initialize the elements of our skin. How to do it, 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 change of the component 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; } 


Notice, when data changes, we just save them, mark the data model with the changed one and call the invalidateProperties () method. This method tells flex that the data has changed and you need to call commitProperties (). You cannot call commitProperties () by yourself, it will be called, as I said, when you redraw the screen.

Our data model is simply a structure with fields describing the model.
  /** *    . * */ public class CardModel { /**  . */ public var name:String = ""; } 


Skin might look like this:
 <?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:components="view.components.*"> <!-- host component --> <fx:Metadata> [HostComponent("view.components.Card")] </fx:Metadata> <s:Label id="nameLabel"/> </s:Skin> 


Here it is worth paying attention to the Metadata block. Here you can see the “native” component for the skin.

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

Flash XML Graphics

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

Architecture

When building the architecture, we aimed to quickly create clones of the game in the future. Therefore, we divided the game into the following levels:

The kernel takes over the communication with the server. Logic stores game mechanics - the basis for clones. Models describe the data and nothing else. Representation is the level of interfaces and interface mechanics. And skins - this is what you see.
The mvc architecture is viewed here, but we didn’t use ready-made frameworks (pureMVC, mate) that implement this architecture to simplify development. We decided only to adhere ideologically to the principle of separation of logic and representation.

Mathematical model of combat

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

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

Server

We decided to do the server part in PHP. In fact, the next choice was to implement client-server protocol on sockets or HTTP. And having made a choice in favor of speed of development and guarantee of stable work for the client, you chose the latter. Namely PHP - because We have a great deal of experience in this area.

The transfer of messages from the server to the clients, and from the client to another client, occurs through the mechanism of periodic polling of the server. As the online gaming grows, this places a heavy load on the server, so we allocated all the polling mechanisms to 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 the players, processes the changes of this data (for example, when buying something in the game in the store), and distributes the players who entered the game across the Lobby- and Battle-servers.
  2. Lobby server. Each player who enters the game is immediately assigned the least loaded of available Lobby-servers. However, each client receives a complete list of all Lobby servers, and sends their Lobby server number to all of them. Thus, if some other client wants to send us (the client) a message, let’s have an invitation to join the battle, he will know from his Lobby server which server we are on and then send the message specifically to our Lobby- server. We, in turn, simply polling our Lobby server every 5 seconds, find out 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 wish to fight each other, they are assigned the least loaded Battle Server available. After that, through the mechanism of Lobby-servers, they are sent invitations to join the battle, indicating the 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 do not interfere with the work of the rest of the server system at all.


The 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, send Lobby-servers of players an invitation to battle to the selected Battle-server with the same key, and again without waiting for an answer , we finish processing the request.

Thanks to such a system, we are able to dynamically change the performance of the server platform (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 do not need to pay in advance for an expensive and powerful server. In addition, a hoster for an additional charge can increase the performance of a VPS without even turning it off.

To the account of the Main server: we hope that we removed the most part of the load from it, having entered the system from the Battle and Lobby servers, and in the beginning only the most powerful VPS will suffice for it, then it will be transferred to the real server hardware. Well, the campaign of collecting statistics, we understand 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 over the network either with a friend or with a random opponent.
Fight with a friend is created through the mechanism of invitations to fight. I can throw an invite to my friend, he, in turn, can either accept or reject it.

It’s interesting here that this time we decided to abandon the tables of the invites themselves, the lists of the missed invites and other Labuda. Already ate with this in the past project. The fact is that it seems that it is simple, but in practice there is a huge number of unforeseen situations. And if the player is offline? And if he is sort of like online, but he just had the Internet cut and nobody knows about it yet? And if he deleted the application at all, but remained in the database? And if he pressed to take 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 easier: we have only two fields in the table of players: state and opponentId. state determines whether a player is free, whether he threw an invite, whether he sees a message about an incoming invite, or whether he is in a battle. opponentId - I suppose, I understand why. Throw an invite can only free players. And even after a sudden reload of the browser page, the client quickly restores all the dialogs concerning invitations to battle.

Fight with a random opponent is realized 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 is a script that fires every 10 seconds. This script selects pairs of applications with the nearest ratings. But not exactly the closest - is used randomly in a certain range. And the range is gradually expanding. First of all, 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 the algorithms here - everything is simple and linear.
A delay of 15 seconds is needed because without it, every second application will start the fight with the first one, and no ratings will be taken into account.

In custody

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

Source: https://habr.com/ru/post/142884/


All Articles