📜 ⬆️ ⬇️

How I made a game under KolibriOS

Hello. In this publication I want to talk about how I made the game under the operating system KolibriOS, the existence of which I had never even suspected before.



How did it all start?


It all started with the fact that an announcement from the administration of KolibriOS about the start of a competition to create a game for their operating system appeared on igdc.ru.

At first I did not take it seriously and did not want to participate for several reasons. The main thing, of course, is laziness. Yes, yes, she saves from a lot of madness in my life. I tried to save this time, but one winter day, when I was walking from work, an insight descended on me, a light bulb lit up with a hellish flame and exploded over my head. I realized that this is a good chance to learn something new and I just have to make a game for this competition.
')
Of course, I hoped that when I got home I would score on this idea (as is usually the case), but, to my surprise, this did not happen. Quite the contrary. When I came home and installed KolibriOS on a virtual machine, I caught on this idea so much so that it was time to put out the fire. Instruments have a little restrained.

Instruments


Preparing to write the game began with the choice of language and development environment. At first, a glance fell on C, but the absence of OOP makes me a sad panda. Option to use Assembler ... Not considered. Pretty quickly, the option of writing “Hello World” in C ++ was found, and that was fine with me. True, the intolerable hatred of C ++ interfered a little, but the struggle with it helped to understand the secret of stars in the language.

Having written a couple of examples on different articles, I could not compile the application. I had to ask for help from the experts on the forum. Good people did not make them wait long and quickly put me on the right path.

I stopped at the utility of automatically creating a project under Visual Studio under the FASM compiler - this is one of the easiest ways in which compiling from the environment immediately created a launching application file for Kolibri. Having specified the compilation path to a USB flash drive, it has become convenient to run the application on a virtual machine. There were no easier ways, shared folders are not supported.

It took about a week to build an empty project, so despite the fact that the competition was given for almost 2 months, only 20 days remained to create the game. But everyone knows - the closer the deadline, the higher the productivity.

12-20 December


It is a secret moment of choice, what game to do in the available technological conditions. At first I wanted to make a scroller, but the very first experiments with the output of pictures killed this idea. The lack of hardware acceleration and problems with accurate timers did not allow a little blood to make a dynamic game. The output of the picture is carried out by the principle of transmitting a one-dimensional array of RGB pixel colors, specifying image sizes and position.

struct RGB { Byte b; Byte g; Byte r; } … void kos_PutImage( RGB * imagePtr, Word sizeX, Word sizeY, Word x, Word y); 

The graphics in the game will be sprite, which means you need to load an image from a file, but what format should you use? png, jpg or gif? As always, the choice fell on png, it is free, fairly economical, does not spoil the image by compression and has the ability to store the alpha channel. Having worked with reading files in KolibriOS, I stepped on dozens of rakes and decided to use a simple array of pixels listed in a global constant.

 const RGB img_water[576] = { 0x0B79BD, 0x0A6DAE, 0x0A69AA, 0x0A6EAD, 0x0B71B0, 0x0A65A8, 0x0A65A6, 0x0B75B4, 0x0B6DAD, 0x0B71B1, 0x0A6AAD, 0x0A6DAD, 0x0C7EBB, 0x0A66AB, 0x0B66AD, 0x0B6DB1, 0x0A61A3, 0x0B79B7, 0x0A72B1, 0x0A6AAD, 0x0C85C1, 0x0A6AAB, 0x0A62A3, 0x0B6EB2, 0x0B7ABC, 0x0B6BAC, 0x0C8BC3, 0x0C9BCE, 0x0C88C1, 0x0B75B4, 0x0B81BD, 0x0B89C1, 0x0B71B1, 0x0B7AB8, 0x0A74B1, 0x0B76B5, 0x0B86BF, 0x0B81BC, 0x0B81BC, 0x0A5B9F, 0x0B70AF, 0x0C86BE, 0x0B76B5, 0x0C94C6, 0x0D9DCB, 0x0A6EAF, 0x0B70B0, 0x0B70B4, 0x0B72B2, 0x0A6EAE, 0x0C8BC4, 0x0DA1D3, 0x0C8CC9, 0x0B7CB9, 0x0B7DBA, 0x0B70AF, 0x0B7CB9, 0x0B89C2, 0x0B80BB, 0x0B7AB9, 0x0B80BD, 0x0C9FCC, 0x0B8DC1, 0x0B73B3, 0x0B79B6, 0x0A61A4, 0x0B81BB, 0x0DAAD3, 0x0EB7D8, 0x0C86C4, 0x0B80BC, 0x0B79BA, 0x0A6EAD, 0x0B81BC, 0x0B8DC5, 0x0C94CA, 0x0C8AC8, 0x0C8DC4, 0x0E90C6, 0x0A64A5, 0x0B71B0, 0x0B81BD, 0x0B87C0, 0x0C8AC6, 0x0D90CB, 0x0D9FCC, 0x0B84BD, 0x0B77B7, 0x0B7DBA, 0x0B80BB, 0x0C97C8, 0x0DA6D3, 0x0EC8E0, 0x0D94CB, 0x0C8AC4, 0x0B79B9, 0x0A64A6, 0x0B7EBA, 0x0B86BF, 0x0B7EBA, … }; 0x0B71B0, 0x0A65A8, 0x0A65A6, 0x0B75B4, 0x0B6DAD, 0x0B71B1, 0x0A6AAD, 0x0A6DAD, 0x0C7EBB, 0x0A66AB, 0x0B66AD, 0x0B6DB1, 0x0A61A3, 0x0B79B7, 0x0A72B1, 0x0A6AAD, 0x0C85C1, 0x0A6AAB, 0x0A62A3, 0x0B6EB2, 0x0B7ABC, const RGB img_water[576] = { 0x0B79BD, 0x0A6DAE, 0x0A69AA, 0x0A6EAD, 0x0B71B0, 0x0A65A8, 0x0A65A6, 0x0B75B4, 0x0B6DAD, 0x0B71B1, 0x0A6AAD, 0x0A6DAD, 0x0C7EBB, 0x0A66AB, 0x0B66AD, 0x0B6DB1, 0x0A61A3, 0x0B79B7, 0x0A72B1, 0x0A6AAD, 0x0C85C1, 0x0A6AAB, 0x0A62A3, 0x0B6EB2, 0x0B7ABC, 0x0B6BAC, 0x0C8BC3, 0x0C9BCE, 0x0C88C1, 0x0B75B4, 0x0B81BD, 0x0B89C1, 0x0B71B1, 0x0B7AB8, 0x0A74B1, 0x0B76B5, 0x0B86BF, 0x0B81BC, 0x0B81BC, 0x0A5B9F, 0x0B70AF, 0x0C86BE, 0x0B76B5, 0x0C94C6, 0x0D9DCB, 0x0A6EAF, 0x0B70B0, 0x0B70B4, 0x0B72B2, 0x0A6EAE, 0x0C8BC4, 0x0DA1D3, 0x0C8CC9, 0x0B7CB9, 0x0B7DBA, 0x0B70AF, 0x0B7CB9, 0x0B89C2, 0x0B80BB, 0x0B7AB9, 0x0B80BD, 0x0C9FCC, 0x0B8DC1, 0x0B73B3, 0x0B79B6, 0x0A61A4, 0x0B81BB, 0x0DAAD3, 0x0EB7D8, 0x0C86C4, 0x0B80BC, 0x0B79BA, 0x0A6EAD, 0x0B81BC, 0x0B8DC5, 0x0C94CA, 0x0C8AC8, 0x0C8DC4, 0x0E90C6, 0x0A64A5, 0x0B71B0, 0x0B81BD, 0x0B87C0, 0x0C8AC6, 0x0D90CB, 0x0D9FCC, 0x0B84BD, 0x0B77B7, 0x0B7DBA, 0x0B80BB, 0x0C97C8, 0x0DA6D3, 0x0EC8E0, 0x0D94CB, 0x0C8AC4, 0x0B79B9, 0x0A64A6, 0x0B7EBA, 0x0B86BF, 0x0B7EBA, … }; 0x0B75B4, 0x0B81BD, 0x0B89C1, 0x0B71B1, 0x0B7AB8, 0x0A74B1, 0x0B76B5, 0x0B86BF, 0x0B81BC, 0x0B81BC, 0x0A5B9F, 0x0B70AF, 0x0C86BE, 0x0B76B5, 0x0C94C6, 0x0D9DCB, 0x0A6EAF, 0x0B70B0, 0x0B70B4, 0x0B72B2, 0x0A6EAE, const RGB img_water[576] = { 0x0B79BD, 0x0A6DAE, 0x0A69AA, 0x0A6EAD, 0x0B71B0, 0x0A65A8, 0x0A65A6, 0x0B75B4, 0x0B6DAD, 0x0B71B1, 0x0A6AAD, 0x0A6DAD, 0x0C7EBB, 0x0A66AB, 0x0B66AD, 0x0B6DB1, 0x0A61A3, 0x0B79B7, 0x0A72B1, 0x0A6AAD, 0x0C85C1, 0x0A6AAB, 0x0A62A3, 0x0B6EB2, 0x0B7ABC, 0x0B6BAC, 0x0C8BC3, 0x0C9BCE, 0x0C88C1, 0x0B75B4, 0x0B81BD, 0x0B89C1, 0x0B71B1, 0x0B7AB8, 0x0A74B1, 0x0B76B5, 0x0B86BF, 0x0B81BC, 0x0B81BC, 0x0A5B9F, 0x0B70AF, 0x0C86BE, 0x0B76B5, 0x0C94C6, 0x0D9DCB, 0x0A6EAF, 0x0B70B0, 0x0B70B4, 0x0B72B2, 0x0A6EAE, 0x0C8BC4, 0x0DA1D3, 0x0C8CC9, 0x0B7CB9, 0x0B7DBA, 0x0B70AF, 0x0B7CB9, 0x0B89C2, 0x0B80BB, 0x0B7AB9, 0x0B80BD, 0x0C9FCC, 0x0B8DC1, 0x0B73B3, 0x0B79B6, 0x0A61A4, 0x0B81BB, 0x0DAAD3, 0x0EB7D8, 0x0C86C4, 0x0B80BC, 0x0B79BA, 0x0A6EAD, 0x0B81BC, 0x0B8DC5, 0x0C94CA, 0x0C8AC8, 0x0C8DC4, 0x0E90C6, 0x0A64A5, 0x0B71B0, 0x0B81BD, 0x0B87C0, 0x0C8AC6, 0x0D90CB, 0x0D9FCC, 0x0B84BD, 0x0B77B7, 0x0B7DBA, 0x0B80BB, 0x0C97C8, 0x0DA6D3, 0x0EC8E0, 0x0D94CB, 0x0C8AC4, 0x0B79B9, 0x0A64A6, 0x0B7EBA, 0x0B86BF, 0x0B7EBA, … }; 0x0B7DBA, 0x0B70AF, 0x0B7CB9, 0x0B89C2, 0x0B80BB, 0x0B7AB9, 0x0B80BD, 0x0C9FCC, 0x0B8DC1, 0x0B73B3, 0x0B79B6, 0x0A61A4, 0x0B81BB, 0x0DAAD3, 0x0EB7D8, 0x0C86C4, 0x0B80BC, 0x0B79BA, 0x0A6EAD, 0x0B81BC, 0x0B8DC5, const RGB img_water[576] = { 0x0B79BD, 0x0A6DAE, 0x0A69AA, 0x0A6EAD, 0x0B71B0, 0x0A65A8, 0x0A65A6, 0x0B75B4, 0x0B6DAD, 0x0B71B1, 0x0A6AAD, 0x0A6DAD, 0x0C7EBB, 0x0A66AB, 0x0B66AD, 0x0B6DB1, 0x0A61A3, 0x0B79B7, 0x0A72B1, 0x0A6AAD, 0x0C85C1, 0x0A6AAB, 0x0A62A3, 0x0B6EB2, 0x0B7ABC, 0x0B6BAC, 0x0C8BC3, 0x0C9BCE, 0x0C88C1, 0x0B75B4, 0x0B81BD, 0x0B89C1, 0x0B71B1, 0x0B7AB8, 0x0A74B1, 0x0B76B5, 0x0B86BF, 0x0B81BC, 0x0B81BC, 0x0A5B9F, 0x0B70AF, 0x0C86BE, 0x0B76B5, 0x0C94C6, 0x0D9DCB, 0x0A6EAF, 0x0B70B0, 0x0B70B4, 0x0B72B2, 0x0A6EAE, 0x0C8BC4, 0x0DA1D3, 0x0C8CC9, 0x0B7CB9, 0x0B7DBA, 0x0B70AF, 0x0B7CB9, 0x0B89C2, 0x0B80BB, 0x0B7AB9, 0x0B80BD, 0x0C9FCC, 0x0B8DC1, 0x0B73B3, 0x0B79B6, 0x0A61A4, 0x0B81BB, 0x0DAAD3, 0x0EB7D8, 0x0C86C4, 0x0B80BC, 0x0B79BA, 0x0A6EAD, 0x0B81BC, 0x0B8DC5, 0x0C94CA, 0x0C8AC8, 0x0C8DC4, 0x0E90C6, 0x0A64A5, 0x0B71B0, 0x0B81BD, 0x0B87C0, 0x0C8AC6, 0x0D90CB, 0x0D9FCC, 0x0B84BD, 0x0B77B7, 0x0B7DBA, 0x0B80BB, 0x0C97C8, 0x0DA6D3, 0x0EC8E0, 0x0D94CB, 0x0C8AC4, 0x0B79B9, 0x0A64A6, 0x0B7EBA, 0x0B86BF, 0x0B7EBA, … }; 0x0A64A5, 0x0B71B0, 0x0B81BD, 0x0B87C0, 0x0C8AC6, 0x0D90CB, 0x0D9FCC, 0x0B84BD, 0x0B77B7, 0x0B7DBA, 0x0B80BB, 0x0C97C8, 0x0DA6D3, 0x0EC8E0, 0x0D94CB, 0x0C8AC4, 0x0B79B9, 0x0A64A6, 0x0B7EBA, 0x0B86BF, 0x0B7EBA, const RGB img_water[576] = { 0x0B79BD, 0x0A6DAE, 0x0A69AA, 0x0A6EAD, 0x0B71B0, 0x0A65A8, 0x0A65A6, 0x0B75B4, 0x0B6DAD, 0x0B71B1, 0x0A6AAD, 0x0A6DAD, 0x0C7EBB, 0x0A66AB, 0x0B66AD, 0x0B6DB1, 0x0A61A3, 0x0B79B7, 0x0A72B1, 0x0A6AAD, 0x0C85C1, 0x0A6AAB, 0x0A62A3, 0x0B6EB2, 0x0B7ABC, 0x0B6BAC, 0x0C8BC3, 0x0C9BCE, 0x0C88C1, 0x0B75B4, 0x0B81BD, 0x0B89C1, 0x0B71B1, 0x0B7AB8, 0x0A74B1, 0x0B76B5, 0x0B86BF, 0x0B81BC, 0x0B81BC, 0x0A5B9F, 0x0B70AF, 0x0C86BE, 0x0B76B5, 0x0C94C6, 0x0D9DCB, 0x0A6EAF, 0x0B70B0, 0x0B70B4, 0x0B72B2, 0x0A6EAE, 0x0C8BC4, 0x0DA1D3, 0x0C8CC9, 0x0B7CB9, 0x0B7DBA, 0x0B70AF, 0x0B7CB9, 0x0B89C2, 0x0B80BB, 0x0B7AB9, 0x0B80BD, 0x0C9FCC, 0x0B8DC1, 0x0B73B3, 0x0B79B6, 0x0A61A4, 0x0B81BB, 0x0DAAD3, 0x0EB7D8, 0x0C86C4, 0x0B80BC, 0x0B79BA, 0x0A6EAD, 0x0B81BC, 0x0B8DC5, 0x0C94CA, 0x0C8AC8, 0x0C8DC4, 0x0E90C6, 0x0A64A5, 0x0B71B0, 0x0B81BD, 0x0B87C0, 0x0C8AC6, 0x0D90CB, 0x0D9FCC, 0x0B84BD, 0x0B77B7, 0x0B7DBA, 0x0B80BB, 0x0C97C8, 0x0DA6D3, 0x0EC8E0, 0x0D94CB, 0x0C8AC4, 0x0B79B9, 0x0A64A6, 0x0B7EBA, 0x0B86BF, 0x0B7EBA, … }; 

And finally, it turned out to display the image in the application window.

But it turned out that there is no transparency in the pixel structure. This put my feet in my mouth, and in the wheels of a stick. There was an idea to create your own frame buffer. It turned out that its use is quite demanding on system resources, and flickering occurs in the output of the buffer. To combat these drawbacks, it was decided to use the RenderTarget, in which the background was pixel-by-pixel mixed with an object with alpha.

 alpha = (float)addPixel.a / 255.0f; newPixel.r = pixel.r * (1 - alpha) + addPixel.r * alpha; newPixel.g = pixel.g * (1 - alpha) + addPixel.g * alpha; newPixel.b = pixel.b * (1 - alpha) + addPixel.b * alpha; 

But there were a few problems. I had to create my own ARGB pixel structure (with transparency) and a number of functions for working with arrays of images. The output of the sprites with alpha is ready; but now I realized with horror that I had no idea what I would do for the game.

December 21-22


Today, my opinion fell on the point of the rules on the project autobuilding contest. After reading a couple of articles on this topic, I got confused and scared even more, so I turned again to the forum for help. But he did not wait for an answer and immediately began to develop the game itself. The basis of the gameplay was taken from the old prototype for the competition IGDC.ru â„–94 , in which I participated a couple of years ago. As I recall that project, so involuntary smile escapes. In this competitive work there was just a little banter on the administration and competitors of the competition: I wrote a game in Delphi in Cyrillic with all the principles of good tone when forming the code. But that's another story.

It looked something like this:
 type  = Boolean;  = Integer;  = Single;  = Double;  = TPoint; T = TList; const :  = True; :  = False; … type T = record X, Y, , : ; end; T = class(T) strict private F: array of T; F: array[0..255] of ; procedure (: ); public procedure (: ); override; procedure (: ); override; procedure (: ); procedure (: ); function (X, Y: ): ; override; end; … while (.[.X, .Y] in [, , ]) do begin … if (.[.X, .Y] is T) or (.[.X, .Y] is T) then begin .[.X, .Y].(.X, .Y);  := ; end 


But back to our sheep.

To work with graphics, a small Delphi program was written that allows you to convert a png image into a C ++ array of pixels. This gave a good impetus to further development. As a result, the first prototype of the level was made fairly quickly:



In general, due to the small amount of built-in tools, I had to do functions for the most basic operations with graphics and game logic.

The game was immediately planned to smooth the animation of moving around the level - no jumps in the cells and twitching.

Honestly, here I decided to cheat a little, making changes only with the actions of the player. This significantly relieved the system both during drawing and in moments of inactivity. To rotate the tank, it was decided not to make pre-prepared images, but to write an algorithm for rotating the image. The usual reflection horizontally and vertically with a change of coordinates gave a simple rotation of the image by an angle of 90 degrees. At first, this was more than enough. But did not dock with the smoothness of the movement of the tank. A little later this will be done, but first we will push the crates and drop them into the water to create a bridge. This will allow you to deal not with special effects, but directly with the mechanics of the game.



December 23-24


Having looked at how the game looks, one could unwittingly get the idea to replace the graphics of unknown origin with one's own, in order to avoid problems with authorship. I had to look for an artist (but, I must say, not for long) and order graphics from him. Glance fell on the familiar artist Weilard . After discussing the idea and showing the prototype, we agreed on a price and began to work. (you don't think professionals work for free?)

The next day he gave me a couple of sketches of his vision of the game:



The fourth option seemed to me the most stylistically correct, and I liked the colors more, therefore, choosing the fourth option, I asked to change the color of the brick and got almost the final version:



Cutting the pieces from the sketch, immediately stuck them into the game instead of my pictures. The master promised to provide the rest of the schedule on weekends (December 27-28). With this graphics, a second wind opened and all the fatigue went away. I just lived this game.

Returning to the problem of a sharp turn, I’ll say that the moment came when the fact that the tank was sharply turned 90 degrees spoiled the whole picture. It was necessary to urgently get rid of this, I had to rewrite the rendering algorithm, taking into account the rotation at any angle. It turned out great. I never would have thought that I would write an algorithm for pixel-by-pixel rotation of the image.

The next step was a laser. The algorithm is simple - we recursively go through the level cells in the direction of the motion vector, until we encounter an obstacle. If the obstacle is a mirror, check its orientation and either end the cycle (the mirror is turned to us by the wall), or change the direction vector and go further (hit the mirror itself).



Having tried again to return to the auto-assembly and having studied this issue, I decided that it was better to postpone until the weekend, because I really wanted to finish the gameplay. He was now far more important, otherwise there will be absolutely nothing to pass.

December 25-26


After adding the destruction of a brick wall with a laser, the project began to be compiled long enough. The reason was hidden in a large number of global constants that were crammed into texture code. Having loaded the resources from the file, I overcame this problem. Messing with downloading png-files did not have the time and desire, since the format is compressed and requires a decent loader. So I had to quickly remake my utility for creating an array of pixels into a file. I spent a lot of time while I realized that I needed to specify the full path to the file (relative paths are not supported), but I managed to do it completely randomly. because The texture file was a simple array of RGB or ARGB structures, I decided not to bother and made a utility for packing all the files into one.



Removing all global arrays from code, the project began to compile instantly, which made me extremely happy.

27th of December


And again this auto assembly ... Auto assembly did not work out right away. Fortunately, good people on the forum helped to save the project from mistakes and to assemble the distribution kit KolibriOS with a stitched game. On the same day, the artist finally threw off the backgrounds for the menu, the level selection windows and the additional textures of the objects. Previously, there was only one scene in the game (of course, talking about the game mode), then to introduce the menu, pause and select a level, we had to add more scenes to the project. At first a menu appeared with the buttons to start the game and exit. When creating the transition between scenes, we had to first realize the events of victory and defeat, but then the problem arose: there was only one level, and that was stored in the constant of the global array.

As always, Delphi came to the rescue, which helped, quickly, to throw in a modest level editor:



Again, I got a little carried away with this business and made a super editor, simple and convenient. Although there is a plus in this - now anyone who wants it, can open the editor and add a couple of their levels to the game:



December 28-29


I spent the next couple of days adding laser guns and destroying them. The check for hit of a tank in the field of sight of the guns was set at the moment of the end of the tank movement. To find an active cannon in the firing line, from the tank, horizontally and vertically, cell checks were launched for the presence of a gun turned in our direction. If it was, then the same laser algorithm was launched as the tank, but with a different color. Here it was necessary to change the algorithm of drawing to the buffer again so that it was possible to paint the texture in the specified color.

The destruction of the gun was possible only when it hit the barrel with a laser, but I did not like the simple visual disappearance. Taking the Magic Particles , I made a frame scan of the explosion animation and once again finalized the rendering of the sprite to the buffer. Now, taking into account the frame number, which can be stored in the texture of several.



He also ordered the artist to draw another tile, an explosion stain (decal).

Having estimated that the deadline very soon, the idea of ​​making two types of mirrors (static and movable), as well as the walls of the laser, came to mind. The artist, killed in me, came to life and perked up - taking the textures of the wall and the box, I cut them in half and glued them with mirrors.

It turned out a new gameplay solution that helps to easily determine whether to move the mirror or not.



December 30th


Well, it's time to create a scene with a choice of level. I could not help but take into account the fact that anyone can add or remove a level by the map editor. Therefore made page-by-page navigation through all levels in the file, thirty levels per page. At the moment, the game has a limit on the number of levels. Less than a thousand levels, and that, just because the four-digit number does not fit into the box with the button: D.

The night came on the 31st and almost all the bugs have already turned into features. As well, I took a vacation for the last 3 days, it allowed me to walk a plane and a file on the project. By the way, one of the funny bugs: after the laser under the mirrors, the soil changed, to the one under the tank (second gif).

31th of December


I decided to devote the whole day to creating levels, because the gameplay part was already quite ready. This turned out to be the most difficult in the whole development. It took about 9 hours to create 48 levels.

Until the end of the surrender, it remains three hours, I make a commit and calm my nerves. Everything had time!

Sources


Sources on SVN: LaserTank , resources
Discussion of the game on the forum: board.kolibrios.org/viewtopic.php?f=41&t=2934

Voting


You can view other entries and vote for your favorites here , in the Habré.

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


All Articles