The first article about the game in development Space Jaguar Action RPG. In this article I will describe the gameplay feature of the Jaguar – Characteristics.
Many RPGs use a static system of character stats, such as those from DnD (Strength, Constitution, Dexterity, Intelligence, Wisdom, Charisma), or Fallout’s S.P.E.C.I.A.L (Strength, Perception, Endurance, Charisma, Intelligence, Dexterity, Luck).
In Space Jaguar, I plan to implement a dynamic system of characteristics, for example, the main character of the game, Jag, at the start has only three characteristics – Blade mastery (half-sabre), shadow operations (making deals in the criminal world), rogue abilities (picking locks, stealing). During the game, characters will be given and deprived of dynamic characteristics within the game module, all checks will be made based on the level of certain characteristics necessary for a given game situation. For example, Jag will not be able to win a game of chess if he does not have the chess game characteristic, or does not have a sufficient level to pass the check.
To simplify the logic of checks, each characteristic is given a 6-digit code in English letters, a name, a description. For example, for blade ownership:
bladeFightingAbility.name = "BLADFG";
bladeFightingAbility.description = "Blade fighting ability";
bladeFightingAbility.points = 3;
Перед стартом игрового модуля можно будет просмотреть список публичных проверок необходимых для прохождения, также создатель может скрыть часть проверок для создания интересных игровых ситуаций.
Ноу-хау? Будет ли интересно? Лично я нахожу такую систему интересной, позволяющей одновременно обеспечить свободу творчества создателям игровых модулей, и возможность переноса персонажей из разных, но похожих по характеристикам, модулей для игроков.
Skeletal Animation (Part 2 – Node Hierarchy, Interpolation)
I continue to describe the skeletal animation algorithm as it is implemented in the Flame Steel Engine.
Since this is the most complex algorithm I’ve ever implemented, there may be errors in the development notes. In the previous article about this algorithm, I made a mistake: the bone array is passed to the shader for each mesh separately, not for the entire model.
Hierarchy of nodes
For the algorithm to work correctly, the model must contain a connection between the bones (graph). Let’s imagine a situation in which two animations are played simultaneously – a jump and raising the right hand. The jump animation must raise the model along the Y axis, while the animation of raising the hand must take this into account and rise together with the model in the jump, otherwise the hand will remain in place on its own.
Let’s describe the node connection for this case – the body contains a hand. When the algorithm is processed, the bone graph will be read, all animations will be taken into account with correct connections. In the model’s memory, the graph is stored separately from all animations, only to reflect the connectivity of the model’s bones.
Interpolation on CPU
In the previous article I described the principle of rendering skeletal animation – “transformation matrices are passed from the CPU to the shader at each rendering frame.”
Each rendering frame is processed on the CPU, for each bone of the mesh the engine gets the final transformation matrix using interpolation of position, rotation, magnification. During the interpolation of the final bone matrix, the node tree is traversed for all active node animations, the final matrix is multiplied with the parents, then sent to the vertex shader for rendering.
Vectors are used for position interpolation and magnification, quaternions are used for rotation, since they are very easy to interpolate (SLERP) unlike Euler angles, and they are also very easy to represent as a transformation matrix.
How to Simplify Implementation
To simplify debugging of vertex shader operation, I added simulation of vertex shader operation on CPU using macro FSGLOGLNEWAGERENDERER_CPU_BASED_VERTEX_MODS_ENABLED. Video card manufacturer NVIDIA has a utility for debugging shader code Nsight, perhaps it can also simplify development of complex algorithms of vertex/pixel shaders, however I never had a chance to check its functionality, simulation on CPU was enough.
In the next article I plan to describe mixing several animations, fill in the remaining gaps.
Sources
https://www.youtube.com/watch?v= f3Cr8Yx3GGA
Space Jaguar 3D Action RPG
I haven’t announced new projects for a long time) The next project I’m starting to work on is a 3D action RPG called Space Jaguar. The story is in a sci-fi setting about a cool guy named Jag and his difficult adventure in search of his missing father. There will be 3D graphics on the Flame Steel Engine (or perhaps any other popular one), using the developments of past projects (Death Mask, Cube Art Project), a comedy plot with many references, arcade battles and bosses. I’m not ready to talk about the release dates of the full version, I plan to release the game in parts.
Project repository:
https://gitlab.com/demensdeum/space-jaguar-action-rpg
ZX Spectrum Game Development in C
This is a silly post about developing a game for an old ZX Spectrum computer in C. Let’s take a look at the beauty:
It began production in 1982 and was produced until 1992. Technical characteristics of the machine: 8-bit Z80 processor, 16-128 KB of memory and other extensions, such as the AY-3-8910 sound chip.
As part of the Yandex Retro Games Battle 2019 competition, I wrote a game called Interceptor 2020 for this machine. Since I didn’t have time to learn assembler for the Z80, I decided to develop it in C. As a toolchain, I chose the ready-made set – z88dk, which contains C compilers and many auxiliary libraries to speed up the implementation of applications for the Spectrum. It also supports many other Z80 machines, such as MSX, Texas Instruments calculators.
Next I will describe my superficial flight over the computer architecture, the z88dk toolchain, and show how I managed to implement the OOP approach and use design patterns.
Installation Features
Installation of z88dk should be carried out according to the manual from the repository, however for Ubuntu users I would like to note a feature – if you already have compilers for Z80 installed from deb packages, then you should remove them, since z88dk will access them from the bin folder by default, due to incompatibility of the toolchain compiler versions you most likely will not be able to build anything.
Hello World
Writing Hello World is very simple:
#include
void main()
{
printf("Hello World");
}
It’s even easier to assemble a tap file:
zcc +zx -lndos -create-app -o helloworld helloworld.c
To run, use any ZX Spectrum emulator with tap file support, for example online:
http://jsspeccy.zxdemo.org/
Draw on the picture on the whole screen
tl;dr Pictures are drawn in tiles, tiles of size 8×8 pixels, the tiles themselves are embedded in the spectrum font, then the picture is printed in a line of indices.
The sprite and tile output library sp1 outputs tiles using UDG. The image is translated into a set of individual UDGs (tiles), then assembled on the screen using indices. It should be remembered that UDG is used to output text, and if your image contains a very large set of tiles (for example, more than 128 tiles), then you will have to go beyond the set boundaries and erase the default Spectrum font. To get around this limitation, I used a base of 128 – 255 by simplifying the images, leaving the original font in place. About simplifying the images below.
To draw full-screen images, you need to arm yourself with three utilities:
Gimp
img2spec
png2c-z88dk
There is a way for real ZX men, real retro warriors: open a graphic editor using the spectrum palette, knowing the features of image output, prepare it manually and upload it using png2c-z88dk or png2scr.
The simpler way is to take a 32-bit image, switch the number of colors to 3-4 in Gimp, edit it a little, then import it into img2spec so as not to work with color restrictions manually, export png and convert it to a C array using png2c-z88dk.
Please remember that for successful export each tile cannot contain more than two colors.
As a result, you will receive an h file that contains the number of unique tiles. If there are more than ~128, then simplify the image in Gimp (increase the repeatability) and perform the export procedure again.
After exporting, you literally load the “font” from the tiles and print the “text” from the tile indices on the screen. Here is an example from the render “class”:
// грузим шрифт в память
unsigned char *pt = fullscreenImage->tiles;
for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
}
// ставим курсор в 0,0
sp1_SetPrintPos(&ps0, 0, 0);
// печатаем строку
sp1_PrintString(&ps0, fullscreenImage->ptiles);
Drawing sprites on the screen
Next I will describe the method of drawing 16-16 pixel sprites on the screen. I did not get to animation and color changes, because at this stage, as I suppose, I simply ran out of memory. Therefore, the game contains only transparent monochrome sprites.
Draw a monochrome png image 16×16 in Gimp, then use png2sp1sprite to translate it into an asm assembler file, declare arrays from the assembler file in C code, and add the file at the build stage.
After the stage of declaring the sprite resource, it must be added to the screen in the desired position, here is an example of the code for the “class” of the game object:
struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2, SP1_TYPE_2BYTE, col2-col1, 0);
sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB, SP1_TYPE_2BYTE, 0, 0);
sp1_IterateSprChar(bubble_sprite, initialiseColour);
From the names of the functions you can roughly understand the meaning – we allocate memory for the sprite, add two columns 8-8, add color for the sprite.
In each frame the position of the sprite is set:
sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);
Emulating OOP
There is no syntax for OOP in C, what to do if you still really want it? You need to connect your mind and be enlightened by the thought that such a thing as OOP equipment does not exist, everything ultimately comes to one of the machine architectures, in which there is simply no concept of an object and other related abstractions.
This fact for a long time prevented me from understanding why OOP is needed at all, why it should be used if in the end everything comes to machine code.
However, having worked in product development, I discovered the charms of this programming paradigm, mainly, of course, the flexibility of development, protective mechanisms of the code, with the right approach, reducing entropy, simplifying work in a team. All the listed advantages follow from the three pillars – polymorphism, encapsulation, inheritance.
It is also worth noting the simplification of solving issues related to application architecture, because 80% of architectural problems were solved by computer scientists in the last century and described in the literature on design patterns. Next, I will describe ways to add OOP-like syntax to C.
It is more convenient to use C structures as a basis for storing class instance data. Of course, you can use a byte buffer, create your own structure for classes, methods, but why reinvent the wheel? After all, we are already reinventing the syntax.
Class data
Example of GameObject “class” data fields:
struct GameObjectStruct {
struct sp1_ss *gameObjectSprite;
unsigned char *sprite_col;
unsigned char x;
unsigned char y;
unsigned char referenceCount;
unsigned char beforeHideX;
unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;
We save our class as “GameObject.h”, #include “GameObject.h” in the right place and use it.
Class Methods
Let’s take the experience of Objective-C language developers into account, the class method signature will be functions in the global scope, the first argument will always be the data structure, then the method arguments. Below is an example of a “method” of the “class” GameObject:
void GameObject_hide(GameObject *gameObject) {
gameObject->beforeHideX = gameObject->x;
gameObject->beforeHideY = gameObject->y;
gameObject->y = 200;
}
The method call looks like this:
GameObject_hide(gameObject);
Constructors and destructors are implemented in the same way. It is possible to implement a constructor as an allocator and field initializer, but I prefer to control object allocation and initialization separately.
Working with memory
Manual memory management of the form using malloc and free wrapped in new and delete macros to match C++:
#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)
For objects that are used by several classes at once, semi-manual memory management is implemented based on reference counting, in the image and likeness of the old Objective-C Runtime ARC mechanism:
void GameObject_retain(GameObject *gameObject) {
gameObject->referenceCount++;
}
void GameObject_release(GameObject *gameObject) {
gameObject->referenceCount--;
if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
sp1_DeleteSpr(gameObject->gameObjectSprite);
delete(gameObject);
}
}
Thus, each class must declare the use of a shared object with retain, and release ownership with release. Modern ARC uses automatic placement of retain/release calls.
We’re making noise!
The Spectrum has a tweeter capable of playing 1-bit music; composers of that time were able to play up to 4 sound channels on it simultaneously.
Spectrum 128k contains a separate sound chip AY-3-8910, which can play tracker music.
A library is provided for using the buzzer in z88dk
What is yet to be discovered
I was interested in getting acquainted with the Spectrum, implementing the game using z88dk, learning many interesting things. I still have a lot to learn, for example, the Z80 assembler, since it allows you to use the full power of the Spectrum, working with memory banks, working with the AY-3-8910 sound chip. I hope to participate in the contest next year!
Links
https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/
Source code
https://gitlab.com/demensdeum/ zx-projects/tree/master/interceptor2020
Death Mask Wild Beta
Death-Mask game enters public beta (wild beta)
The game’s main menu screen has been redesigned, a view of the blue zone of the techno-labyrinth has been added, with pleasant music in the background.
Next, I plan to rework the gameplay controller, add smooth movement like in old shooters, high-quality 3D models of boxes, weapons, enemies, the ability to move to other levels of the techno-labyrinth not only through portals (elevators, doors, falling through holes in the floor, holes in the walls), I will add a little variety to the environment of the generated labyrinth. I will also work on the game balance.
Skeletal animation will be added during the polishing phase before release.
The Good, the Bad and the Ugly Singleton
In this note, I will describe my experience and the experience of my colleagues when working with the Singleton pattern (Singleton in foreign literature), when working on various (successful and not so) projects. I will describe why I personally think this pattern cannot be used anywhere, I will also describe what psychological factors in the team affect the integration of this anti-pattern. Dedicated to all fallen and crippled developers who tried to understand why it all started with one of the team members bringing a cute little puppy, easy to handle, not requiring special care and knowledge on caring for it, and ended with the fact that the bred beast took your project hostage, demands more and more man-hours and eats up the human-nerves of users, your money and draws absolutely monstrous figures for assessing the implementation of seemingly simple things.
Wolf in sheep’s clothing by SarahRichterArt
The story takes place in an alternate universe, all coincidences are random…
Pet a cat at home with Cat@Home
Every person sometimes in life has an irresistible desire to pet a cat. Analysts around the world predict that the first startup to create an application for the delivery and rental of cats will become extremely popular, and in the near future will be bought by the company Moogle for trillions of dollars. Soon this happens – a guy from Tyumen creates the application Cat@Home, and soon becomes a trillionaire, the company Moogle gets a new source of income, and millions of stressed people get the opportunity to order a cat to their home for further petting and calming.
Attack of the Clones
A very rich dentist from Murmansk, Alexey Goloborodko, impressed by an article about Cat@Home from Forbes, decides that he also wants to be astronomically rich. To achieve this goal, through his friends, he finds a company from Goldfield – Wakeboard DevPops, which provides software development services, he orders the development of a clone of Cat@Home from them.
Team of Winners
The project is called Fur&Pure, a talented team of 20 developers is assigned; then we focus on a mobile development group of 5 people. Each team member gets their share of the work, armed with agile and scrum, the team completes the development on time (in six months), without bugs, releases the application in the iStore, where 100,000 users rate it at 5, many comments about how wonderful the application is, how wonderful the service is (it is an alternative universe, after all). The cats are ironed, the application is released, everything seems to be going well. However, Moogle is in no hurry to buy a startup for trillions of dollars, because Cat@Home has already appeared not only cats but also dogs.
The dog barks, the caravan moves on
The app owner decides it’s time to add dogs to the app, asks the company for an estimate, and gets about at least six months to add dogs to the app. In fact, the app will be written from scratch again. During this time, Moogle will add snakes, spiders, and guinea pigs to the app, and Fur&Pur will only get dogs.
Why did this happen? The lack of a flexible application architecture is to blame, one of the most common factors being the Singleton design anti-pattern.
What’s wrong?
In order to order a cat to your home, the consumer needs to create an application and send it to the office, where the office will process it and send a courier with the cat, the courier will then receive payment for the service.
One of the programmers decides to create a class “CatApplication” with the necessary fields, and puts this class in the global application space via a singleton. Why does he do this? To save time (a penny saving of half an hour), because it is easier to put the application in general access than to think through the application architecture and use dependency injection. Then the other developers pick up this global object and bind their classes to it. For example, all the screens themselves access the global object “CatApplication” and show the application data. As a result, such a monolithic application is tested and released.
Everything seems fine, but suddenly a customer appears with a requirement to add applications for dogs to the application. The team frantically begins to estimate how many components in the system will be affected by this change. Upon completion of the analysis, it turns out that 60 to 90% of the code needs to be redone to teach the application to accept in the global singleton object not only “ApplicationForCat” but also “ApplicationForDog”. It is useless to estimate the addition of other animals at this stage, at least two can be handled.
How to avoid singleton
First, at the requirements gathering stage, clearly indicate the need to create a flexible, extensible architecture. Second, it is worth conducting an independent examination of the product code on the side, with a mandatory study of weak points. If you are a developer and you like singletons, then I suggest you come to your senses before it is too late, otherwise sleepless nights and burnt nerves are guaranteed. If you are working with a legacy project that has a lot of singletons, then try to get rid of them as quickly as possible, or from the project.
You need to switch from the anti-pattern of singletons-global objects/variables to dependency injection – the simplest design pattern in which all the necessary data is assigned to the class instance at the initialization stage, without any further need to be tied to the global space.
Sources
https://stackoverflow. com/questions/137975/what-is-so-bad-about-singletons
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
https://blog.ndepend.com/singleton-pattern-costs/
Grinding gears
Oh muse, how difficult it is to catch you sometimes.
The development of Death-Mask and related frameworks (Flame Steel Core, Game Toolkit, etc.) is suspended for several months in order to decide on the artistic part of the game, the musical and sound accompaniment, and to think through the gameplay.
The plans include creating an editor for the Flame Steel Game Toolkit, writing an interpreter for game scripts (based on the Rise syntax), and implementing the Death-Mask game for as many platforms as possible.
The most difficult stage has been passed – the possibility of writing your own cross-platform game engine, your own IDE, and a set of libraries has been proven in practice.
I’m moving on to the stage of creating a truly thoughtful, interesting project, stay tuned.
Fighting Malevich, OpenGL black squares
Malevich comes to any OpenGL developer from time to time. It happens unexpectedly and boldly, you just launch the project and see a black square instead of a wonderful render:
Today I will describe the reason why I was visited by a black square, the problems found due to which OpenGL does not draw anything on the screen, and sometimes even makes the window transparent.
Use tools
Two tools helped me debug OpenGL: renderdoc and apitrace. Renderdoc is a tool for debugging the OpenGL rendering process, you can view all vertices, shaders, textures, debug messages from the driver. Apitrace is a tool for tracing graphics API calls, makes a dump of calls and shows arguments. There is also a great opportunity to compare two dumps via wdiff (or without it, but not so convenient)
Check who you work with
I have Ubuntu 16.10 operating system with old dependencies SDL2, GLM, assimp, GLEW. In the latest version of Ubuntu 18.04 I get a build of the game Death-Mask which shows nothing on the screen (only a black square). When using chroot and building in 16.10 I get a working build of the game with graphics.
Looks like something is broken in Ubuntu 18.04
LDD showed linking to identical libraries SDL2, GL. Running a non-working build in renderdoc, I saw garbage at the entrance to the vertex shader, but I needed more solid confirmation. In order to understand the difference between the binaries, I ran them both through apitrace. Comparison of dumps showed me that the build on a fresh Ubuntu breaks the transfer of perspective matrices to OpenGL, actually sending garbage there:
The matrices are built in the GLM library. After copying GLM from 16.04 – I got a working build of the game again. The problem turned out to be the difference in the initialization of the identity matrix in GLM 9.9.0, it is necessary to explicitly specify the argument mat4(1.0f) in the constructor. Having changed the initialization and written to the author of the library, I started making tests for FSGL. In the process of writing which I discovered shortcomings in FSGL, I will describe them below.
Decide who you are in life
To work correctly with OpenGL, you need to voluntarily or forcibly request a context of a certain version. This is how it looks for SDL2 (you need to specify the version strictly before initializing the context):
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3);SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2);SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
For example, Renderdoc does not work with contexts below 3.2. I would like to note that after switching the context, there is a high probability of seeing that same black screen. Why?
Because OpenGL 3.2 context necessarily requires the presence of a VAO buffer, without which 99% of graphics drivers do not work. Adding it is easy:
glGenVertexArrays(1, &vao);glBindVertexArray(vao);
Don’t sleep, you’ll freeze
I also encountered an interesting problem on Kubuntu, instead of a black square I got a transparent one, and sometimes everything was rendered correctly. I found a solution to this problem on Stack Overflow:
https://stackoverflow.com/questions/38411515/sdl2-opengl-window-appears-semi-transparent-sometimes
The FSGL test render code also had sleep(2s); So on Xubuntu and Ubuntu I got the correct render and sent the application to sleep, but on Kubuntu I got a transparent screen in 80% of cases when launched from Dolphin and 30% of launches from the terminal. To solve this problem, I added rendering in each frame, after polling SDLEvent, as recommended in the documentation.
Test code:
https://gitlab.com/demensdeum/FSGLtests/blob/master/renderModelTest/
Talk to the driver
OpenGL supports a communication channel between the application and the driver. To activate it, you need to enable the GL_DEBUG_OUTPUT, GL_DEBUG_OUTPUT_SYNCHRONOUS flags, set the glDebugMessageControl notification, and bind the callback via glDebugMessageCallback.
An example of initialization can be found here:
https://github.com/rock-core/gui-vizkit3d/blob/master/src/EnableGLDebugOperation.cpp