Hello! I want to tell my story of creating a browser-based online game for social. networks. In the article I will try to consider everything from the beginning to the end, from the idea to the 10th restart. The article was published not small, but detailed. Perhaps some of the features applied in the game will seem obvious to someone. So, anyone interested in learning about the horror that I went through, I ask for a cat! (I wonder maybe the most novice igrodelyam and holivarschikam)
Introduction. (You can skip this)
The path to the idea of ​​the game was very long. It all started with the fact that I knew jQuery. Yes. It was a golden time. With the help of blunt animations, I did all sorts of fuck like emoticon movement across the field to the place of the mouse click. When this level reached perfection, I wanted to add onlineness to this “game”. And then it started ...
The first thing that occurred to me was sending a request to the server every N seconds if the second player had done something. Everything was done in PHP + MySQL. Information about the place and time of the click was saved in the database and issued to players upon request. Source miracle unfortunately not left, well, okay.
The next vital step was performance. The option with constant requests to the server naturally disappeared, and there was a need to look for other solutions. I went through all sorts of Comets, flash drives ... but I couldn't even get all of this on the server. And then suddenly I stumbled upon NodeJS. It was something. I was happy as a child doing the first tests, and only when I learned about Socket.io ....
Well, you understand me =)
')
It was all for me how to discover America, I didn’t sleep at night and wrote code, did tests ... when I noticed that my game was stuck in place. A little thought, I began to make my first creation, similar to the game. Again ... the customer message was on jQuery, the same animation, the same movement of emoticons. Everything was gradually improved, a chat was added, several types of weapons (aha, emoticons were shooting). The large map was a regular 10k x 10k pixel image. Then I learned about Chrome Dev Tools. Seeing how my brainchild eats my memory, I began again to delve into the depths of Google. Learned that HTML5 already exists with its Canvas. This was another reason to rejoice. Again, sleepless nights of studying API (the word of the night is in the plural, because I generally knew JS extremely superficially, and now it's time to work on them closely). After a couple of weeks, my “game” was rewritten into pure JS and HTML5. At that time I was working in my college as a lab technician, and we sat down and tested my crutches with the same lab technicians. A couple of days later, it became boring, and I began to dive into 3D ...
Oh, this is 3D. Stupid attempts to create 3D shapes in the browser basically did not lead to anything. That is, they turned out to create on the page, even move between the red balls and gray krakozyabram, blended in a blender. Surprisingly, while writing this article, I discovered that on a server this night called “wife in the night shift” is still alive.
Link for especially curious. - You can even rotate the mouse and move the arrows. TreeJS seemed to me very difficult and again Google came to my rescue. There I learned about Unity3D. In general, I have a stupid habit of doing stupid performance tests. When I first started the unit, I was surprised at how conveniently the interface was made, and even without having the skills to work with such programs, I managed to make a map with mountains, paths and waterfalls. I didn’t grow together with waterfalls, I accidentally added several tens of thousands to the map, and when rendering, the video card refused. Well, what to do ... did an excellent laying of bricks.
Crucial moment.
I think many people know the story of the success of the game "Racing on the keyboard"? (I did not find the link to the article, I will be happy to insert it). When I found out how much the developers of SUCCESSFUL applications on social networks earn, I probably, like others, wanted to create my own game for a social network. And I began active work on this. I learned that a unit project can also be put together under the web ... The idea of ​​the game was relatively simple - it had its own line with something and courtesans. To have something to suffer in a unit, I even started negotiations with a designer who can draw mobs, maps, etc. for playing games. Of course, nobody wanted to work on bare enthusiasm, and I decided to do something “simpler” to fill my pocket for the initial budget of a more serious project. This “simpler” was the idea to make 2d online races on HTML5, only not the online one, in which you click on the opponent, you wait, and “you won! life for myself, I decided to make races for racing =) Well, and what ... no turns, less physics ... In short, after half a year everything ended with a test version with 20 cars. I already exhausted to look for all the parameters for machines, t. To. tried to make realistic (even 2d) physics, needed parameters such as aerodynamic drag, torque plots and other characteristics that, as it turned out, were not so easy to find.
But these races were something for me to teach. I studied all the new technologies, invented bicycles from crutches, ran into my own rake several times ... But over the course of this half of the year some ideal client-server skeleton for me has been formed. Specifically, on the canvas client, on the nodejs + socket.io server. Trite, crooked, but it worked. And if it works, then don’t touch it =) Basically, I experienced problems due to ignorance of the language, and even the simplest functions that are already embedded in JS I cycled myself, not knowing about their existence. Something like this looked like a small piece of the server part:
io = require('socket.io'); mysql = require('mysql'); players = [];
(Who is no longer interested in reading but would like to see - the link at the end of the article)
Inside swith, there was a code that sent a specific response to the user. By the way that in case you can write not only numbers, I didn’t know, for this I had to keep in mind all types of messages, of which there were as many as 50. Among them are authorization, the beginning of the race, and the end of the race, and chat, and various different in-game events. I knew that for socket, instead of using switch constructions, you can immediately write socket.on ('message type' ...), but this approach creates an extra portion of spaghetti, and not very big convenience of writing code, so the server part of the final game is written directly exactly. That is, such a skeleton, later, I still found out that in a case, you can write whatever you want =). On the client, the construction is one-on-one, only there is no array of all players, there is only an object of itself (and global), and a huge set of functions (also global) that take on the role of incoming message handlers.
Gradually, I learned the features of JS, NodeJS and socket.io. I remember the first successfully failed test of the maximum number of connections. At 10k + - 250 connections, socket.io lost responsiveness and did not accept new connections. After a long fuss
, this article helped. There everything is described in detail so I will not repeat.
As I said, with the races I butted half a year, and I didn’t have enough strength for more. About the idea of ​​a 3d game, I had already completely forgotten, and all that was left of it was a drawing paper on a room door with a database structure. And so, since the races were too difficult for me, I decided to simplify my task even more, and to make airplanes flying in the air and just shooting at each other.
My game, my game ...
First version
Logic and govnokod
And so, one fine free day, I sat down at work, loaded the interns and began to experiment again with the canvas. The first experiments were unpretentious - to make the square move at least directly (after driving the box in races, I already forgot how I did it and decided to rewrite everything anew with new knowledge). Of course, he began to move after 10 minutes ... then decided to add key management - up and down. And then everything stalled. This canvas with my own matrixes and translates, be they wrong ... there were so many calculations that I could no longer navigate the code. Although the whole "physics" was based on the geometry of the 5th grade, or rather the geometry of various triangles =) He hated trigonometry in school, which he later regretted. The calculations of the triangles were too large and heavy, but after learning the basics of trigonometry, I realized what a fool he was. But even with trigonometry
calculations, the straight-handedness left much to be desired. This is how the working code of the first version of the game on the canvas looks like with the key pressed down:
else if(player[pid].key.up == false){ if(player[pid].key.down == true){ if(player[pid].alfa >= -359){ player[pid].alfa -= (1.4*(time/player[pid].speed)); } else{ player[pid].alfa = 0; } player[pid].x += k2*Math.cos(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed); player[pid].y += k2*Math.sin(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed); player[pid].x = rezak(pid,real,player,k1,k2); if(pid == 'green'){ real['green'].x = player['green'].x + k2*(player[pid].rad * Math.sin(player['green'].alfa*Math.PI/180)); real['green'].y = player['green'].y-player[pid].rad + k2*(player[pid].rad * Math.cos(player['green'].alfa*Math.PI/180)); } else if(pid == 'red'){ real['red'].x = player['red'].x + k2*(player[pid].rad * Math.sin(player['red'].alfa*Math.PI/180)); real['red'].y = player['red'].y-player[pid].rad + k1*(player[pid].rad * Math.cos(player['red'].alfa*Math.PI/180)); } } player[pid].x += k2*Math.cos(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed); player[pid].y += k2*Math.sin(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed); player[pid].x = rezak(pid,real,player,k1,k2); if(pid == 'green'){ real['green'].x = player['green'].x + k2*(player[pid].rad * Math.sin(player['green'].alfa*Math.PI/180)); real['green'].y = player['green'].y-player[pid].rad + k2*(player[pid].rad * Math.cos(player['green'].alfa*Math.PI/180)); } else if(pid == 'red'){ real['red'].x = player['red'].x + k2*(player[pid].rad * Math.sin(player['red'].alfa*Math.PI/180)); real['red'].y = player['red'].y-player[pid].rad + k1*(player[pid].rad * Math.cos(player['red'].alfa*Math.PI/180)); } ctx.save(); ctx.translate(player[pid].x, player[pid].y-player[pid].rad); ctx.rotate(k1*player[pid].alfa*Math.PI/180); ctx.drawImage(player[pid].img, -22,+(player[pid].rad-8)); ctx.restore(); }
I myself am now writing an article and proving to my cat that this is not my code. I’m not saying that there are constants in the code that are chosen by the method of scientific typing ... I consider the rest of the code to be dangerous to the readers. All the problems were due to the fact that each time I had to do a translate canvas, because of which it was very difficult for me to catch the REAL coordinates (those that the user sees) of the plane, and real coordinates are needed, at least, to calculate the amount of aircraft hit (in the code for example, coordinates for the canvas are player [pid] .x, player [pid] .y, and real coordinates are already real ['red']. x and real ['red']. y) - don't ask about the choice of variable names, now I open the code, and there ...: (a clean copy-paste of my code, the comments were just like that, I even remembered that moment, because no not understand how it works requestAnimationFrame)
function animate(id,pid,appid,imagesD,player,real,warID){
By the way, even then all the game logic was transferred to the server. In particular, the purchase of game items, ammo and life during the battle. All counted the server. The calculations are minimal, and the tests showed that the home celeron 2.4 GHz completely holds 10k DIFFERENT messages per second, which then was still far from reality and was fine with me. So the beginning of protection from kulhatzkerov was laid at this time.
Govnodizayn
All the while developing, design was easy. I said goodbye to this idiotic idea only after version 3 of the release. More on this later ... In general, to make it clear, I post the first version of the “design” of the “game”:

As you can see, contrasting colors, round corners, the lack of frames are a bit like a village. “I can shave as much as I can” - so the techies will treat with understanding, and I hope the interface designers have not frightened the picture.
Session, diploma, summer ... lyrical digression
When summer came with their bonuses like a session, or rather state. exams, defense of a non-initiated diploma, problems with the army - I had to forget about the game for a while, and for 2 months (June, July) I had a brain break. And suddenly it dawned on me that somehow I earn little, and began to look for a new job. Somehow it didn't really work out to find something acceptable ... as I opened the console on the main page of Yandex and saw “Do you like to look in the console? Or maybe js can write?
company.yandex.ru/job/vacancies/interface_dev_mail.xml . For the sake of interest, I answered the test questions and safely forgot. After about a week, a letter came from Yandex, “but would you like to have an interview on Skype?” Of course I do! Lip is not a fool =) In short, the interview, I miserably flunked. Namely, I did not know just what this was and how to use it, the designer, new etc ... I decided that it would be nice to train myself and began to smoke JS manuals again. And I realized how cool that is this, new, call, apply ... I learned how to use these things so to speak ... and did something. I sat down to rewrite the game.
First start
And again a little about govnodizine
That summer I was lucky to buy my wife an iPhone and pick up HTC on WP 7.5 for myself. For some reason, I terribly liked the tiled interface. It is simple and straightforward. Really simple and straightforward. And I wanted to start “rewriting” the game with design. For myself, decided that it should be simple and clear ... in short plagiarism with tiles. In short, what happened to me has gone somewhere, so
you are lucky now not to cover your face with your hand in horror, do not even dare to think about it. But take my word for it, it looked much better than the previous version.
Code, code, code again
It was crazy for me to make these tiles, decided to show off (in front of whom?) And make each tile on a separate canvas. Of course I did ... but, forgive me, a
bad word for a comma in Russian , one tile occupied for me for some reason half of the entire client code.
After rewriting everything, it's time to file VK api. Here I would cross out all the text further but ... I will try to express briefly and decently: there is a lot of functionality - there is little documentation. In the documentation, error codes are not all given, half of the parameters are not described, the result is returned in the form of nothing, then for me a meaningless
array of object objects . I drunk all these requests to the API 1 by day per game, for, again, using the method of scientific typing. A matter of habit of course. There is nothing more to say. Guys, write the documentation for your grandmother. Newbies are lost).
Actually, the first launch
The planned release date of the game was approaching, and minor problems arose that needed to be addressed. During development, I used my home server exclusively, but it was clear that I would have to rent everything. I did not have any money, so the ideal solution for me at that time was to rent a cloud server. Selectel fit perfectly.
XXX August is the day of the first failure of the game. The application was approved a week after the application was submitted, but this is even good. During this week I managed to drain almost 10 thousand on the target advertising of VK - my most useless waste of money. 10k flew in a couple of hours. But these couple of hours turned out to be a great experience. With the first players for each garbage NodeJS began to ache and fall, I did not know about try ... catch, and until the game got into the catalog, it was necessary to solve something. I managed to close a lot of holes, but not all, which is why this “release” failed before it began. I had to manually restart the nodejs, in the round-the-clock mode to sit in the console so that God forbid the node lay more than a minute. I also did not know about such a thing as bash, or rather did not want to know.
Some statistics
In the early days, the game was set by 6-7 thousand people, which could not help but please me (of course, there is nothing to compare it with). So, by the way, an interesting (or maybe not) moment happened here.

(circled)
There you can see what date, so the age of the players is not difficult to guess. Be careful in choosing release dates.
Everything, after that, I was completely desperate, the installation went on decline, there was a maximum of one and a half "pilot" online. This is including me.
The only advantage that made me continue to work is the non-sickly offers to buy the game. On the first day there were a lot of offers about the exchange, and the prices up to 80 thousand. On the second day I was offered up to $ 7,000, and now I regret that I did not agree.
Bug work
Shaking my brain about what was
put in the hair shampoo is not so, I decided that it was a matter of whistles, perfections and the absence of social spam on the walls (of course, on behalf of the application). And what do you think I did? Not guessed. I began to rewrite the game again. Started again with design.
Error 1. I thought that a good designer would come out of me.I decided to simplify even stronger, made huge buttons with ugly inscriptions - this is a miracle, I also do not want to show for religious reasons.
Error 2. Few whistles (weapons, body kit, as you wish)There was a lot of work done (on the mistakes, of course). Besides the fact that I added 12 new types of weapons / bonuses, I made a “special effect” for each of them when they hit it - by the way, nothing looked. At this stage, rewriting is justified. There was a certain modularity, thanks to which I can now add even a new weapon per minute, there would be ideas. Immediately was made super protection from kulhackinga.
If it used to be like this: the client fires, the client checks if there is any such weapon, he (the client) checks if the rocket hit the opponent, and if it hit, then only send a message to the server and it sends a message to the opponent that got into it.
It became like this: the client presses the key, the key code is transmitted to the server, the server looks at which weapon the key is assigned to the player, checks whether the weapon is reloaded, whether it is at all, whether it can be used on this plane (also added aircraft each has its own advantages, including which weapon you can use), and a bunch of minor conditions. In the event that there was not a single false, the shot data was sent to both players. If all the same was found false, then the shooter politely (in red capital letters) showed a message like "FLASHING! ....". Actually, if it's interesting like this:
case 'shot': if(Date.now() < players[socket.id].per[msg.typeW]){ console.log(''); } else if(players[socket.id].weapons[msg.typeW] <= 0){ players[socket.id].weapons[msg.typeW] = 0; console.log(' '); } else if(weapons[players[socket.id].plane].weapons.indexOf(msg.typeW) < 0 && weapons.weapons.indexOf(msg.typeW) >= 0){ console.log(' '); } else if(weapons[players[socket.id].plane].bonuses.indexOf(msg.typeW) < 0 && weapons.bonuses.indexOf(msg.typeW) >= 0){ console.log(' '); } else{ if(players[socket.id].wrate != weapons[players[socket.id].plane].rate + players[socket.id].rate && Date.now() >= players[socket.id].per.P){ players[socket.id].wrate = weapons[players[socket.id].plane].rate + players[socket.id].rate; } if(weapons.weapons.indexOf(msg.typeW) >= 0){ msg.per = weapons[msg.typeW].rate*1000/players[socket.id].wrate; players[socket.id].per[msg.typeW] = Date.now() + msg.per; players[socket.id].weapons[msg.typeW] -= 1; players[socket.id].pot[msg.typeW] += 1; msg.hit = weapons[msg.typeW].radius; msg.speed = weapons[msg.typeW].speed; msg.val = players[socket.id].weapons[msg.typeW]; msg.side = players[socket.id].side; } else if(weapons.bonuses.indexOf(msg.typeW) >= 0){ msg.per = weapons[msg.typeW].rate*1000*players[socket.id].wdamage; players[socket.id].per[msg.typeW] = Date.now() + msg.per; players[socket.id].weapons[msg.typeW] -= 1; players[socket.id].pot[msg.typeW] += 1; msg.val = players[socket.id].weapons[msg.typeW]; msg.side = players[socket.id].side; switch(msg.typeW){ case 'I': players[socket.id].life += 10; msg.life = Math.ceil(players[socket.id].life); if(players[socket.id].life > 100){ players[socket.id].life = 100; msg.life = 100; } break; case 'J': players[socket.id].wsuspension += 2; break; case 'L': players[socket.id].kamikadze = true; break; case 'P': players[socket.id].wrate = 100000; break; }; } socket.json.send(msg); io.sockets.socket(players[socket.id].oppSocket).json.send(msg); } break;
A little explanation to the code. players is an array of all players and is identified by socket.id. Each array element is a player object that is created immediately upon entry into the game, by assigning what it returned to MySQL. In fact, between each else if there was a sending error to the user, but I did not find this code. weapons - is an array of parameters of weapons and bonuses.
I made the miscalculation of the hit on the sly: the opponent calculates. How to describe it further ... after a successful shot of 1 player, 2 player draws a rocket in her and looks to see if she has fallen into it (herself). If it does, it sends the message to the server with the content of "what is and in what place on the screen." The server calculates the damage, and sends both players. At the same time, a fantastic special effect is drawn and the rocket disappears. At the same time on the server it is checked how many things are left there, lives, etc. - and if suddenly player 2 has less than 0 lives, then we write everything into the database, send players information about who won and who lost and everyone is happy.
If interested, here is the client's hit calculation code:
for(var i = 0; i < weapons_1.length; i++){ if(weapons_1[i].draw({t:avatars[0].time2, x:avatars[0].x, y:avatars[0].y, ra:avatars[0].ra}) == 'coordLimit'){ weapons_1.splice(i,1); } else if(weapons_1[i].s && Math.abs(avatars[0].x-weapons_1[i].x) < 80 && Math.abs(avatars[0].y-weapons_1[i].y) < 80){ var dl = Math.sqrt(((avatars[0].x-weapons_1[i].x)*(avatars[0].x-weapons_1[i].x))+((avatars[0].y-weapons_1[i].y)*(avatars[0].y-weapons_1[i].y))); if(dl < (avatars[0].w/4+weapons_1[i].w/4)+weapons_1[i].hit){ sock.send($.toJSON({type: 'hit', typeW: weapons_1[i].typeW, i: i})); weapons_1.splice(i,1); }; } }
weapons_1 is nothing more than an array of all missiles launched by an opponent. As you can see, in order not to load the memory, when the rocket leaves the limits of the canvas, it is removed from the array.
The second piece is a small optimization. I do not know how useful it is, but in order not to count each frame of the hypotenuse (in a triangle a rocket-plane), first there is a small calculation of the absolute position on the screen. And if the rocket is too far by approximate measures, then there is no need to consider the hypotenuse.
avatars [0] and avatars [1] are player objects. After reading
this article , for some reason I decided to call it avatars, so it was.
The “event” of hit on the client looks like this: case 'hit': avatars[0].life = msg.life; if(avatars[0].life <= 0) avatars[0].life = 0; if(avatars[0].life > 100) avatars[0].life = 100; $('#life span, #life2 span').html(msg.life); if(msg.damage <= 0){ particles.push(new Effect({type: 'message', text: ('!'), x: avatars[0].x, y: avatars[0].y, t: 1000})); } else{ particles.push(new Effect({type: 'message', text: ('-'+(Math.ceil(msg.damage))), x: avatars[0].x, y: avatars[0].y, t: 1000})); } switch(msg.typeW){ case 'C': avatars[0].ra -= msg.damage*msg.damage*avatars[1].koef; break; case 'E': avatars[0].debaff.E.per = Date.now(); avatars[0].debaff.E.tper = Date.now()+(msg.damage*500); particles.push(new Effect({type: 'message', text: (' !'), x: 350, y: 350, t: 2000})); break; } particles.push(new Effect({type: msg.typeW, x: avatars[0].x, y: avatars[0].y, side: 0, damage: msg.damage})); break; case 'pop': avatars[1].life = msg.life; if(avatars[1].life <= 0) avatars[1].life = 0; if(avatars[1].life > 100) avatars[1].life = 100; if(weapons_0.length > msg.i){ weapons_0.splice(msg.i,1); } if(msg.damage <= 0){ particles.push(new Effect({type: 'message', text: ('!'), x: avatars[1].x, y: avatars[1].y, t: 1000})); } else{ particles.push(new Effect({type: 'message', text: ('-'+(Math.ceil(msg.damage))), x: avatars[1].x, y: avatars[1].y, t: 1000})); } switch(msg.typeW){ case 'C': avatars[1].ra += msg.damage*msg.damage*avatars[1].koef; break; case 'E': particles.push(new Effect({type: 'message', text: (' ! !'), x: 300, y: 220, t: 2000})); break; } if(msg.typeW == 'F') msg.damage = 1; particles.push(new Effect({type: msg.typeW, x: avatars[1].x, y: avatars[1].y, side: 1, damage: msg.damage})); break;
Here case 'pop' is nothing more than a translation of the word "hit", so those who cannot boast of knowing the language can understand me. particles - an array of particles (special effects). There is nothing to comment on. The event of a shot occurs by analogy, but instead of particles, arrays of weapons_0 (my missiles) and weapons_1 (his missiles) are used, with cramming of new objects in them.
Error 3. Many bugs.Apart from minor server crashes due to the abrupt lack of any variables, the main problem for me was ping. It so happened that at work the connector fell off the patch cord, but it wasn’t in stock, and I had to sit on wifi. At work, he is rotten, so that the delays were ensured by quality (150-200ms instead of 18-20 usual). Testing another weapon, I noticed that after some time in two browser windows the picture of the location of the aircraft is not at all the same. And as it turned out, the cause is wifi, or rather ping, and even packet loss. As decided - we read further if you are not tired.
What happens in the game at all. Mechanics.
Yes, there is nothing to say. You press the button - the plane flies up / down. Only now the player starts flying in the right direction, and the opponent does not immediately, for the long wait for the ill-fated messagé that the opponent has long pressed the button up and flies to itself ... Actually this is the problem with the ping.
The more ping - the faster the picture becomes different for two clients. Solved the problem ingeniously - every N time interval (1.5 - 3 seconds) synchronization is made about the real state of affairs. There were of course minor jumps of airplanes during this synchronization (within a radius of 2-3px), but this is not as noticeable as what you are shooting at an opponent, you see how a rocket flies through it, and did not hit it - because in fact the player it may already be in another corner and shoot from there at you. And so here and there.The entire system was built in the spirit of client- (server-check) -client (s).Second run
When I closed the holes, tried try ... catch, learned how to check the server’s negligence (the script below) on the base, drunk the VK API at the level of receiving a photo of the opponent, and issuing TOP 30 players (also with photos, by the way, and even with names), again began to spend money on advertising. This time more meaningfully, right in the application catalog. For moderate 100 votes (700r), I received about 200-300 installations, well, about half of the installed ones were immediately removed, so “150 clean”.Script to check if node is running
By the way, there is still worth noting that I found for myself an amazing screen utility , and without it, leaving the terminal meant death. Now without her as without hands.Again rework
Due to failure in terms of installations and angry reviews of players, I again realized that this was a failure. But in our business the main thing is not to give up! And again what? Yes, I again began to rewrite everything. On the second launch, I made the following conclusions:- Again not enough sociality
- Not enough attractiveness (
shit design takes its own) - Lack of plot
- Difficult calculations on the client (8-10ms for rendering and drawing one frame is a dofig for such a “game” on i7, please note)
- Large server load (30 people online - 10% of one core, I don’t remember about memory, and 5 Mbit traffic)
- There is no training, because of what the players are not at all clear what and how
I started, as always, again with design, and it was already more like the truth. I showed it to friends, said “WOW is much better than that shit!”, But I didn’t feel this WOW, and finally took the first right step - I went to freelancing to look for an interface designer. I found it, talked about it, I ponil about all my problems in the game, the person understood everything and made candy - which was only possible for 12 thousand. (rubles)
This is the final version that exists now. About more, I did not dream.Well, about the design, then there were more small tasks like icons, a background for the game itself, and new effects for the weapon.Learning to do it turned out to be a hemorrhoid exercise for me, and I managed to create tips on each button.We make an incentive to play
Of the new features added, the main achievement I consider to receive medals and bonuses for some merit. Play 100 times, finish off with a machine gun, etc. Naturally with the proposal to place a beautiful picture of medals on your wall. Two birds with one stroke. Social fake was done. By the way, here is the code for uploading a photo to the Vkontakte server: <? $url = $_POST['uploadUrl']; $postdata = array('photo' => '@/path/to/image/'.$_POST["type"].'.jpg', 'app_id' => '3509664', 'api_key' => '1234567890'); $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); $data = curl_exec($ch); curl_close($ch); echo $data[1]; ?>
I posted this code for the reason that I couldn’t find the nichrome in the network, and this is a kind of impression of all that I could find. I had to work with curl for the first time, so it was not without problems with its installation, but this is not so interesting.I'll tell you about the algorithm posting entries on the wall of a user with a picture. More precisely and faster will lead the code with detailed comments. function wall(id, msg, typeMsg){
Such are spaghetti. Eat on health!Client optimization
So, I finally decided to more closely read about the sine cosines and, first of all, get rid of the separate calculations for drawing and real coordinates. There is nothing special to tell, but the code is clearly better. this.draw = function(t){ this.x2 = this.x; this.y2 = this.y; this.s2 = this.s; this.r2 = this.r; this.cosa2 = this.cosa; this.a2 = this.a; this.timeK = (t > 40) ? 1+((t-40)/40) : 1; this.x += this.timeK*this.koef*this.s*Math.sin((90-this.ra)*this.PI/180); if(isNaN(this.x)) this.x = this.x2+(2*(this.timeK*this.koef*this.s*Math.sin((90-this.ra)*this.PI/180))); this.y -= this.timeK*this.koef*this.s*Math.sin(this.ra*this.PI/180); if(isNaN(this.y)) this.y = this.y2-(2*(this.timeK*this.koef*this.s*Math.sin(this.ra*this.PI/180))); this.s -= this.timeK*(this.koef*Math.sin(this.ra*this.PI/180))*(this.wspeed/(this.y/this.wspeed))/4.5; if(isNaN(this.s)) this.s = this.s2-(2*(this.timeK*(this.koef*Math.sin(this.ra*this.PI/180))*(this.wspeed/(this.y/this.wspeed))/4.5)); if(this.s <= 0) this.s = this.koef*0.0001; this.r = this.timeK*this.s*50/this.wradius; if(isNaN(this.r)) this.r = this.r2; if(this.r == 0) this.r = this.koef*0.000000001; this.cosa = this.timeK*(this.r*this.r+this.r*this.r-this.s*this.s)/(2*this.r*this.r); if(isNaN(this.cosa)) this.cosa = this.cosa2; this.a = this.timeK*this.koef*Math.acos(this.cosa)*180/this.PI; if(isNaN(this.a)) this.a = this.a2; for(var i = 0; i < weapons.all.length; i++){ if(this.id && this.per[weapons.all[i]] <= this.tper[weapons.all[i]]){ this.per[weapons.all[i]] += (t > 40) ? t+40 : 40;
Here everything seems to be clear except for the names of the variables; One of the players discovered (and later checked by me) an unpleasant bug - the samoe is abruptly in the upper left corner and everything, you can leave the game. This was due to the fact that when calculating some variables, with a small degree of probability they became === 0, and then they had to be divided, and such a mess was obtained. After the crash into the verification code on the NaN, the bug disappeared and everyone is happy.if (this.opacity> = 0.5) {...} the code of the rendering on the canvas is executed only if the plane is not in stealth mode (yes, there is such a bonus)Another point - the function takes the parameter t. It is essentially needed for weak computers / tablets. Ie this parameter contains infa about how much time a frame is drawn, and if it is more than 40ms (everything works at a speed of 25 frames per second), then all movements of the aircraft must be proportionally increased. These changes reduced the rendering time by about 2-3ms, which is not bad anymore.The next step was the optimization of the smoke from the aircraft, because most of all the memory and processor he ate. About how smoke is implemented, I wrote in this article. To optimize, I made another image - smaller in size and which should not be rotated (gray round gradient), and once I drew it on an invisible canvas (buffer), and then copied from it. Nothing complicated. And, well, I also made the particles be added only when the device has enough relics to draw it (t <10). This gave another minus 3-4ms calculations.Then the biggest hole was found - in drawing the reload rate and quantity for each weapon. In battle, it looks like this:
The arrows indicate the pieces, the rendering of which was the most difficult (arc). I decided that because most of the weapons most of the time is in full recharge (or with no ammo (bottom bar)), then you can simply draw these states on a separate canvas, and if necessary just draw ready (do not draw at all). The most interesting thing is that the optimization of these 32 semicircles gave the greatest effect - ash 5-6ms. And now the mission is complete. With the maximum possible smoke from both players (the fewer lives, the more smoke), the frame began to draw 1-2ms, and without smoke, generally less than 1ms (0.2-0.3 if you take the average). All numbers for Chrome 32.Server optimization
It turned out to be too expensive to check every rocket launched on the server, let alone send an answer to the client (socket.io is not perfect, as it turned out). Therefore, I transferred this responsibility to the client, but in order to save myself from cheaters, there is simply a selective check of questionable users on the server (a smart one built such a thing - each user on the server accumulates a certain amount of errors, after which he gets more careful control , to the extent that I personally check the logs of the player - well, this is quite if the suspicious caught). And the answer from the server in the case of, for example, the absence of weapons does not go - and what, I also have to notify the cheater that he will fail? In general, this removed a huge part of the load on both the node and the base. The result was 5% CPU for 150 people. online.Memory eats all a penny.Any vague customer losses
When advertising was given in the application directory of contact, the statistics of the installation did not correspond to reality, i.e. A lot of users simply did not even wait for the game to load (less than 2 seconds on average - this is not a flash). I started looking for a problem and it turned out to be used by some players (20%) https. And then danced with tambourines. Eventually socket.io and nginx managed to be configured. Here is a piece of nodejs server code that creates an https server socket.io var fs = require('fs'); server = require('https').createServer({key: fs.readFileSync('/path/to/*.key'), cert: fs.readFileSync('/path/to/*.crt')}).listen(1006); io = require('socket.io').listen(server);
There is nothing to comment on, but I spent more than one day on this, and that does not include ordering and installing certificates on the server.Third launch
Or rather - nedozapusk. Vkontakte has the opportunity to place your application in the top 9 of the “New” block for 1000 votes (7 thousand rubles, excluding discounts). I threw in the votes, sent the application, chose the day (right for the then tomorrow), and waited. At night, I received a notification that I was forced to refuse placement, with a proposal to add to the game the possibility of playing with a computer or learning. I decided to do both.Artificial Intelligence
I already thought about the possibility of playing with a computer, but somehow I didn’t want to get into this mess, because I thought it was as long as it took me long. But since I already mentioned above that the code of the game was modular, for the first day I made the opportunity to kill the computer blank, for the second day I made a computer brain and drank it 30 levels. In total, on the server side I added 3 lines (not counting the level config), and on the client lines 30-40. And it turned out to be quite not bad, I myself can’t kill level 25 AI.The AI ​​code is a little: the enemy plane just constantly follows your plane, and in the case that the angle of attack becomes acceptable, it shoots from the whole weapon. Here again, trigonometry of the 7th grade and nothing more. Code:
if(avatars[0].oppId == 'computer'){ var x = Math.abs(avatars[0].x-avatars[1].x); var y = Math.abs(avatars[0].y-avatars[1].y); var gp = Math.sqrt((x*x)+(y*y)); var rad = avatars[1].ra*Math.PI/180; var prom; if(avatars[0].x < avatars[1].x && avatars[0].y < avatars[1].y) prom = Math.asin(y/gp)+rad else if(avatars[0].x > avatars[1].x && avatars[0].y < avatars[1].y) prom = (Math.PI/2)-Math.asin(y/gp)+rad+(Math.PI/2) else if(avatars[0].x > avatars[1].x && avatars[0].y > avatars[1].y) prom = Math.asin(y/gp)+rad+Math.PI else if(avatars[0].x < avatars[1].x && avatars[0].y > avatars[1].y) prom = (Math.PI/2)-Math.asin(y/gp)+rad+(Math.PI*1.5); var sin = Math.sin(prom); var cos = Math.cos(prom); avatars[1].time1 -= 40; avatars[1].time2 -= 40; avatars[1].time3 -= 40; if(avatars[1].time2 <= 0 && avatars[0].levelBot == 0 && avatars[1].time3 <= 0){ if(weapons.learning[avatars[1].learning[0]]){ if(weapons.learning[avatars[1].learning[0]][avatars[1].learning[1]] == '^') $('#learning').append('<br/>') else $('#learning').append(weapons.learning[avatars[1].learning[0]][avatars[1].learning[1]]); avatars[1].learning[1] += 1; avatars[1].time3 = 90; if(avatars[1].learning[1] >= weapons.learning[avatars[1].learning[0]].length){ avatars[1].learning[1] = 0; avatars[1].learning[0] += 1; avatars[1].time2 = 1000; $('#learning').empty(); } } } if(weapons.learning[avatars[1].learning[0]] && avatars[0].levelBot == 0){avatars[1].x = -800} else{ if(sin > 0.02){ if(avatars[1].time1 <= 0){avatars[1].key = 'up'; avatars[1].time1 = 100/avatars[1].wradius} } else if(sin <= 0.02 && sin >= -0.02){ if(avatars[1].time1 <= 0){avatars[1].key = false; avatars[1].time1 = 100/avatars[1].wradius} } else if(sin < -0.02){ if(avatars[1].time1 <= 0){avatars[1].key = 'down'; avatars[1].time1 = 100/avatars[1].wradius} } if(avatars[1].y > 510){ if(Math.cos(rad) >= 0){avatars[1].key = 'up'; avatars[1].time1 = 100/avatars[1].wradius} else{avatars[1].key = 'down'; avatars[1].time1 = 100/avatars[1].wradius} } if(sin <= 0.035 && sin >= -0.035 || (sin <= 0.3 && sin >= -0.3 && gp < 30) || gp < 15){ for(var i = 0; i < avatars[1].all.length; i++){ if(avatars[1].weapons[avatars[1].all[i]] > 0){ avatars[1].per[avatars[1].all[i]] += 40; if(avatars[1].per[avatars[1].all[i]] >= avatars[1].tper[avatars[1].all[i]]){ switch(avatars[1].all[i]){ case 'I': if(avatars[1].life <= 20 && avatars[0].life >= 25){ sock.send($.toJSON({type:'shot',x:avatars[1].x,y:avatars[1].y,a:avatars[1].ra,koef:avatars[1].koef,typeW:avatars[1].all[i],side:avatars[1].sideName,bot: 1})); avatars[1].weapons[avatars[1].all[i]] -= 1; } break; case 'J': if(avatars[1].life <= 50){ sock.send($.toJSON({type:'shot',x:avatars[1].x,y:avatars[1].y,a:avatars[1].ra,koef:avatars[1].koef,typeW:avatars[1].all[i],side:avatars[1].sideName,bot: 1})); avatars[1].weapons[avatars[1].all[i]] -= 1; } break; default: sock.send($.toJSON({type:'shot',x:avatars[1].x,y:avatars[1].y,a:avatars[1].ra,koef:avatars[1].koef,typeW:avatars[1].all[i],side:avatars[1].sideName,bot: 1})); avatars[1].weapons[avatars[1].all[i]] -= 1; } } } } } } }
In addition to the idea, there is nothing to learn from here, I do not advise using the code.The if block (avatars [1] .time2 <= 0 && avatars [0] .levelBot == 0 && avatars [1] .time3 <= 0) {...} is training. It occurs only when playing with level 0 AI. There the essence is that the messages are displayed by letter (fashionably, the type is printed) from the array of messages.Also, the AI ​​has a little brain on the topic of not breaking. At a height below a certain, he begins to ignore you, and tries to steer in order not to die. Well ... and the first-aid kit, he also uses correctly, and not as horrible.Actually, the mission is completed, the AI ​​is ready, the training is there, it's up to the small thing - to wait for the approval of the administration to reapply for placement in that very block. For some reason, the second time I was able to choose the nearest date of placement on March 23, so I am waiting, waiting. And if it will lead to some kind of permanent online and profit, then I will definitely write an article about money withdrawal, taxes, and so on. So far the game has brought 20 votes, so there’s nothing to talk about.findings
- Can't do like me
- Test more. Ask friends, preferably from the age group of players that you expect to see in the game. You can simply show the picture and ask what is convenient there.
- If you are not going to add over 100,500 new features in the game - do not rewrite all the code! In general, it is better to start with something simpler.
- All that is written above is only a small part of all the horror that I encountered. So be prepared for the difficulties!
- Began - finish to the end. BUT! Only if you are confident of success and are pursuing a specific goal and not having fun ... otherwise why?
- Do not try to wash eggs with expensive shampoo. Soap is the most it. (A metaphor that can be understood differently, but the meaning will not disappear)
ps I do not recommend using the code written here. I am sure that there are many more elegant solutions, which I will be glad to hear in the comments. Thanks for attention!
Update With a clear conscience, in view of the large number of requests to show the link, I write it here: reference