Idea of the game
I had a long-standing desire to develop a full-fledged game for social networks, but there were no interesting ideas. Once I saw in the appstore a word game that was only in English. I wanted to do the same with the Russian dictionary.
The meaning of the game is to make words from contiguous hexagons. The game has 3 modes:
- game against time (when searching for words, 1 second is added for each letter);
- find a way out of the allotted time (find the words and thus pave the way to the exit from the center);
- free mode (word search and completion at any time).
Monetization
In the original game, the monetization was that one of three types of games was available to the user. The rest are unlocked after 50 and 100 games played.
I decided to do a similar monetization. But later he added the functionality of word hints. Tips can be purchased.
Additional advertising:
- VK - dating service and preloader
- OK - offer traffic management system
Time spent
The development was conducted from scratch (including the study of the framework) in its free time from the main work, in the first month is very active.
The schedule of expenses of pure time for development (does not include time for documentation and forums):

Theory
First of all, before developing the game, I had to study the features of mathematics with hexagons. Great article that covers everything I need to know
www.redblobgames.com/grids/hexagonsChoosing a development environment
The development was divided into 2 parts - into 2 autonomous projects:
- JS code of the game itself;
- PHP code for saving / issuing player statistics and additional files for interacting with social networks.
Initially, the idea was to use a cross-platform framework to develop a game for the browser and then port it to ios and android. Therefore, the choice fell on the cocos2d framework version html5.
The main disadvantage of this framework is bad documentation, some things had to be found in the source code. But the use of this framework was bribed by some of its popularity, as well as the participation of zynga in its development. In general, I was not disappointed in him.
A feature of this framework is that everything on the screen is divided into layers and sprite objects. Which by default are drawn with a frequency of 60 frames / sec. You can also activate forcibly or automatically selecting a webgl mode that enables hardware acceleration for graphics.
Graphics
I ordered all the graphics on the freelance site after the prototype was ready. Separately, I had to order the icon, which has become more advantageous.
Some technical issues and development features
Problem 1 - sprite caching
If you look at the main screen of the game, you can see a lot of objects - hexagons and letters. In two games, their total number is about 5 thousand. This caused the first problem - the framework redraws them all 60 times per second and slows down terribly.
To solve this problem, there was one solution:
- Use letters as pictures;
- Combining all the pictures into one file. Using the Texture Packer program, all the pictures were packed into 2 png files. One file was used for images in the main menu, the second - in the game itself. It saves one common picture with sprites and additionally attaches an xml file with a description of the boundaries of each sprite. This is all imported by the framework and stored in memory;
- Using a special container SpriteBatchNode. According to the documentation, all the sprites included in SpriteBatchNode will be rendered in a single WebGL rendering call, and all the sprites that are not included in this component are drawn individually. Be sure all files added to the container must be in the same file.
Sample code that uses the SpriteBatchNode containervar size = cc.director.getWinSize(); var x0 = size.width / 2 - (3 / 4 * this.game.tileWidth * this.game.size) / 2; var y0 = size.height / 2 + this.game.tileHeight * this.game.size / 2; var hex = cc.textureCache.addImage(res.Game_png); this._hexBatch = new cc.SpriteBatchNode(hex, 50); this._tableLayer.addChild(this._hexBatch); for (var i = 0; i < this.game.size; i++) { for (var j = 0; j < this.game.size; j++) { var layer = this.game.table[i][j]; if (!layer || layer == '*') { continue; } var height = Math.sqrt(3) / 2 * layer.width - 2; var width = layer.width - 2; var x = x0 + i * 3 / 4 * width; var y = y0 - j * height / 2 - (j + 1) * height / 2 - (i % 2) * height / 2; var border = new cc.Sprite('#' + layer.hexagon_border); border.x = x; border.y = y; this._hexBatch.addChild(border); layer.attr({ x: x, y: y }); this._hexBatch.addChild(layer); } } this.addChild(this._tableLayer); this.wordPreviewLayer = WordPreviewLayer.create(); this.addChild(this.wordPreviewLayer);
The only problem is that it only makes sense if the user has an active WebGL, without hardware acceleration, it will not work in the same way.
Problem 2 - events
The whole project is divided into separate components, each of which lies in a separate class and file. They do not directly contact each other. Interaction is implemented using global events. Cocos2d contains the EventManager class for working with events.
A number of own events were created.
Event Initialization Example var Timer = cc.LabelBMFont.extend({ started: false, paused: false, lastTime: null, timerLength: 0, alert: 20, listeners: [], init: function () { this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_START, function (data) { this.start(data.getUserData()); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_STOP, function () { this.stop(); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_PAUSE, function () { this.pause(); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_CONTINUE, function () { this.resume(); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_ADD, function (data) { var seconds = data.getUserData(); this.add(seconds); cc.eventManager.dispatchCustomEvent(EVENT_TIMER, this.timerLength); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_END, function (data) { this.timerLength = 0; this.stop(); }.bind(this))); return true; }, cleanup: function() { this._super(); for (var i in this.listeners) { cc.eventManager.removeListener(this.listeners[i]); } },
Event call example:
cc.eventManager.dispatchCustomEvent(EVENT_TIMER_START, this.timer_start);
When working with custom events, there are also some features: after an object is destroyed, the eventManager will still consider it subscribed. To solve this problem, a local array of listeners is filled in for each object, and when the object is destroyed, the destructor method is always called cleanup, in which I remove the custom methods that have been added.
Problem 3 - mobile version
With the mobile version of the game did not happen.
Firstly, the game is launched in a mobile environment in the SpiderMonkey emulator, in which, for some reason, caching did not work. Because of what the game is terribly slow on the iPad.
Secondly, some constructions written by me caused an error, the code needs to be modified in some places for compatibility.
The porting plan for ios and android is currently postponed.
Achivki
Later in the game were added 50 achivok. Working with them was not so difficult. To implement achivok, we create an array that contains the parameters and the init method to initialize the handler, which will fix the fulfillment of the achivka condition:
Example var achievements = [ { id: 'the_graduate', name: t('The Graduate'), description: t('You completed Tutorial'), game: 'Tutorial', icon: 'graduate.png', pts: 1, init: function () { var event = cc.eventManager.addCustomListener(EVENT_TUTORIAL_END, function () { sendAchieve(this, event); }.bind(this)); } }, { … }];
And we add initialization of all achievements that the user has not earned yet.
Code cc.eventManager.addCustomListener(EVENT_GAME_LOADED, function () { for (var i in achievements) { if (!cc.UserData.achievements || cc.UserData.achievements.indexOf(achievements[i].id) == -1) { achievements[i].init(); } } cc.eventManager.addCustomListener(EVENT_SEND_ACHIEVEMENT, function(data) { var achieveData = data.getUserData(); if (!cc.UserData.achievements) { cc.UserData.achievements = []; } cc.UserData.achievements.push(achieveData.id); }) });
In this example, when the user completes the training level, a pop-up message about the earned profit is displayed to the user, a request is sent to the server, and the feedback is added to the array of information about the user.
Word search
When a game is loaded from the server, a dictionary of words is loaded, which is searched by selecting each letter. Initially, a simple array with words was used, which affected the overall performance, as the dictionary contains about 150 thousand words. When I started looking for a solution to this problem, I came across the concept of Trie or Prefix Tree. As a result, the original array was converted to a file in trie data format. This solution has significantly reduced the brakes in the allocation of each letter of the word.
Build project
Before you transfer the js source to the second project, you need to build and minify all files into one js file. For this, cocos2d already has a ready-made build.xml file, which has been updated. As a result, the assembly is performed by one command in the console - ant. We simply copy the finished file to the second project.
Statistics collection and integration with social networks
As a backend, the choice fell on symfony 2 and DB mongo just because for me it is a quick and easy way to implement the backend part.
Here you can select only one feature - for each social network a separate integration file was created and build.xml created 3 minified files for each social network.
Web server optimization
The entire project is hosted on one vps server from digitalocean with a standard rate of $ 10: 1 core cpu, 1Gb ram, 30Gb ssd. OS debian, nginx + php-fpm web server. Db mongodb.
To optimize traffic, gzip optimization was enabled. But it increased the load on the processor at the time when there was a lot of traffic to classmates. As a result, the following solution was implemented:
gzip_static on;
For each js file, the build.xml script created a compressed version with the .gz extension. As a result, nginx simply took these files and gave them to the client without any load on the processor.
Statistics
The game was first launched in the VC. The day after approval, the game was added to the new section.
A separate splash was after 2 weeks, when I ordered a new icon, which looked more logical.
With Facebook is not a very clear situation. Yet it seems to me that there is no point in crawling there without advertising - there are many mobile games in the catalog.
Statistics next. In early January, a clear surge is seen, which abruptly went to zero.
To transfer the money earned, you must provide documents. Here, too, I did not understand - you can register as a private person, but they also ask for the OGRN.
To start the game in the OK you need to have the PI or legal entity. But you can find people who already have their games in OK and publish through them, agreeing on the amount of 10% of the funds transferred to the p / s account (OK takes a little more than 50%).
In OK, the statistics look more interesting:
Google general statistics since launch:
Also, Google has the ability to save events. For example, there are such statistics on events in the game:
CPU load statistics:
Total
Expenses
- The development took about 200 hours of free time from work;
- Design - 5500 rubles.
"Income"
VC:
- Users - 31,000 people;
- Payments for the entire period - 84 votes;
- Advertising revenue - 1500 rubles.
OK:
- Users - 171,500 people;
- Payments for January - 27660 OK;
- Advertising revenue (for 2 weeks) - 2600 rubles.
FB:
- Users - 12,900 people;
- Payments are $ 4.70.
Results
It can be concluded that for some earnings you need a game with a mandatory donat. Also, all types of monetization and advertising must be connected immediately. Since I was not familiar with popular advertising sites and the actual amount of traffic, I didn’t connect them later when the first wave of traffic passed.
It is also necessary to perform load testing, since it is not known in advance how the game will go.