How I made the game under KolibriOS

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



How did it all start?


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

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, it saves from a lot of madness in my life. I tried to save this time too, but one winter day, when I was walking from work, an insight came over me, the light bulb lit up a hellish flame and exploded over my head. I realized that this is a good chance to learn something new and I simply must make a game for this contest.

Of course, I hoped that when I got home I would score on this idea (as it usually happens), but, to my surprise, this did not happen. Quite the contrary. When I got home and installed KolibriOS on a virtual machine, I got so excited about this idea that it was already time to put out. Tools a little tempered ardor.

Instruments


Preparation for writing the game began with a choice of language and development environment. At first, the view fell on C, but the lack of OOP makes me a sad panda. Option to use Assembler ... Not considered. Quite quickly, I found the option of writing “Hello World” in C ++, and that was fine with me. True, an intolerable hatred of C ++ interfered a bit, but the fight against it helped to understand the secret of asterisks in the language.

Having written a couple of examples on various articles, I could not compile the application. I had to seek help from experts on the forum. Kind people did not make me wait long and quickly set me on the right path.

I stopped at the utility for automatically creating a project under Visual Studio under the FASM compiler- This is one of the easiest ways in which compilation from the environment immediately created a launchable application file for Kolibri. Having indicated 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, public folders are not supported.

About a week was spent on assembling an empty project, therefore, despite the fact that almost 2 months were given for the competition, only 20 days were left to create the game. But everyone knows - the closer the deadline, the higher the productivity.

December 12-20


It was the innermost moment of choosing what kind of 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, indicating the image size 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 download the image from the file, but what format should I use? png, jpg or gif? As always, the choice fell on png, it is free, quite economical, does not spoil the picture with 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 entered 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,
…
};

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

But it turned out that the pixel structure lacks transparency. It 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 flicker occurs when the buffer is output. To combat these disadvantages, it was decided to use RenderTarget, in which the background was mixed pixel by pixel with the sprite of an object that has 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 several problems. I had to create my own ARGB pixel structure (with transparency) and a number of functions for working with image arrays. The output of sprites with alpha is ready; but now I was horrified to realize that I had no idea what kind of game I would do.

December 21-22


Today, my opinion has fallen on the paragraph of the rules of the competition for the project auto assembly. After reading a couple of articles on this topic, I got confused and scared even more, so I again turned to the forum for help. But he did not wait for an answer and immediately started developing the game itself. The basis of the gameplay was taken from the old prototype for the competition IGDC.ru No. 94 , in which I participated a couple of years ago. As I recall that project, an involuntary smile breaks out. In this competitive work, there was just a small banter over the administration and competitors of the competition: I wrote a game in Delphi in Cyrillic with all the principles of good form in the formation of the code. But this is a completely different 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 rams.

To work with graphics, a small program was written in Delphi, which 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 level prototype was made rather quickly:



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

In the game, a smooth animation of moving along the level was immediately planned — no jumping through the cells and twitches.

Honestly, here I decided to cheat a little, making changes only with the actions of the player. This significantly relieved the system both during rendering and during downtime. To turn the tank, it was decided not to make pre-prepared images, but to write an algorithm for turning the image. The usual horizontal and vertical reflection with a change of coordinates gave a simple rotation of the picture by an angle multiple of 90 degrees. At first, this was more than enough. But it did not fit into the smooth movement of the tank itself. This will be done a little later, 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


After looking at how the game looks, the idea involuntarily came to replace the graphics of obscure origin with your 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. The look fell on the familiar artist Weilard . Having discussed the idea and showing the prototype, we agreed on a price and started work. (You don’t think that professionals work for free?)

The next day he provided me with 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 brick and got almost the final version:



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

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

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, we check its orientation and either end the cycle (the mirror is turned toward us by the wall), or change the direction vector and move on (hit the mirror itself).



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

December 25-26


After adding the destruction of the brick wall with a laser, the project began to compile for a long time. The reason was hidden in a large number of global constants, which were hammered into the texture code. Having loaded resources from a file, I overcame this problem. There was no time and desire to download png files, since the format is compressed and requires a decent bootloader. Therefore, I had to quickly redo my utility for creating an array of pixels into a file. I spent a lot of time until I realized that I need 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 to pack all the files into one.



Having removed all global arrays from the code, the project began to compile instantly, which made me incredibly happy.

27th of December


And again, this car assembly ... The car assembly did not work right away. Fortunately, the good people on the forum helped save the project from errors and assemble the KolibriOS distribution with a wired game. That same day, the artist finally threw off the backgrounds for the menu, level selection windows and additional textures of objects. Previously, there was only one scene in the game (of course, we were talking about the game mode), but for the introduction of the menu, pause and level selection, more scenes had to be added to the project. First, a menu appeared with buttons to start the game and exit. When creating the transition between the scenes, we had to first realize the events of victory and defeat, but here the problem arose: there was only one level, and that level was stored in the constant of the global array.

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



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



December 28-29


The next couple of days I devoted to adding laser cannons and destroying them. The gun’s vision was checked at the moment the tank’s movement was completed. To find an active gun in the line of fire, from the tank horizontally and vertically, cells were checked 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. Then I had to change the drawing algorithm to the buffer again, so that I could 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. Picking up Magic Particles, I did a frame scan of the explosion animation and once again finalized the drawing of the sprite to the buffer. Now, taking into account the frame number, which can be stored in the texture a few.



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

Having estimated that the deadline was coming soon, the idea came up with the idea of ​​making two types of mirrors (static and mobile), as well as laser-passing walls. 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 you easily determine whether you can move the mirror or not.



December 30th


Well, now it's time to create a scene with a choice of level. I could not help but take into account the moment that anyone with a map editor can add or remove a level. Therefore, I did pagination on 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’s only because the four-digit number does not fit into the box with the button: D.

Night fell on the 31st and almost all the bugs have already turned into features. It’s good that I took a vacation for the last 3 days, this allowed me to go through the planer and file on the project. By the way, one of the funny bugs: after the laser, the soil under the mirrors changed to the one under the tank (second gif).

December, 31st


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. About 9 hours were spent on creating 48 levels.

Three hours are left until the end of the change, I commit and calm my nerves. All in time!

Source code


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

Voting


You can see other entries and vote for your favorite here on the hub

Also popular now: