I was lucky at work to do what I loved in a strong team with good people. We built and demolished castles in the air, fought with windmills, introduced, maintained and did not worry. Once I wanted to build my castle. Judging from different sides, I decided that it will be small, and I will build it myself, it will be a hobby project. There were several ideas, I chose one and began to develop, it was a game.
Idea and implementation
The inspiration was the
Rubik's Cube and the
R-functions of VL Rvachev . What is the idea: on the playing field there are elements that are linked into lists. Chains of related items can be moved within these lists. An important part of the implementation and gameplay - the geometric arrangement of elements. In the case of the Rubik's Cube, they are located in a three-dimensional matrix.
Now attention. List nodes do not have to be placed in a rectangular matrix. They can be arranged arbitrarily, you can make chains of different lengths, impose different conditions on this graph and move along it. It sounds simple until we try to visualize such a model. Here we face the first interesting task.
')
We are used to the fact that game objects have a clear and quite specific size. However, if we specify arbitrary trajectories of the movement of objects, then when visualizing the puzzle elements, collisions will be inevitable, they will overlap each other. Solution option - to calculate the size and shape of the elements, depending on their relative position.
In general, it would be interesting to get imitation drops, for example, such: Looks good. R-functions will not get such a result, but I drew inspiration from them. Now we complicate the model to see how it may look like in its entirety. Drops had to be made more "hard", otherwise the whole image went shaking.
And again it looks good, and even good. With the verification of the starting model finished, now you need to extract useful artifacts from it. After experiments, I stopped at this option.
- To reduce memory consumption, graphic elements (now I’ve started calling them blobs) will be stored in vector format in the form of polygons.
- To give them a rounded shape, polygons will be rendered with interpolation by splines, as in vector fonts.
- All the vertices and indices of a separate playing field will lie in one continuous buffer to reduce the number of draw passes.
Another small amount of memory for service structures to describe frames. The result was a data structure ranging in size from 0.5 to 1 MB per playing field. The size depends on the desired accuracy of the geometric description of blobs and the smoothness of their movement. There is a model, there is data, it remains to revive them. After some effort, the initial idea materialized into this gameplay:
In the article from the idea to the implementation fit a couple of paragraphs. In practice, the path traveled was longer, and, of course, it was already well known to me many times. Therefore, I will dwell only on a few episodes of the work done, which seem to me interesting.
Platform Selection and SDK
I approached the choice very far away. I decided that it should be something cross-platform (or easily portable) and the first implementation should definitely be on a mobile platform, for example, on Android.
My professional activities are related to enterprise development primarily on the .net stack, and the words “mobile” and “game” were something far away for me. Based on the foregoing, it would be logical to choose the Xamarin or mono-based implementation of other common libraries as the main development tool. Therefore, after some thought, I decided to write in c ++ and use it as a framework. As a result, 99.9% of the program code was written on it. Development and debugging was carried out in Visual Studio under the native Windows OS. For the mobile platform, only the final build and minor tests were performed.
The main thing - do not give up
The smoothness of movement of blobs is an important part of the gameplay, I needed FPS 60 and enough optimization to keep this frame rate on the maximum number of devices. What happened with the implementation: API OpenGL ES2 is used, with minimal graphics settings in the application (disabled: anti aliasing, highlighting selected chains of blobs, etc.) the main screen is drawn for 3 calls to glDrawElements, with maximum settings from 6 to 9 calls. In general, it turned out quite sparingly, it would be possible to work on optimization, but disappointment awaited me here. Under no circumstances did I manage to achieve 60 FPS from my old Galaxy Ace (I thought it would work on it — it would work everywhere). After many experiments and considerable time spent, it finally came to me. All the time I tried to optimize the shaders and the number of draw-stakes, and the solution to the problem with Galaxy Ace was in a completely different plane. I excluded this device from my checklist, after which the need to continue to work on optimizing performance has disappeared. The main thing is not to give up, there will definitely be a solution!
A pair of buttons on the interface
Another interesting task is the user interface. Getting to it, I did not expect that the implementation of the “pair of buttons” would take so long. It was a very, very big surprise. To make the “buttons” work, several render-s were implemented: for text, icons, sprites, rectangles, alignment of elements relative to the screen and each other was added. Objects are more complex from primitives, behavior is added to them: text labels, buttons, dialog and pop-up windows, etc. Of course, I was concerned with performance and could not just draw the “buttons”, it had to be done quickly. And if you need to quickly, then one option - minimize the draw-cola. As a result, UI elements were combined into groups so that all objects of the same group are drawn in one call. The bike turned out to be similar to a micro library for UI. For example, a button is now created like this:
std::unique_ptr<IControl> button(new Icon( "render_group_for_all_controls_on_the_screen", IconType::MenuSolid,
Developer Feedback
As the release approached, I thought about journaling. The application was ported to Android and tested on a pair of physical devices and emulators. Obviously not enough to press the “publish” button with a clear conscience. We need a centralized error log, which will help in the first days after the release (or the next update) to fix something critical. Raising my servers is not at all what I would like. Ready-made solutions for mobile applications did not like, at least free. Went to the clouds. In general, all the same. By coincidence, which is not at all fundamental, I settled on Azure with its blob storage. According to my expectations, a solution with a remote log in the cloud should be cheap because the minimum is written to the log and, during normal operation of the application, the logs must be empty. At the time of this writing, did not have time to check.
In conclusion about the main thing
Looking back at the work done and the decisions made, it is absolutely possible to say that the resources, which were already limited, were spent erratically and terribly inefficiently, but on the other hand it was very correct. Business model, planning, product practices with hypotheses and pivots are very good tools when your project = work. But if you suddenly have a desire to “compose” something beyond the usual, the metrics stop working, the goal becomes not the result, but the process. And the most difficult task in it is to keep this strange and unsteady motivation. Only with it you can climb into the most inaccessible places. This was my main challenge during the project. Probably, like everyone who passed on this road.
All this time I have been working on a product, and it is much more than a code or an idea, work on it can never stop, but today I put one more tick: “The lock is ready”.