The so-called .io-games are browser-based massively multiplayer action-games in which a lot of people struggle with surplus free time. Massively multiplayer - this means that the game is a multiplayer crowd of a large number of (hundreds +) players who play in a common location. It is believed that everything started with the game Agar.io (cells in a Petri dish). Other successful examples include Slither.io (snakes) and Diep.io ( tanchiki ). If you believe the statistics, then every day millions of players play these games. Today, there are dozens of different .io-games, most of which can be found by downloading at the request of the “ io games list ”.
I will talk about how we made our .io-game Oceanar.io - a game about fish and other marine inhabitants, I will try to focus on the issues of the overall technical structure of the whole system, and will give some modest advice.
I will try to describe the device of the system as simply as possible, in a format in which it would be most understandable for me - on the fingers, without any holes in the code.
First you had to decide what the game would be about. Having considered the game mechanics of the two most successful representatives - Agario and Slizario - we decided that the game should have the following properties:
We considered that it would be original if it’s not the power of the game creature that grows directly, but the number of these creatures. The second idea is the ability to combine pairs of weaker creatures into one more powerful one. Thus, in the beginning, we have one creature that eventually grows into a pack, and the pack collapses back into fewer, more powerful creatures. A pair of creatures of the first level can collapse into one creature of the second, a pair of creatures of the second - into one creature of the third, and so on. So we get exponentially slowing down pumping, which can be rocked almost an unlimited amount of time.
Now it remains to choose the artistic component. The choice was between a flock of microorganisms - viruses, bacteria, protists, prions, and a flock of marine inhabitants - fish, crabs, jellyfish. After some reflection, the choice was made in favor of the fish. At this finish with the art part, and move on to the technical :)
I have no experience in creating serious MMO-games, but, having bumped on my past projects, as well as after reading about the experience of various developers ( for example ) I had the opinion that the client-server architecture of .io-games is not fundamentally different from the others MMO-games - it is based on about the same ideas. As one of the important (and pleasant for single developers) differences in .io-games, I would single out the essentially simplified game rules: if a serious MMORPG needs to play an entire rule in a serious MMORPG to calculate the damage from an ax hit on a goblin head on a server, then c. io-game it will be a simple formula in one line of code. That is, the .io-game is the same MMO with a large number of players, but much simpler in terms of organizing its insides. Simple enough to be written by one programmer, both client and server.
The game client is a browser with an html page open in it. The technologies are the same as in most other .io games, namely: programming language — JavaScript, graphics — WebGL , interaction with the game server — via WebSockets . It so happened that .io-games are not made to sound (although all the technical capabilities for this are available), so we decided that our game would be without sound (so as not to attract too much attention from the authorities at work).
Simply speaking, the whole movement occurs on the server, and the client is only a monitor-visualizer of this movement. All that a client can do besides passive visualization is to send player actions to the server (in our case, the coordinates of the mouse cursor and the state of its buttons). The client is not able to do anything else, and should not be able to - no game logic should run on the client, otherwise it will instantly become an opportunity for cheating. Thus, from a programmatic point of view, a client is an object that, among other things:
Here are examples of messages and actions for their processing by the client:
Show fish with creature_id, which is in position, has velocity velocity vector, belongs to player with owner_id, and is creature_type_id.
From the point of view of JavaScript, it will be for example the following object:
{ message_type_id: 1, creature_id: 68328, creature_type_id: 2, owner_id: 9306, position: { x: 1.436, y: -39.32 }, velocity: { x: -0.17235, y: -0.1157 } }
Having received such a message, the client will add the fish to the fish map, and when it comes to rendering the game scene, the fish will appear on the screen.
A fish with id1 bit the fish with id id2.
Having received this message, the client will take the coordinates of the fish with id2, and add a bubble with the same coordinates to the bubble array, as well as a blood stain to the blood spot array. Further drawing will display them on the screen.
Thus, it can be said that the messages from the server to the client are basically notifications that something happened in the field of view of the client. Something the client should know about in order to notify the player about it in a correct and understandable way (graphically). Examples of other types of messages - “the game is over with such a score”, “a player with such a name entered the game”, “a list of top 10 players”, “a list of all players and their coordinates” (to display them on the map) , and so on.
The server is the application within which the whole game takes place. The server consists of game rooms. The game room is a copy of the game location. The room has adequate geometric dimensions for the gameplay and a limit on the number of players in it. When all the game rooms are full, the server creates a new room, and players are gradually distributed evenly between the occupied and free rooms. Game rooms are not connected with each other, and work completely in isolation from each other. At the topmost level, the server is able to accept incoming connections, choose a suitable game room, if necessary, create a new one, and send the player to the selected room.
Our server is written in C ++, Boost is used from third-party libraries, the network part is Boost.Asio. Development and testing - Visual Studio / Windows, production server - GCC / Linux.
Here is the whole game logic. A room is an object that, among other things:
The non-fat pseudo-code game room interface:
class room { methods: join(user); update(dt); dispatch(protocol::kill_creature); dispatch(protocol::consume_food); dispatch ... dispatch ... dispatch ... properties: container<creature> creatures; container<food> foods; container<observer> observers; }
This is a function that implements changes in the game world that have occurred over a fixed period of time dt (in our case, 0.1 seconds), I will call this function update. In general, the game room update is the update call for game objects, that is, the update call further down the aggregation ladder, from the general to the particular. A simple example: let us have a fish located in the position coordinates and moving with the velocity vector velocity. Then her update function is the calculation of her position in time dt:
next_position = position + dt * velocity;
In addition to moving, the fish can decide for this dt to go to some other place, and then, in addition to updating its coordinates, as a result of the update, its velocity velocity velocity can change. On the pseudo-code, the update room looks like this:
room::update(dt) { message_queue.dispatch(); // , , foreach(creature in creatures) { creature.update(dt); } foreach(food in foods) { food.update(dt); } foreach(observer in observers) { observer.update(dt); } spawn_food(); spawn_npc(); }
As a result of the execution of the underlying update (for example, the creature’s update), messages can be sent to the room, which will be processed on its next update. For example - a crab bit a fish, and the fish died. You need to remove the dead fish from the container of fish, create food at the site of the dead fish, and also notify players about the death of the fish and the appearance of food. To do this, the crab inside its update will send a message to the room stating that it killed the fish, and the room during the dispatch of this message will perform all the necessary actions - removing the fish, creating food, notifying customers.
In a number of algorithms that implement the logic of the game, you need to be able to sort through objects closest to this one. For example, to show the user all the fish in his field of view, or to check whether the fish is going to bite someone from his environment. If we iterate over pairs of objects according to the principle “every with each”, we will get quadratic complexity, which we do not need, neither for the CPU nor for the traffic.
In order to get rid of the quadratic complexity, we use the following method: the space is divided into cells, and for each cell is stored a list of objects that are in this area of ​​the playing space. Access to the list of cells in its coordinates has a constant complexity.
If we are interested in the list of objects closest to this one, we request lists of the cells in which the object is located, as well as lists of objects from neighboring cells. A total of 9 cells.
The division into cells is done in two independent duplicate layers - small cells for checking near interactions such as collisions, bites, eating, and large cells for resolving visibility issues - hitting objects in the player’s field of view.
Thus, the complexity of the algorithms remains quadratic, but not the total number of game objects in a room, but only their number in the field of view of the algorithm, is squared.
A separate question is the protocol of interaction between the server and clients. By protocol, I mean the description of the set and structure of messages that the server and clients will exchange, their serialization when sending, and de-serialization and scheduling upon receipt. I used my own simplified bicycle solution - the code generator from the JSON package descriptor (as it happens). If you need a ready-made serious solution, not a bicycle, that is, ru.wikipedia.org/wiki/Protocol_Buffers , which is also a code generator.
The multiple producer single consumer queue is used to send messages to the room. To send messages to customers - single producer single consumer. Both types of queues - samopisno-cycling. It was probably possible to use Boost.Lockfree , but, again, it just so happened. For (de-) serialization and dispatching of messages traveling within the server, the same code-generated mechanism is used as for sending messages over the network.
I tried to describe the overall structure of the game and some of its individual, important points in my opinion, so that you have a complete picture of how the whole system works. I hope I did it :)
If you are going to make your .io-game, then I can only say one thing - take it and do it! :) And here are some modest tips:
Together with the client and the server immediately make bots. And bots must go to the server on the network, using the same protocol as the clients, and “see” the same information as the real players. This greatly facilitates debugging, and also allows you to assess the resources consumed in conditions close to combat. How many online players will the server pull? When we rest in the CPU or the width of the network channel? These and some other questions will help you answer the bots, thereby saving you from unpleasant surprises after the release. In addition, bots will make the game not too boring, while there are few live players in it.
So, minimize network traffic. Usually, the hosting providers have either paid traffic on the meter, or a prepaid package with the option of re-purchasing, or unlimited, but with an asterisk-footnote on which there is a reservation that it’s not really that unlimited - you will be waited for punitive measures in the form of, for example, cuts the width of the channel, or else you have to pay from above. Therefore - carefully consider your protocol and save every byte! We have 10,000 live players, played for the day, generated for the day about 200 gigabytes of gaming traffic. Perhaps this may not seem like much, but do not forget that the volume of consumed traffic will grow with your audience. A few saved bytes in a single packet can save hundreds of megabytes or even gigabytes of traffic per day.
Keep traffic statistics with the layout of the type of game packages. Most likely, you will see that despite the fact that you have several dozen types of game packages, most of the traffic falls on several of them - they should be given special attention.
If you want to make your game including iframe-game VKontakte, you will have to forcefully move to HTTPS, since once vk.com itself works through HTTPS, then the same protocol will be required from the iframe inside it. A website that works on HTTPS can only work with SSL web-sites.
Here is the full list of technologies you need to make an .io game:
Our game is still in beta, but you can see the result right now: oceanar.io (for now it works only for the desktop, mobile and tablet versions are in the queue).
Thanks for attention. In the comments I will answer your questions.
Source: https://habr.com/ru/post/314864/
All Articles