📜 ⬆️ ⬇️

Development of Return of Dr. Destructo: What Progress Reached

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



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

I conducted my Progress.txt in English, but for this article all entries will be translated into Russian.
')
08/02/11: Fiddled with the sky, water, the sun and the moon
Development of the project began in 2011, after the next, larger-scale home project again rotten. I wanted to do something that I can definitely bring to the end something simple, but still interesting. Create version of “Island of Dr. Destructo ”for the PC was my old idea. This game is very memorable since childhood, when it came to me, among others, on a cassette of “airplane games” brought from the Tsaritsyn market. The main feature that struck me then was a destructible level: every downed enemy, every abandoned bomb pulled a piece from an enemy ship, and not some pre-selected by the authors of the game, but a specific one, exactly in the place where it was hit! This and now is rarely found in games, and then - well, it was just ah!

I started the development by drawing the sky and water. In the original game, there was a change in the time of day, which looked like the passage of the sun and moon across the sky, and also, at night the stars appeared. 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 defined by the key points between which the game interpolates the corresponding values ​​linearly.

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

Days of the day





08/10/11: Finished refactoring
While I was fiddling with the sky, all code lived in a pair of classes created and called from main (), but then it was time to think about architecture. My entire previous one 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 formulation of the problem leads to the fact that several abstractions must be broken at once and that what used to be independent, or vice versa.

On the other hand, to abandon encapsulation completely and put everything in one pile is also a direct path to hell. Just at that moment when I started writing “Return of Dr. Destructo ”, at work, the boss talked about the component approach. I must say, I understood him weakly (as it turned out later). But on the basis of the understanding that was, I, nevertheless, thought up some architecture. Looking ahead, I will say that it turned out to be quite successful: I have never rewrote its large pieces, and the number of absolutely nasty crutches remained minimal. On the other hand, if you have Unity before your eyes on the component architecture, then I’ll say right away that I didn’t do quite that.

So, how is the game architecture organized? Everything that relates to any one subsystem - sound, graphics, physics, mechanics - is rendered into a separate component. There is also a GameObject component that unites them all, which can no longer do anything, but only contains the IDs of other components. It is the ID - I did not use any kind of links, for which I paid - the access code to the components of the objects was inconvenient. However, unlike Unity, the component is a very dull thing. It is just a data structure that lies in some sort of array. It does not contain methods (with the exception, perhaps, of some simple auxiliary), and all data in it is public.

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

Since the processors do not have access to "alien" components, data duplication is forced. For example, an object must have physical, and graphic coordinates. In a more complex game, they might not even coincide, but here they are always the same, if no error occurred. However, there is no common place to store them, so at some point you have to copy them from the physical component (which changes them) to the graphic component (which they need to know where to draw the object). Such copying, as well as other inter-component interaction, 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.

The file of this class, at first glance, may be a little scary with its two thousand lines, but in reality everything is not so bad, just a few more auxiliary classes were just described here, which it was time to take out to separate files for a long time, but the hands never reached. In the main state class, the main set of methods is all sorts of things like ControlsToPhysics, PhysicsToSound, etc., which are 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 Automata , well 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, entering and updating over time receives only the topmost state object, and all are drawn (this is so that, for example, you can draw the state of the Teaching Mode over the usual Level state); secondly, it is possible to remove states not only from the top of the stack, but also from the middle - all states above the one being deleted will be removed, since they are considered “children”.

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

08/12/11: Started working on the XML deserialization system (basic things are already working)
08.16.11: Instead of deserialization, some crap came out. The whole damn system must be rewritten from scratch.
08/18/11: Bye finished messing around with deserializations (but she’s still damn ugly anyway)

C ++ and (de) serialization of these objects is an endless topic. In any case, while in the next standard they do not screw at least some kind of reflection. Prior to writing “Return of Dr. Destructo ”I had experience with several samopisnymi (not me) systems, as well as with Boost.Serialization (oh, that was one more experience ...). Therefore, I understood that it was beautiful, convenient and just the task was not solved. But I didn’t want to write endless loops on elements in the XML file either, so I decided to make my own system for loading data from XML into named objects of the same type.

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

A small lyrical digression on the topic "why is all this necessary." First, the definition: “The prototype of an object” in my personal lexicon, drawn from the first job, is a class containing data common to all objects of this type. Depending on the need, the data from the prototype can be copied and modified later, or the object can hold a reference to the prototype, and then this data will be unchanged. Here are the data for these very prototypes and I had to download from XML files.

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

A terrible and terrible example of deserialization of a graphic object prototype
//      SGraphicsProto   ID.    . class GraphicDeserializer : public XMLNamedObjectDeserializer<SGraphicsProto, std::string> { //      (  ID  ,  , AnimationID) class AnimDeserializer : public XMLNamedObjectDeserializer<SAnimProto, AnimationID> { //     ,   ID  ,     class FrameDeserializer : public XMLObjectDeserializer<SAnimFrame> { public: FrameDeserializer() : XMLObjectDeserializer<SAnimFrame>( "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<SAnimFrame, int>( "X2", true, object, &SAnimFrame::SetX2 ); Attrib_SetterValue<SAnimFrame, int>( "Y2", true, object, &SAnimFrame::SetY2 ); } }m_frameDes; public: AnimDeserializer() : XMLNamedObjectDeserializer<SAnimProto, AnimationID>( "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 ); } }; private: // 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<SGraphicsProto>( 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<SGraphicsProto, int>( "ID", false, object, &SGraphicsProto::SetCaps ); } public: GraphicDeserializer() : XMLNamedObjectDeserializer<SGraphicsProto, std::string>( "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 { public: GraphicDeserializer m_graphicDes; GraphicsDeserializer() : RootXMLDeserializer( "Graphics" ) { SubDeserializer( m_graphicDes ); } }; //       : void GraphicsProtoManager::LoadResources() { GraphicsDeserializer root; //      ,    Set  Get  root.m_graphicDes.SetReceiver<GraphicsProtoManager>( *this, &GraphicsProtoManager::AddResource ); root.m_graphicDes.SetGetter<GraphicsProtoManager>( *this, &GraphicsProtoManager::GetResource ); // XMLDeserializer         XMLDeserializer des( root ); // ! des.Deserialize( "Data/Protos/graphics.xml" ); } 



As you can see, it is quite verbose, and not very convenient to use and support. However, writing the load with my hands using the bare calls TinyXML would still be longer ... Much later, I wrote another deserialization option, more convenient, but slightly less functional, but, unfortunately, it remained in another, abandoned project . Maybe someday I will come back to him.

08/19/11: Made prototypes for two enemy airplanes and ripped sprites for them. I loaded them into the game in test mode

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

Original 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, so that it looked like the original.

09/25/11: VERSION 0.4 ISSUED (exactly in big 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 airplanes flew, they could be shot down, they fell on the target, punched holes in it, and if there were a lot of holes to pierce, the target sank and the level ended. Then, however, immediately began anew, but it 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: Began work on the object of the downed helicopter

Oh, helicopters! If you played the original, you should hate them as much as I do. Not only do these creatures suddenly change their direction of movement and shoot rockets, so after you knock them off, they become even more dangerous! The downed helicopter, unlike most other enemies, retains the ability to collide with the player and kill him, and he falls not just like that, but as a quick zigzag. The motivation of this design solution is simple: the usual dangerous airplanes are the easiest way to knock down from the bottom, because it’s easier to get into them, and you can still turn away from a collision if you cannot get there. But the helicopters (and later - the bombers) just make the player change tactics.

Programming their behavior was a separate pleasure, since they are much more difficult than any previously encountered enemies, and my system of describing the behavior of enemies with some tension passed this test! The helicopter is the first enemy in the game, which 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 the collisions were determined quite roughly, so it was easier to hit the enemies. And the screen was smaller, closer. In the new game, a deviation from the required course of several degrees could cause a miss, often a fatal one. It was not possible to compensate for this accuracy of control (maybe it was not enough), so I decided to add auto-targeting to the game: setting a difficulty that would allow departing rounds to deviate a little from the straight line, and fly to intercept the nearest target. I had to mess around for a long time with the selection of parameters - after all, what should be considered the immediate goal? - but the result made the game more enjoyable, so in the final version, auto-aiming is enabled by default.

12/04/11: Made a briefing between levels, added comments along the course

I have a bad habit - try to add a story to any game. In due time, my friend and arkanoid did even with history! In fact, it is, of course, often vain - it is not necessary to push dialogues and characters everywhere. But in “Return of Dr. Destructo "I could not resist. Partially, the briefings before the levels were born out of design necessity: I wanted to somehow break the sequence 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. After all, in appearance it is absolutely impossible to understand which of them will knock you down in a collision, and which ones are safe. Unfortunately, the last problem was not resolved in this way. Therefore, players are forced to suffer just 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 goal, it was necessary to punch three holes in it to the bottom. Each hole was displayed by a fountain of water. It was not possible to do this in my remake, because now falling enemies knocked out from the target not neat bricks, but round holes in arbitrary places. But somehow it was necessary to make the player understand that he was closer to victory. 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 target. Later, a damage indicator was added, gradually filling in the lower left part of the UI, where the name of the level is displayed.

Speakers in the ranks in the final version




02/06/12: Finished working on the Music component, using the irrKlang library instead of the Allegro sound API

Another recognition is that I usually play games with muted sounds and music. Well, really with the music for sure. Even your favorite tunes bother you if you play them continuously, and the authors of most games have different tastes in music (why would anyone not make a classic rockabilly soundtrack toy? At least a race!). Therefore, I put aside the insertion of music and sounds into my game for as long as possible. But that moment came ...

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

But the Allegro sound API, at first, seemed too complicated to me. I wanted a simple one: create a sound, play a sound. Therefore, after some searches, I chose to play the sound of the irrKlang library, 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 it format), the author refused to acknowledge and repair, there were no sorts, and under Linux there were some horrors in general. Therefore, then I had to cut it, and I still had to figure out how to work with sound in Allegro (it turned out, nothing terrible).

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

I am not a musician, I don’t know musical notes, I’m not trained in music 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 lying somewhere around a quarter away. I wrote music in an honestly purchased Guitar Pro 5, but then there was trouble: GP could export the results of work only to WAV or MIDI. Wavs, even compressed in OGG or MP3, I did not like: it turned out that I would have more music than the rest of the game combined. And MIDI did not know how to play Allegro (and irrKlang). I had to adjust a complicated process - download the melody from Guitar Pro to MIDI, and then MPTracker to convert it into a tracker format that is understandable to existing libraries. Perversion? Sure! Works? Yes!

The first track, now playing during the level, was written according to the recollections of PC-Speaker music from Prehistorik, but not the one from the very beginning, from which the heart of any schoolchild of the 90s, “tada-tta, tada-tta”, and, it seems, from the third, forest level. Honestly, I didn’t find it then, looking at the Youtube recordings, that fragment that I thought I remembered, and based on which I composed my own melody.

The second track is also “based on,” this time, the rock-mocking instrumental of Mohawk Twist by the Jackals group. Hardly you heard about it. / hipster mode off

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

3/9/12: Began translating AI to Lua

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

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

 <FlyStraight Trigger="Delta_X" TriggerParam="$P1"> <Random ID="P1" From="100" To="900"/> </FlyStraight> <FlyDown Trigger="Delta_Y" TriggerParam="100"> </FlyDown> <FlyStraight Trigger="Forever"> </FlyStraight> 


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, mathematics, and conditional operators. It seemed to me an incredibly bad idea to implement all this in XML, so, with some regret, the entire previous AI was thrown into the dustbin, and Lua and Luabind nestled in the game.

But the general principle of work wanted to leave the same. Each line of the script had to set some simple behavior (fly down, fly up, shoot, turn around), which had to continue until the specified trigger worked. To support this concept in Lua, I used the mechanism of quorutine and lua-threads.

Korutiny is 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, whenever you want, continue the script from the place where you finished last time, preserving the entire state. Today, Korutin can even be written in C (although it is not easy to do it cross-platform), and there seems to be even ready language mechanisms in the C ++ standard. But in Lua everything is ready and convenient. Only one thing interferes: when you exit the corortina, the state of the Lua virtual machine is saved to the Lua state, but the next call (calculating AI for another object) will wipe this state with its own. You can make many VM steits, but this is 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 flows, and are not executed simultaneously. But they provide the ability to make lightweight copies of the state of VM Lua, just suitable for korutin.

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() ) end 


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

A few words about Luabind: this is one more hell. I will not argue about the shortcomings and merits of its syntax and the overhead, but the fact that it is now quite difficult to assemble is to be recognized. The active development of the original developer has long been abandoned, and the new branch is not thriving. 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 calculation for two types of achievements

Honestly, I don’t remember why I decided to add Achievements to this game. It seems, in order to force the player again to deviate from the optimal tactics to obtain them. For example, achieving “High Flyer” requires that a player 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 selected enemy airplanes. And here - here you want it or not, but you have to fly in the danger zone, where the density of enemies is at its maximum.

I didn’t plan to do any integration with social networks so that the achivki 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 - insert it into the forum, upload to Facebook ... But this idea was not implemented as a result (or rather, was removed from the final version) . I think no one will miss her especially ...

Achievement screen in the final version



06/01/12: VERSION 0.9 ISSUED

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

Self-treatment results

To begin with, I tried to model the plane of a player in Wings3D (the most user friendly editor to the programmer, I think). The result was not so bad, but nothing good.


An attempt to modernize the ship from the first level - again, it cannot be said that everything was completely bad, but it was immediately obvious - “ sounded drawn by professional programmers!”


07.27.12: Began work on the training mode

Experiments on living people have shown that players do not understand the purpose of the game. Many people think that the ship at the bottom of the screen must be defended, not destroyed. I myself remembered 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, if the player does not understand what to do in the game, he will close it ...

So I had to add a tutorial to the game. Generally speaking, the training mode in the game is usually one of the most disgusting and crutched parts, because it violates the whole game mechanics, climbs into the UI code and does other nasty things not provided for by the architecture. In the process of adding a training mode to the game, I previously participated three times already, and did not want to repeat this experience. Therefore, I decided to do without a little blood: with the text and frames, to show the player what is there. Yes, "show, don't tell", but ... Who does not like to read texts in games - that is not my friend!

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

08/18/14: I started work on supporting gamepads in the game
08/19/14: Gamepad has earned

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

On the other hand, the mechanism that would allow easily and cross-platform to say that “Stick 2 Axis 1” is the right stick, the vertical axis is not. The problem is partially solved in the SDL by introducing a database with a description of different gamepads, but it is in no way compatible with Allegro, due to some discrepancies in working with joysticks.

In addition, in each of the desktop operating systems (Windows, Linux, MacOS X) there are two APIs for working with gamepads, each with its own quirks.

Windows DirectInput and XInput Allegro abstracts quite well, but only XInput constants like "XINPUT_GAMEPAD_A" it translates into indices, 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 go there not from 0 to 1, as in Windows, but from -1 to 1, and -1 is a neutral value (the trigger is released) . Why so - from the driver code, I did not understand. And the documentation states that the values ​​of the ABS triggers should still be positive. But come the negative ...

Under MacOS X, the new trendy API supports XInput gamepads, although for some reason it does not support the Start and Back buttons (not to mention the Guide / X-Box). And the old way of working with gamepads (applied in Allegro) - through the HID Manager - is still black magic. By the way, if you yourself deal with this topic, then you might be surprised - 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” at all, as you might think, but even “Rotational”. The USB HID standard knows nothing about gamepads with two sticks.This devilish invention on the PC appeared too late. But he knows about the controllers for aircraft simulators, which have only one stick, but there may be additional axes of rotation, these are the Rx, Ry and Rz. Sly authors of gamepads simply use the first four axes to transfer values ​​from the left and right sticks, not bothering with their original purpose.

09/03/14: Translated the build to CMake, restructured the repository

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

In short, when I came up with the question of assembling on three platforms now, and more mobile - in the future, I decided to put everything in order, and use CMake to generate projects for all platforms. In general, the experience with CMake was very positive. The only drawback is that there is no support for setting many parameters in projects for Visual Studio for new platforms (Android, via Tegra NSight or in VS2015, Emscripten). The problem could be solved by adding a keyword to connect to the project props-files, but in the mailing list CMake say it contradicts the ideology ... Of course, CMake has other drawbacks, but it’s better than all the alternatives, be it writing the assembly file with your hands under each platform, or use gyp. The biggest problem was the build for MacOS X, since the idea of ​​the app bundle CMake is somewhat crutch-supported:you can’t just take and indicate which directory to put inside .app - you have to go over all the files and set properties to them.

10/27/14: Integrated into the Google Breakpad game

About the integration of Google Breakpad, I wrote a whole article on my blog. In short, I didn’t expect from Google such inconvenience, I would even say, lack of professionalism in terms of code organization. The code itself, I do not know, maybe it's good, maybe it's bad — I didn't particularly look. But the fact that for the sake of connecting a small, in fact, the library is proposed to pump out to yourself and drag the entire huge repository with tests, tools 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. However, I never received any crash reports (except for test ones) - whether the game is written so well that it does not fall, or the crash sending system does not work!

02.26.15: Finished the loss of life effect

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 had shot him down. Therefore, I decided to add a special effect — cracks scattering across the screen, and also to show in a special window a picture of that particular enemy that the player had unsuccessfully hit. Of course, this did not completely correct the already mentioned problem of distinguishing between dangerous and safe airplanes, but it allowed us to give the player a little more information.

And the cracked effect 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 went to free swimming. But the story of the game is not over yet - I am currently working on porting the game to mobile platforms. So the file Progress.txt is not completely closed yet!



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, not collecting it, then the binary builds are on the project website .

Thanks for attention!

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


All Articles