Development of Return of Dr. Destructo: what Progress has come to

I recently released my first completed "home" project - a remake of the game "Island of Dr. Destructo ” (also known as just Destructo) with the ZX Spectrum. In this post, I would like to tell a little about how the development went and share some interesting notes about cross-platform development and code architecture.

In all my home projects, I used a simple means of maintaining motivation - I kept the Progress.txt file, in which I wrote down what was done every day. In combination with the “not a day without a line” approach recommended by many writers, this method personally gives me very good results. In the first, active development period, “Return of Dr. Destructo ”, I was able to work on the game almost every day for a year. Such a file can be interesting to re-read some time later, remembering what you did a month, half a year, or a year ago. Just "re-read the pager, thought a lot ...", as joked in the 90s. Now we’ll do it together - and don’t be afraid, I’ll try to choose only places that I can tell you about other than dry lines “made a feature, fixed a bug” and will accompany all this with a number of pictures.

I kept my Progress.txt in English, but for this article all entries will be translated into Russian.

08/02/11: Tinkered with sky, water, sun and moon
The development of the project began in 2011, after the next, larger-scale home project was again rotten. I wanted to do something that I could definitely bring to the end something simple, but still interesting. Create Version of “Island of Dr. Destructo "for the PC was my long-standing idea. This game has been very memorable since childhood, when it came to me, among others, on the cassette of "games about airplanes" brought from the Tsaritsyno market. The main feature that struck me then was the destructible level: every enemy shot down, every bomb thrown pulled a piece out of an enemy ship, and not some one pre-selected by the authors of the game, but a specific one, exactly in the place where the hit was! This is now rarely seen in games, but then - well, it was just ah!

I started the development by drawing the sky and water. In the original game, there was a change of time of day, which looked like the passage of the sun and moon across the sky, and also, stars appeared at night. Of course, I wanted to modernize the picture a bit, so the sky became gradient, and the stars began to appear gradually. The parameters of these processes are set by key points between which the game linearly interpolates the corresponding values.

The sun, moon and stars are reflected in the water. Initially, I wanted to reflect in general everything on the screen - enemy planes, player’s plane, level goal. But it turned out that it was ugly and uncomfortable.


08/10/11: Finished refactoring
While I was busy with the sky, all the code lived in a couple of classes created and called from main (), but then it was time to think about architecture. All my previous ones showed that hardcore OOP is very poorly suited for game mechanics: complex beautiful class hierarchies and isolated layers of abstraction are too inflexible for this area, in which often a small change in the statement of the problem leads to the fact that you need to break several abstractions at once and connect something what used to be independent, or vice versa.

On the other hand, to refuse encapsulation completely and put everything in one heap is also a direct way to hell. Just at the moment when I started writing “Return of Dr. Destructo ”, at work the boss talked about the component approach. I must say, I understood it poorly (as it turned out later). But on the basis of the understanding that was, I nevertheless invented some architecture. Looking ahead, I’ll say that it turned out to be quite successful: I have never rewritten its large pieces, and the number of absolutely nasty crutches has remained minimal. On the other hand, if you think of Unity when you think of component architecture, I’ll say right away that it didn’t work out that way.

So, how is the architecture of the game organized. Everything related to any one subsystem - sound, graphics, physics, mechanics - is taken out in a separate component. There is also a GameObject component that unites them all, which can do nothing more, but only contains the ID of other components. Namely ID - I did not use any kind of links, for which I paid - the access code to the components of the objects turned out to be inconvenient. However, unlike the same Unity, the component is a pretty dumb thing. This is just a data structure that lies to itself in some kind of array. It does not contain methods (with the possible exception of some simple auxiliary methods), and all data in it is public.

During frame calculation, all components of the same type are sequentially processed by the corresponding Processor. The processor for physics - calculates movements and collisions, the processor for graphics - changes animation timers and draws frames, and so on. At the same time, the processor always works with one type of component - others are not available to it.

Since processors do not have access to “foreign” components, data duplication is forced. For example, an object must have physical and graphic coordinates. In a more complex game, they might still not coincide, but here they are always the same if there was no error. However, there is no common place to store them, so you have to copy them at some point from the physical component (which changes them) to the graphic one (which needs them to know where to draw the object). Such copying, as well as other inter-component interactions, is handled by the code of game states. Basically, all such mechanics live in the GameStateLevel class, which is responsible for what happens on the screen during the level.

FileThis class, at first glance, can be a little scary with its two thousand lines, but in reality it’s not so bad, just a few more helper classes are described right there, which should have been put into separate files a long time ago, but their hands never reached. In the main state class, the main set of methods is all sorts of things like ControlsToPhysics, PhysicsToSound, etc., responsible for transferring the necessary data from one component to another using transformations.

Unlike the mechanical part of the game, game states and UI elements are written in a more familiar object-oriented paradigm. The states are Pushdown Automatawell described in Game Programming Patterns: there is some stack on which the state can be put or removed. In my implementation, there are two features: firstly, only the top-most state object receives input and updates in time, and everything is drawn (this is so that you can, for example, draw the state of the Learning Mode on top of the usual state of the Level); secondly, it is possible to remove states not only from the top of the stack, but also from the middle - in this case, all states above the deleted one will be removed, since they are considered to be "children".

My game is pretty simple, so the game states and the UI states are the same for me. In the general case, this is not so, and even in my development I came across cases when it was inconvenient!

08/12/11: Started working on XML deserialization system (basic things are already working)
08.16.11: Instead of deserialization, some kind of crap came out. The whole damned system needs to be rewritten from scratch
08/18/11: So far I have finished messing with deserialization (but it’s damn ugly anyway)

C ++ and (de) serializing these objects is an endless topic. In any case, until in the next standard at least some reflection is screwed. Before writing “Return of Dr. Destructo ”I already had experience working with several self-written (not me) systems, as well as Boost.Serialization (oh, that was that experience ...). Therefore, I understood that the task is not solved beautifully, conveniently and simply. But I also did not want to write endless loops over the elements in the XML file, so I decided to create my own system for loading data from XML into the same named objects.

In appearance, my task was simpler than the general case: I needed only deserialization, the reverse process - no. I needed support for only one format - XML. And I did not set myself the difficult condition that the name of a deserializable member of a class should be mentioned only once when it is declared (such as Deserializable m_someField). Moreover, according to the idea, the deserialization code should have been moved to a separate class. But there was also some complication: in cases where deserialization worked with named objects (for example, descriptions of animations), support for inheritance was needed so that a previously loaded object could be completely copied and then some fields changed in it.

A small lyrical digression on the topic "why all this is needed." Firstly, the definition: “Object prototype” in my personal vocabulary, lasting from the first work, is a class that contains data common to all objects of this type. Depending on the need, the data from the prototype can be copied and later changed, or the object can hold a link to the prototype, and then this data will be unchanged. Here is the data for these very prototypes and I had to download from XML files.

I must say that the results were working (the whole game actively uses this set of classes). On the other hand, it is terrible in terms of convenience, and it might be better to write all the deserialization with your hands. Judge for yourself:

A terrible and terrible example of deserializing a graphic object prototype
  // Объявляем десериализатор именованного объекта типа SGraphicsProto со строковым ID. Это прототип графического компонента.
class GraphicDeserializer : public XMLNamedObjectDeserializer
      // Объявляем десериализатор для одной анимации (у неё ID не строковый, а числовой, AnimationID)
	class AnimDeserializer : public XMLNamedObjectDeserializer
          // Объявляем десериализатор для одного кадра, у него ID нет вообще, наследовать кадры не получится
		class FrameDeserializer : public XMLObjectDeserializer
			FrameDeserializer() : XMLObjectDeserializer( "Frame", false )
              // Функция Bind связывает поля свежевыделенного объекта SAnimFrame с атрибутами XML-тэга
			void Bind( SAnimFrame & object )
                  // Attrib_Value - обычные атрибуты, которые будут прочитаны в соответствующее поля объекта без изменений
				Attrib_Value( "X", false, object.x );
				Attrib_Value( "Y", false, object.y );
				Attrib_Value( "W", true, object.w );
				Attrib_Value( "H", true, object.h );
				Attrib_Value( "FlipH", true, object.flipH );
				Attrib_Value( "FlipV", true, object.flipV );
                  // Attrib_SetterValue - а эти атрибуты будут записаны при помощи ф-ий SetX2 и SetY2, которые, на самом деле,
                  // превратят из в W и H - просто иногда было удобнее указывать размеры кадра так.
				Attrib_SetterValue( "X2", true, object, &SAnimFrame::SetX2 );
				Attrib_SetterValue( "Y2", true, object, &SAnimFrame::SetY2 );
			: XMLNamedObjectDeserializer( "Animation", false, "ID" )
              // Запоминаем, что внутри объекта анимации надо читать кадры
			SubDeserializer( m_frameDes );
          // Аналогичная операция для объекта анимации
		void Bind( SAnimProto & object )
			Attrib_Value( "FPS", false, object.m_fps );
			Attrib_Value( "Dir", true, object.m_dir );
			Attrib_Value( "Reverse", true, object.m_reverse );
			Attrib_Value( "FlipV", true, object.m_flipV );
			Attrib_Value( "FlipH", true, object.m_flipH );
			Attrib_Value( "OneShot", true, object.m_oneShot );
			Attrib_Value( "SoundEvent", true, object.m_soundEvent );
              // Прочитанные кадры надо добавлять в анимацию при помощи функции AddFrame
			m_frameDes.SetReceiver( object, &SAnimProto::AddFrame );
      // XMLDataDeserializer - класс для чтения данных без создания новых объектов
	XMLDataDeserializer m_imgDes;
	XMLDataDeserializer m_bgDes;
	XMLDataDeserializer m_capsDes;
      // А вот если мы встретили описание анимации - то новый объект надо будет выделить и заполнить
	AnimDeserializer m_animDes;
	void Bind( SGraphicsProto & object )
          // Это значение читается напрямую из тэга
		Attrib_Value( "Layer", false, object.m_layerID );
          // Добавлять новые анимации в SGraphicsProto будем функцией SetAnim
		m_animDes.SetReceiver( object, &SGraphicsProto::SetAnim );
          // Анимации - именованные, а значит их можно наследовать! Чтобы это работало,
          // указываем функцию, которая умеет по имени достать ранее прочитанную анимацию
		m_animDes.SetGetter( object, &SGraphicsProto::GetAnim );
          // Ну, а из этих тэгов мы будем читать по одному атрибуту.
		m_imgDes.Attrib_Value( "Path", false, object.m_image );
		m_bgDes.Attrib_Value( "Path", false, object.m_imageBg );
		m_capsDes.Attrib_SetterValue( "ID", false, object, &SGraphicsProto::SetCaps );
		: XMLNamedObjectDeserializer( "Graphic", true, "Name")
		, m_imgDes( "Image", false )
		, m_bgDes( "Bg", true )
		, m_capsDes( "Caps", false )
		SubDeserializer( m_imgDes ); 
		SubDeserializer( m_bgDes ); 
		SubDeserializer( m_animDes ); 
		SubDeserializer( m_capsDes ); 
  // Корневой десериализатор, который, по сути, проверит, что файл начинается с тэга Graphics и передаст управление десериализатору графики
class GraphicsDeserializer : public RootXMLDeserializer
	GraphicDeserializer m_graphicDes;
		: RootXMLDeserializer( "Graphics" )
		SubDeserializer( m_graphicDes ); 
  // А так выглядит использование подготовленного ранее класса:
void GraphicsProtoManager::LoadResources()
	GraphicsDeserializer root;
      // Графические объекты целиком тоже можно наследовать, поэтому им нужны Set и Get функции
	root.m_graphicDes.SetReceiver( *this, &GraphicsProtoManager::AddResource );
	root.m_graphicDes.SetGetter( *this, &GraphicsProtoManager::GetResource );
      // XMLDeserializer умеет загружать файл и передавать управление корневому десериализатору
	XMLDeserializer des( root );
      // Поехали!
	des.Deserialize( "Data/Protos/graphics.xml" );	

As you can see, it is very verbose, and not too convenient to use and support. However, writing hands-free loading using TinyXML bare calls would still be longer ... Strongly later, I wrote another deserialization option, more convenient, but slightly less functional, but unfortunately it remained within the framework of another, abandoned project . Maybe someday I will return to him.

08/19/11: Made prototypes for two enemy airplanes and ripped up sprites for them. Uploaded them to the game in test mode

This will be about this: on the screen, it was necessary to draw some kind of objects. And I myself am an artist solely from the word is bad, and there is no time to learn this craft. Therefore, it was decided to tear out all the graphics that interest me from the original game, and then replace it with something more interesting. With large static objects, everything was simple: we take a screenshot of the emulator window and cut out everything we need from there. But what to do with animated airplanes? If you capture different frames of animation with screenshots, you get tired VERY quickly ... Fortunately, the EmuZWin emulator, which has useful functions for viewing memory, helped me, moreover, viewing memory in graphical form. Using it, sorting through the different sizes of objects, I managed to get almost all the sprites that interest me:

Source graphics

The rest was a matter of technique and patience - each frame had to be found, cut, folded into a texture, painted in the desired color and selected animation parameters to look like the original.

09/25/11: VERSION 0.4 IS RELEASED (just like that, in capital letters, as in the file)

Version 0.4 was the first public debut of the game. I decided to take this step, because, in fact, I had a working game: the planes flew, they could be shot down, they fell on the target, punched holes in it, and if there were enough holes to break through, the target drowned and the level ended. Then, however, he immediately began anew, but that was no longer important. Thus, a little less than two months passed from the start of development to the first playable release.

Version 0.4 in all its glory

11/23/11: Started work on the object of a downed helicopter

Oh, helicopters! If you played the original, you should hate them just like me. Not only that, these creatures suddenly change direction and shoot rockets, so after you kill them, they become even more dangerous! A downed helicopter, unlike most other enemies, retains the ability to collide with a player and kill him, and he crashes not just like that, but with a quick zig-zag. The motivation for this design decision is simple: ordinary dangerous airplanes are the easiest to shoot down from below, because it’s easier to get into them, and it remains possible to turn away from a collision if you couldn’t get there. But helicopters (and later - bombers) just make the player change tactics.

Programming their behavior was a separate pleasure, since they are much more complicated than all previously encountered enemies, and my system for describing the behavior of enemies has already passed this test with some tension! A helicopter is the first enemy in the game that can turn around and shoot, and also has a complex logic of movement after death.

12/02/11: Finished work on auto-aiming

Having added several levels and new enemies to the game, I compared it with the original, and came to the conclusion that it became much more difficult to play. The fact is that in the original game, collisions were determined rather rudely, so getting into enemies was easier. And the screen was smaller, cramped. In the new game, a deviation from the desired course by several degrees could cause a miss, often fatal. It was not possible to compensate for this with control accuracy (maybe I didn’t have enough mind), so I decided to add auto-aiming to the game: setting the complexity, which would allow the flying cartridges to deviate slightly from the line, and fly to intercept the nearest target. I had to deal with the selection of parameters for a long time - after all, what should be considered the immediate goal? - but the result made the game more enjoyable, therefore, in the final version, auto-aiming is enabled by default.

12/04/11: I made a briefing between the levels, added comments during the course of the level

I have a bad habit - try to add history to any game. At one time, my friend and I even made an arkanoid with history! In fact, it, of course, is often in vain - not everywhere you need to shove dialogs and characters. But in Return of Dr. Destructo "I could not resist. In part, the briefings before the levels were born out of design necessity: I wanted to somehow break up the series of successive ships, castles and islands with moments of relaxation. In addition, I wanted to be able to somehow tell the player what awaits him at the next level in terms of new enemies. Indeed, in appearance it is completely impossible to understand which of them will knock you down in a collision and which ones are safe. Unfortunately, the last problem was not really solved in this way. Therefore, players are forced to suffer as I suffered in the distant 90s.

Conversations along the level were also born out of necessity: in the original game, in order to sink the target, it was necessary to punch three holes in it to the bottom. Each hole was displayed by a fountain of water. In my remake, this did not work, because now the falling enemies knocked out of the target not neat bricks, but round holes in arbitrary places. But somehow to make it clear to the player that he was closer to victory, it was necessary. As a result, I made a controversial decision at each level to show three text messages, each of which corresponds to about a third of the progress in drowning the goal. Later, a damage indicator was added, gradually filling in the lower left part of the UI, where the level name is displayed.

Phrasebooks in the ranks in the final version

02/06/12: Finished work on the Music component, used the irrKlang library instead of the Allegro sound API.

Another admission is that I usually play games with disabled sounds and music. Well, that's for sure with music. Even your favorite tunes are annoying if you twist them continuously, and with the authors of most games, the tastes in music completely differ (why shouldn't someone make a toy with a soundtrack from classic rockabilly? At least racing!). Therefore, I put off the insertion of music and sounds into my game for as long as I could. But that moment came ...

Actually, I wrote the whole game using the Allegro library. It may not be as widespread as SDL, but I like its API more, and in general, I’m with Allegro together with the DOS version that I found in the 11th grade when I first started learning C after QuickBasic and FutureLibrary.

But the Allegro sound API seemed too complicated to me at first. I wanted a simple one: create a sound, play a sound. Therefore, after some searches, I chose the irrKlang library for audio playback, the API of which corresponded more to my requests. This turned out to be a mistake: irrKlang leaked when playing tracker files (and the music in the game in the it format), the author refused to acknowledge and fix the problem, there were no scammers, and under Linux there were some kind of horrors. So then I had to cut it out, and still figure out how to work with sound in Allegro (it turned out - it's okay).

By the way, why is the music in Impluse Tracker format? In general, I am not a fan of trackers, in the sense that I never wrote music for them, and did not listen to it on purpose. Of course, I heard something - you yourself know where ...

I am not a musician, I don’t know musical notation, I’m not trained in musical theory. But somehow I can play the guitar. Therefore, I decided that I would try to write music for my game myself, especially since I had one composition ready for about a quarter. I wrote music in an honestly bought Guitar Pro 5, but the trouble was further: GP was able to export the results only to WAV or MIDI. I didn’t like Wavs, even squeezed into OGG or MP3: it turned out that the music would occupy more than the rest of the game combined. But Allegro (and irrKlang) did not know how to play MIDI. I had to set up a complex process - to upload a melody from Guitar Pro to MIDI, and then MPTracker to convert it into a tracker format that is understandable to existing libraries. Perversion? Undoubtedly! Works? Yes!

The first track, now playing during the level, was written based on the memory of PC-Speaker music from the Prehistorik game, but not from the very beginning, from which the heart of any student of the 90s begins to beat faster, “tada-tta, tada-tta”, but, it seems, from the third, forest level. Honestly, I later did not find, looking at the recordings on Youtube, the fragment that I thought I remembered and based on which I composed my melody.

The second track is also “based on”, this time, the rocky instrumental “Mohawk Twist” by Jackals. It’s unlikely that you heard about her. / hipster mode off

To listen to the tracks separately, if you do not want to play the game, click here:
Game Music 1: .it .ogg
Game Music 2: .it .ogg

09.03.12: AI started translating in Lua

In fact, AI in the game. In the sense that AI is something interactive, it must respond to the game situation, make decisions, act ... The enemies in the game fly according to strictly defined rules: fly 500 pixels straight, then drop 100 pixels, then continue again fly right to the end. Etc. Therefore, in reality, the place to be is not AI, but behavior scripts. But it is long, therefore everywhere further it will be called AI.

Initially, like all other game data, AI was described by XML, something like this:

For simple scripts, this was enough, although it was already inconvenient. However, towards the end of the game, enemies began to appear with more complex behaviors that required cycles, maths, and conditional statements. Implementing all this in XML seemed to me an incredibly bad idea, so, with some regret, the entire previous AI was thrown into the trash, and Lua and Luabind got into the game.

But I wanted to leave the general principle of work the same. Each line of the script should have set some simple behavior (fly down, fly up, shoot, turn around), which should continue until the specified trigger fires. To support this concept in Lua, I used the coroutine and Lua thread mechanism.

Coroutines are generally my old dream in terms of scripts. They allow you to interrupt the execution of the script at any place, return control to the calling code, and then, when you want, continue the script from the place where the last time you finished, with all state preserved. Today, coroutines can even be written in C (although it is not easy to do this cross-platform), but the new C ++ standard seems to even have ready-made language mechanisms. But in Lua everything is ready and convenient. Only one thing interferes: upon exiting coroutine, the state of the Lua virtual machine is saved in the Lua state, but the next call (calculating the AI ​​for another object) will overwrite this state with its own. You can do a lot of VM stats, but it's expensive. This is where Lua threads come to the rescue. In the platform sense of the word, they are not threads, that is, they do not generate platform threads, and are not executed at the same time. But they provide the ability to make lightweight copies of the state of Lua VM, just suitable for coroutine.

As a result, my new AI began to look something like this:

function height_change_up( context )
    local dx = RandomInt( 100, 900 )
    local dy = 100
    context:Control( Context.IDLE,		Trigger_DX( dx )			)
    context:Control( Context.CTRL_VERT_UP,	Trigger_DY( dy )			)
    context:Control( Context.IDLE,		Trigger_Eternal()			)

Which, you see, is much easier to write, and even read. All context functions are actually declared in yield in Luabind, that is, they return control until the next resume. Which will be done as soon as the condition of the specified trigger is met in C ++ code.

A few words about Luabind: this is hell. I will not argue about the shortcomings and advantages of its syntax and overhead, but I have to admit that it is now quite difficult to assemble it. Active development by the original developer has long been abandoned, and the new branch does not thrive at all. So if you integrate Lua and C ++ - consider more modern alternatives that, at least, do not require such an amount of Boost ... 04/19/12

: Added statistics for two types of Achievements

Honestly, I don’t remember why I decided to add Achievements to this game. It seems, in order to make the player again deviate from the optimal tactics for obtaining them. For example, achieving “High Flyer” requires the player to spend a lot of time in the upper half of the screen at one of the later levels. And this is a VERY difficult task! Usually, an experienced player will spin at the very bottom of the screen, emerging from there to attack the selected enemy aircraft. And here - if you want, you don’t want, but you have to fly in a danger zone, where the density of enemies is maximum.

I did not plan to do any integration with social networks so that achievements could be shared. But there was an idea to make it possible to unload the "order bar" in the form of a PNG file, with which the player could already do anything - paste into the forum, upload to Facebook ... But this idea was not implemented as a result (more precisely, it was removed from the final version) . I think no one will especially miss her ...

Final Achievement Screen

06/01/12: VERSION 0.9 RELEASED 0.9

0.9 was the latest version with old graphics. Everything that was supposed to work in the game already worked in it, and the most interesting and active part of the development was completed. Further, for half a year I was looking for an artist to draw new, beautiful graphics. I tried to do it myself, but the results, alas, were not impressive:

Self-medication Results

To begin with, I tried to simulate a player’s plane in Wings3D (the most programmer-friendly 3D editor, it seems to me). The result was not only bad, but also nothing good.

An attempt to modernize a ship from the first level - again, you can’t say that everything was completely bad, but you can see right away - " voiced by professional programmers!"

07/27/12: Started work on a training mode

Experiments on living people showed that players do not understand the purpose of the game. Many people think that the ship at the bottom of the screen needs to be protected, not destroyed. I myself recalled that I did not immediately guess what to do in this game when I played the original. But then I figured it out, and it became obvious to me. For others, no. And the times, now, are not those, if the player does not understand what to do in the game, he will close it ...

Therefore, I had to add a tutorial to the game. Generally speaking, the training mode in the game is usually one of the most vile and crutchy parts, since it violates all game mechanics, creeps into the UI code and does other nasty things that are not provided for by the architecture. In the process of adding a training mode to the game, I previously participated three times, and did not want to repeat this experience. Therefore, I decided to get by with a little blood: to show the player what and how here with text and frames. Yes, “show, don't tell,” but ... Whoever doesn’t like reading texts in games is not my friend!

As a result, the training mode was created quite quickly and without much intrusion into the mechanics. Then, however, already in 2014-2015, I had to finish it to show the control scheme, since not everyone immediately got into Options, and the layout of the keys did not come out obvious. But this is a slightly different story ...

08/18/14: Started work on support for gamepads in the game
08/19/14: Gamepad earned

Ha ha ha He earned, yeah ... No, do not get me wrong - adding the gamepad support to the game was really very simple, thanks to the Allegro library. But then a little embarrassment arose: firstly, in the control settings menu it looked something like this: “Fire: Button 11”. And secondly, in the Training mode, it was necessary to draw this Button 11 somehow, so that the player could understand what to press in order to shoot. No, some games just leave Press [Button 11] to fire. But this is ugly, because the player doesn’t have to know what kind of button he goes on the gamepad in the XInput mode under the index 11 (especially since under Linux, for example, the same button can have a completely different index!).

On the other hand, there is a mechanism that would make it easy and cross-platform to say that “Stick 2 Axis 1” is the right stick, the vertical axis is not. The problem was partially resolved in SDL by introducing a database with descriptions of different gamepads, but it is not compatible with Allegro in any way, due to some differences in working with joysticks.

In addition, each desktop OS (Windows, Linux, MacOS X) has two APIs for working with gamepads, each with its own suit.

DirectInput and XInput screw Allegro abstracts quite well, but only here it translates XInput constants like “XINPUT_GAMEPAD_A” into indexes, and again we get “Button 11”.

Under Linux, I managed to get my Logitech F300 to work only in XInput mode, and, suddenly, the triggers there go not from 0 to 1, like on Windows, but from -1 to 1, and -1 is a neutral value (the trigger is released) . Why so - from the driver code, I still do not understand. And the documentation claims that the values ​​of the ABS triggers should still be positive. But the negative ones come ...

Under MacOS X, the new trendy API supports XInput gamepads, though for some reason it does not support the Start and Back buttons (not to mention Guide / X-Box). And the old way of working with gamepads (used in Allegro) - through the HID Manager - is still black magic. By the way, if you yourself deal with this topic, you may wonder - why do these values ​​from the right stick come through GD_Z and GD_Rx? It seems to be somehow illogical, why not GD_Rx and GD_Ry? The answer is simple - because R is not “Right”, as you might think, but even “Rotational”. The USB HID standard does not know anything about gamepads with two sticks. This devilish invention on the PC came too late. But he knows about the controllers for airplane simulators, in which there is only one stick, but there may be additional rotation axes, these same Rx, Ry and Rz.

09/03/14: Transferred the assembly to CMake, restructured the repository

Initially, the game was built only under Windows. When I wanted to build it under Linux (in the region of version 0.9, or earlier), I found the MakeItSo utility, which converted my Visual Studio file to a Makefile. True, then he had to finish with pens, and in general ...

In short, when the question arose about assemblies on three platforms now, and even mobile ones in the future, I decided to put everything in order, and use CMake to generate projects for all platforms. Overall, the experience with CMake was very positive. The only negative is that there is no support for installing many parameters in projects for Visual Studio for new platforms (Android, through Tegra NSight or in VS2015, Emscripten). The problem could be solved by adding a keyword to connect props files to the project, but in the mailing list CMake says that this is contrary to ideology ... Of course, CMake has other drawbacks, but it is better than all alternatives, whether it's writing the assembly file with your hands under every platform, or using gyp. The biggest problem was the build for MacOS X, since the idea of ​​app bundle CMake is supported somewhat skeptically:

10.27.14: Integrated into the game Google Breakpad.

About the integration of Google Breakpad, I wrote a whole article in my blog. In short, I did not expect such inconvenience from Google, I would even say unprofessionalism in terms of organizing code. The code itself, I don’t know, can be good, maybe bad - I didn’t really look. But the fact that for the sake of connecting a small, in fact, library, it is proposed to pump it out and drag the entire huge repository with tests, sorts for other platforms and other garbage - this is all very inconvenient.

But the main thing is that in the end I managed to integrate Breakpad and raise the server on my host. True, I haven’t received a single crash report (except for test ones) - either the game is written so well that it doesn’t crash, or the crash sending system still doesn’t work!

02/26/15: Completed the effect of the loss of life

One of the complaints during the beta testing of the final version was that it was not always clear that your plane was shot down. And it was not particularly clear who shot him down. Therefore, I decided to add a special effect - cracks running across the screen, and also show in a special window a picture of that specific enemy that the player unsuccessfully hit. Of course, this did not completely fix the already mentioned problem of distinguishing between dangerous and safe aircraft, but it allowed the player to give at least a little more information.

And the effect with cracks on the glass came to my mind as a memory of another game with the ZX Spectrum - Night Gunner .

Game over

04/13/15: FINISHED VERSION 1.0

On April 13th, I collected all the builds for all platforms and uploaded them to the site. From now on, Return of Dr. Destructo set sail. But the story of the game is not over yet - now I am working on porting the game to mobile platforms. So the Progress.txt file is not yet completely closed!

The project code is available on Github under the MIT license, and resources are CC-BY-SA.
If you want to get acquainted with the game without collecting it, then the binary assemblies are on the project website .

Thanks for attention!

Also popular now: