DevOps C++ and "kitchen wars", or How I started writing games while eating

"I know that I know nothing" Socrates

For whom: for IT people who spit on all the developers and want to play their games!

About what: how to start writing games in C/C++ if you need it!

Why should you read this: App development is not my work specialty, but I try to code every week. Because I love games!

Hello my Name Is Andrey Grankin, I'm a DevOps at Luxoft. Application development is not my work specialty, but I try to code every week. Because I love games!

The computer games industry is huge, even more rumored today than the movie industry. Games have been written since the beginning of the development of computers, using, by modern standards, complex and basic development methods. Over time, game engines began to appear with already programmed graphics, physics, and sound. They allow you to focus on the development of the game itself and not bother about its foundation. But along with them, with the engines, the developers "go blind" and degrade. The very production of games is put on the conveyor. And the quantity of production begins to prevail over its quality.

At the same time, when playing other people's games, we are constantly limited by locations, plot, characters, game mechanics that other people came up with. So I realized that...

… it is time to create your own worlds, subject only to me. Worlds where I am the Father, and the Son, and the Holy Spirit!

And I sincerely believe that by writing your own game engine and a game on it, you will be able to open your eyes, wipe the windows and pump your cabin, becoming a more experienced and integral programmer.

In this article I will try to tell you how I started writing small games in C / C ++, what is the development process and where I find time for a hobby in a busy environment. It is subjective and describes the process of an individual start. Material about ignorance and faith, about my personal picture of the world at the moment. In other words, "The administration is not responsible for your personal brains!".

Practice

“Knowledge without practice is useless, practice without knowledge is dangerous.” Confucius

My notebook is my life!


So, in practice, I can say that everything for me starts with a notebook. I write down not only my daily tasks there, but also draw, program, design flowcharts and solve problems, including mathematical ones. Always use a notepad and write only with a pencil. It's clean, comfortable and reliable, IMHO.

DevOps C++ and "kitchen wars", or How I started writing games while eating
My (already filled) notebook. This is how it looks. It contains everyday tasks, ideas, drawings, diagrams, solutions, black bookkeeping, code, and so on.

At this stage, I managed to complete three projects (this is in my understanding of “finality”, because any product can be developed relatively endlessly).

  • Project 0: this is an Architect Demo 3D scene written in C# using the Unity game engine. For macOS and Windows platforms.
  • Game 1: console game Simple Snake (known to everyone as "Snake") for Windows. written in C.
  • Game 2: console game Crazy Tanks (known to everyone as "Tanks"), already written in C ++ (using classes) and also under Windows.

Project 0 Architect Demo

  • Platform: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Language: C#
  • Game engine: Unity
  • Inspiration: Darrin Lile
  • Repository: GitHub

DevOps C++ and "kitchen wars", or How I started writing games while eating
3D Scene Architect Demo

The first project was implemented not in C/C++, but in C# using the Unity game engine. This engine was not as demanding on hardware as Unreal Engine, and also seemed to me easier to install and use. I did not consider other engines.

The goal in Unity for me was not to develop some kind of game. I wanted to create a 3D scene with some kind of character. He, or rather She (I modeled the girl I was in love with =) had to move and interact with the outside world. It was only important to understand what Unity is, what the development process is, and how much effort it takes to create something. This is how the Architect Demo project was born (the name was invented almost from the bullshit). Programming, modeling, animation, texturing took me probably two months of daily work.

I started with tutorial videos on YouTube on how to create 3D models in Blender. Blender is a great free tool for 3D modeling (and more) that doesn't require installation. And here a shock awaited me ... It turns out that modeling, animation, texturing are huge separate topics on which you can write books. This is especially true for the characters. To model fingers, teeth, eyes and other parts of the body, you will need knowledge of anatomy. How are the muscles of the face arranged? How do people move? I had to “insert” bones into each arm, leg, finger, knuckles!

Model the clavicle, additional bone levers, so that the animation looks natural. After such lessons, you realize what a huge work the creators of animated films do, just to create 30 seconds of video. But 3D movies last for hours! And then we come out of the theaters and say something like: “Ta, a shitty cartoon / movie! They could have done better…” Fools!

And one more thing about programming in this project. As it turned out, the most interesting part for me was the mathematical one. If you run the scene (link to the repository in the project description), you will notice that the camera rotates around the girl character in a sphere. To program such a camera rotation, I had to first calculate the coordinates of the position point on the circle (2D), and then on the sphere (3D). The funny thing is that I hated math at school and knew it with a minus. Partly, probably, because at school they simply don’t explain to you how the hell this mathematics is applied in life. But when you are obsessed with your goal, dream, then the mind is cleared, revealed! And you begin to perceive complex tasks as an exciting adventure. And then you think: “Well, why couldn’t *beloved* mathematician normally tell where these formulas can be leaned?”.

DevOps C++ and "kitchen wars", or How I started writing games while eating
Calculation of formulas for calculating the coordinates of a point on a circle and on a sphere (from my notebook)

Game 1

  • Platform: Windows (tested on Windows 7, 10)
  • Language: I think it was written in pure C
  • Game engine: Windows console
  • Inspiration: javidx9
  • Repository: GitHub

DevOps C++ and "kitchen wars", or How I started writing games while eating
Simple Snake game

The 3D scene is not a game. In addition, modeling and animating 3D objects (especially characters) is long and difficult. After playing around with Unity, I realized that I had to continue, or rather start, from the basics. Something simple and fast, but at the same time global, to understand the very structure of games.

And what do we have simple and fast? That's right, console and 2D. More precisely, even the console and symbols. Again, I started looking for inspiration on the Internet (in general, I consider the Internet the most revolutionary and dangerous invention of the XNUMXst century). I dug up a video of one programmer who made console Tetris. And in the likeness of his game, he decided to cut down the "snake". From the video, I learned about two fundamental things - the game loop (with three basic functions / parts) and output to the buffer.

The game loop might look something like this:

int main()
   {
      Setup();
      // a game loop
      while (!quit)
      {
          Input();
          Logic();
          Draw();
          Sleep(gameSpeed);  // game timing
      }
      return 0;
   }

The code presents the entire main() function at once. And the game cycle starts after the corresponding comment. There are three basic functions in the loop: Input(), Logic(), Draw(). First, input data Input (mainly control of keystrokes), then processing the entered data Logic, then displaying on the screen - Draw. And so every frame. Animation is created in this way. It's like cartoons. Usually processing the input data takes the most time and, as far as I know, determines the frame rate of the game. But here the Logic() function is very fast. Therefore, the frame rate must be controlled by the Sleep() function with the gameSpeed ​​parameter, which determines this rate.

DevOps C++ and "kitchen wars", or How I started writing games while eating
game cycle. Snake programming in notepad

If you are developing a symbolic console game, then displaying data on the screen using the usual stream output 'cout' will not work - it is very slow. Therefore, the output must be carried out in the screen buffer. So much faster and the game will work without glitches. To be honest, I don't quite understand what a screen buffer is and how it works. But I will give a code example here, and perhaps someone in the comments will be able to clarify the situation.

Getting the screen buffer (if I may say so):

// create screen buffer for drawings
   HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0,
 							   NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
   DWORD dwBytesWritten = 0;
   SetConsoleActiveScreenBuffer(hConsole);

Direct output to the screen of a certain line scoreLine (the line for displaying scores):

// draw the score
   WriteConsoleOutputCharacter(hConsole, scoreLine, GAME_WIDTH, {2,3}, &dwBytesWritten);

In theory, there is nothing complicated in this game, it seems to me a good example of an entry-level game. The code is written in one file and arranged in several functions. No classes, no inheritance. You yourself can see everything in the source code of the game by going to the repository on GitHub.

Game 2 Crazy Tanks

DevOps C++ and "kitchen wars", or How I started writing games while eating
Crazy Tanks game

Printing characters to the console is probably the simplest thing you can turn into a game. But then one trouble appears: the characters have different heights and widths (the height is greater than the width). Thus, everything will look disproportionate, and moving down or up will seem much faster than moving left or right. This effect is very noticeable in "Snake" (Game 1). "Tanks" (Game 2) do not have such a drawback, since the output there is organized by painting the screen pixels with different colors. You could say I wrote a renderer. True, this is already a little more complicated, although much more interesting.

For this game, it will be enough to describe my system for displaying pixels on the screen. I think this is the main part of the game. And everything else you can come up with yourself.

So, what you see on the screen is just a set of moving colored rectangles.

DevOps C++ and "kitchen wars", or How I started writing games while eating
Rectangle set

Each rectangle is represented by a matrix filled with numbers. By the way, I can highlight one interesting nuance - all the matrices in the game are programmed as a one-dimensional array. Not two-dimensional, but one-dimensional! One-dimensional arrays are much easier and faster to work with.

DevOps C++ and "kitchen wars", or How I started writing games while eating
An example of a game tank matrix

DevOps C++ and "kitchen wars", or How I started writing games while eating
Representing the Matrix of a Game Tank with a One-Dimensional Array

DevOps C++ and "kitchen wars", or How I started writing games while eating
A more illustrative example of a matrix representation by a one-dimensional array

But access to the elements of the array occurs in a double loop, as if it were not a one-dimensional, but a two-dimensional array. This is done because we are still working with matrices.

DevOps C++ and "kitchen wars", or How I started writing games while eating
Traversing a one-dimensional array in a double loop. Y is the row ID, X is the column ID

Please note that instead of the usual matrix identifiers i, j, I use the identifiers x and y. So, it seems to me, more pleasing to the eye and clearer to the brain. In addition, such a notation makes it possible to conveniently project the matrices used onto the coordinate axes of a two-dimensional image.

Now about pixels, color and display. The StretchDIBits function (Header: windows.h; Library: gdi32.lib) is used for output. Among other things, the following is passed to this function: the device on which the image is displayed (in my case, this is the Windows console), the coordinates of the start of displaying the image, its width / height, and the image itself in the form of a bitmap (bitmap), represented by an array of bytes. Bitmap as an array of bytes!

The StretchDIBits() function at work:

// screen output for game field
   StretchDIBits(
               deviceContext,
               OFFSET_LEFT, OFFSET_TOP,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               0, 0,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               m_p_bitmapMemory, &bitmapInfo,
               DIB_RGB_COLORS,
               SRCCOPY
               );

Memory is allocated in advance for this bitmap using the VirtualAlloc() function. That is, the required number of bytes is reserved to store information about all pixels, which will then be displayed on the screen.

Creating an m_p_bitmapMemory bitmap:

// create bitmap
   int bitmapMemorySize = (PMATRIX_WIDTH * PMATRIX_HEIGHT) * BYTES_PER_PIXEL;
   void* m_p_bitmapMemory = VirtualAlloc(0, bitmapMemorySize, MEM_COMMIT, PAGE_READWRITE);

Roughly speaking, a bitmap consists of a set of pixels. Every four bytes in the array is an RGB pixel. One byte per red value, one byte per green value (G), and one byte per blue color (B). Plus, there is one byte per indent. These three colors - Red / Green / Blue (RGB) - are mixed with each other in different proportions - and the resulting pixel color is obtained.

Now, again, each rectangle, or game object, is represented by a number matrix. All of these game objects are placed in a collection. And then they are placed on the playing field, forming one large numerical matrix. I mapped each number in the matrix to a specific color. For example, the number 8 is blue, the number 9 is yellow, the number 10 is dark gray, and so on. Thus, we can say that we have a matrix of the playing field, where each number is some kind of color.

So, we have a numerical matrix of the entire playing field on the one hand and a bitmap for displaying the image on the other. So far, the bitmap is "empty" - it does not yet have information about the pixels of the desired color. This means that the last step will be filling the bitmap with information about each pixel based on the numerical matrix of the playing field. An illustrative example of such a transformation is in the picture below.

DevOps C++ and "kitchen wars", or How I started writing games while eating
An example of filling a bitmap (Pixel matrix) with information based on the numerical matrix (Digital matrix) of the playing field (color indices do not match the indices in the game)

I will also present a piece of real code from the game. The variable colorIndex at each iteration of the loop is assigned a value (color index) from the numerical matrix of the playing field (mainDigitalMatrix). Then the color itself is written to the color variable based on the index. Further, the resulting color is divided into the ratio of red, green and blue (RGB). And together with the indent (pixelPadding), this information is written to the pixel over and over again, forming a color image in the bitmap.

The code uses pointers and bitwise operations, which can be difficult to understand. So I advise you to read separately somewhere how such structures work.

Filling a bitmap with information based on the numerical matrix of the playing field:

// set pixel map variables
   int colorIndex;
   COLORREF color;
   int pitch;
   uint8_t* p_row;
 
   // arrange pixels for game field
   pitch = PMATRIX_WIDTH * BYTES_PER_PIXEL;     // row size in bytes
   p_row = (uint8_t*)m_p_bitmapMemory;       //cast to uint8 for valid pointer arithmetic
   							(to add by 1 byte (8 bits) at a time)   
   for (int y = 0; y < PMATRIX_HEIGHT; ++y)
   {
       uint32_t* p_pixel = (uint32_t*)p_row;
       for (int x = 0; x < PMATRIX_WIDTH; ++x)
       {
           colorIndex = mainDigitalMatrix[y * PMATRIX_WIDTH + x];
           color = Utils::GetColor(colorIndex);
           uint8_t blue = GetBValue(color);
           uint8_t green = GetGValue(color);
           uint8_t red = GetRValue(color);
           uint8_t pixelPadding = 0;
 
           *p_pixel = ((pixelPadding << 24) | (red << 16) | (green << 8) | blue);
           ++p_pixel;
       }
       p_row += pitch;
   }

According to the method described above, one picture (frame) is formed in the Crazy Tanks game and displayed on the screen in the Draw() function. After registering keystrokes in the Input() function and their subsequent processing in the Logic() function, a new picture (frame) is formed. True, game objects may already have a different position on the playing field and, accordingly, are drawn in a different place. This is how animation (movement) happens.

In theory (if you haven’t forgotten anything), understanding the game loop from the first game (“Snake”) and the system for displaying pixels on the screen from the second game (“Tanks”) is all you need to write any of your 2D games for Windows. Soundless! 😉 The rest of the parts are just a flight of fancy.

Of course, the game "Tanks" is designed much more complicated than the "Snake". I already used the C++ language, that is, I described different game objects with classes. I created my own collection - you can see the code in headers/Box.h. By the way, the collection most likely has a memory leak. Used pointers. Worked with memory. I must say that the book helped me a lot. Beginning C++ Through Game Programming. This is a great start for beginners in C++. It is small, interesting and well organized.

It took about six months to develop this game. I wrote mainly during lunch and snacks at work. He sat in the office kitchen, stomped on food and wrote code. Or at home for dinner. So I got such "kitchen wars". As always, I actively used a notebook, and all the conceptual things were born in it.

At the end of the practical part, I will pull out a few scans of my notebook. To show what exactly I was writing down, drawing, counting, designing…

DevOps C++ and "kitchen wars", or How I started writing games while eating
Tank image design. And the definition of how many pixels each tank should occupy on the screen

DevOps C++ and "kitchen wars", or How I started writing games while eating
Calculation of the algorithm and formulas for the rotation of the tank around its axis

DevOps C++ and "kitchen wars", or How I started writing games while eating
Diagram of my collection (the one with the memory leak, most likely). The collection is created as a Linked List

DevOps C++ and "kitchen wars", or How I started writing games while eating
And these are futile attempts to screw artificial intelligence into the game

Theory

"Even a journey of a thousand miles begins with the first step" (Ancient Chinese wisdom)

Let's move from practice to theory! How do you find time for your hobby?

  1. Determine what you really want (alas, this is the most difficult).
  2. Set priorities.
  3. Sacrifice all "superfluous" for the sake of higher priorities.
  4. Move towards your goals every day.
  5. Do not expect that there will be two or three hours of free time for a hobby.

On the one hand, you need to determine what you want and prioritize. On the other hand, it is possible to abandon some cases / projects in favor of these priorities. In other words, you will have to sacrifice everything "superfluous". I heard somewhere that in life there should be a maximum of three main activities. Then you will be able to deal with them in the best possible way. And additional projects/directions will start to overload corny. But this is all, probably, subjective and individual.

There is a certain golden rule: never have a 0% day! I learned about it in an article by an indie developer. If you are working on a project, then do something about it every day. And it doesn't matter how much you make. Write one word or one line of code, watch one tutorial video, or hammer one nail into the board—just do something. The hardest part is getting started. Once you start, you will probably do a little more than you wanted to. So you will constantly move towards your goal and, believe me, very quickly. After all, the main brake on all things is procrastination.

And it is important to remember that you should not underestimate and ignore the free "sawdust" of time in 5, 10, 15 minutes, wait for some big "logs" lasting an hour or two. Are you standing in line? Think about something for your project. Are you going up the escalator? Write down something in a notebook. Do you eat on the bus? Okay, read some article. Use every opportunity. Stop watching cats and dogs on YouTube! Don't mess with your brain!

And the last. If, after reading this article, you liked the idea of ​​creating games without using game engines, then remember the name Casey Muratori. This guy has broker. In the "watch -> PREVIOUS EPISODES" section you will find amazing free video tutorials on how to create a professional game from scratch. In five lessons of Intro to C for Windows, you may learn more than in five years of study at the university (someone wrote about this in the comments under the video).

Casey also explains that by developing your own game engine, you will have a better understanding of any existing engines. In the world of frameworks, where everyone is trying to automate, you will learn how to create, not use. Understand the very nature of computers. And you will also become a much more intelligent and mature programmer - a pro.

Good luck on your chosen path! And let's make the world more professional.

Author: Grankin Andrey, DevOps



Source: habr.com