Monday, May 9, 2016

Abuse 1996 - SDL port 0.9a



When I released my Quake 2D demo back in 2012 many people compared it to Abuse, a game by Crack dot Com released in 1996. While I was waiting for my new PC to get fixed(it took months!) I was stuck with a PC bought in 2005. I already played everything I could on it and my gaming options were limited. I found out Abuse was available for free, so I wanted to check it out. It ran in DOSBox at 320x200 resolution and felt like playing a FPS game with very low FOV. Aiming with the mouse was very difficult, because even in fullscreen the mouse was still behaving like it was 320x200 resolution and was too sensitive.

Reading the included readme file I saw there was a high resolution option, but it seemed to be only available in the shareware version or in editor mode, and the game would automatically turn off the in-game lights, because it would be too demanding for the PCs in 1996 on high resolutions. Not to mention a bug would cause the entire screen to go black the second time a level was loaded. While looking for a fix I found out the original source code was released and there were several source ports released during the years, mostly for Linux. Finally Xenoveritas SDL2 port from 2014 showed up in the search results and was exactly what I needed; a working Windows port, plus it had instructions how to build it.

After I enabled custom resolutions and fixed the light, I also wanted to extract the textures, so I can use them in my own engine. Then I saw Xenoveritas never finished adding Xbox controller support, so I did it instead... it didn't stop there, and here is the final result in motion:


As you can see the game gets pretty intense. It's a classic design where you need to find switches to open new areas, and you need to cleverly use security turrets and destructible walls to fight the enemies. It takes 2-3 hours to beat the game.

 Here is the list of updates in Abuse SDL 0.9a compared to Xenoveritas version from 2014:

- Enabled custom resolutions and enabled lights on high resolutions
- Re-enabled OpenGL rendering to enable vsync
- Game screen scaling in window and fullscreen mode using F11 and F12
- Enabled some high resolution images from the 1997 Mac OS release
- Fixed level music not being played correctly, added "victory" music in the end game screen
- Fixed the health power image, fixed mouse image when choosing initial gamma
- Added or re-enabled various settings in the config file (borderless window, grab input, editor mode, high resolution images...)
- Local save game files and configuration files
- Quick load using F9, quick save using F5 on save consoles
- Added cheats via chat console: bullettime, god, giveall, flypower, sneakypower, fastpower, healthpower, nopower
- XBox360 controller support with rebindable buttons
- Updated abuse-tool so it can extract the images in Abuse SPEC files to modern image formats as individual images, tilemaps or a texture atlas with information about image, tile and animation frame sizes and positions


I only tested the game on my old 2005 PC and the new one, running Windows 32bit and 64bit. I would really appreciate some feedback, so I know it works or not. I would also like to know how playing with the Xbox controller feels, since I plan to use the same controls in my own engine. Please read the included README file for more information, controls and links to previous port releases and other stuff. You can download the game from this link or on ModDB:

md5: 16E116B753A2142E4AE2BB63C2A4A351


I already mentioned I wanted to extract the images, so here is an archive that contains all the extracted Abuse images in PNG format, together with information about image name, size and positions of individual frames of animation: 
 Aliens addon

md5: 5A2D1E65A2D76C6C0D340F600E2F7B5B

I also used a HMI to MIDI converter to convert the music to MIDI. I didn't convert them to wav or mp3, because different soundfonts give different results when playing MIDI music. You can download the MIDI files here:
md5: 5FC937F71DB047DA9E46961F83016CDA

The source code for Abuse SDL port 0.9a is available on my GitHub page. As I said the game is not fully tested and there are few issues which are described in the README file. The game physics are locked at 15 FPS, and the rendering is a bit slow, those are two major things I would like to eventually fix. Multiplayer doesn't work, but that is way out of my league:


Thank you for playing Abuse!

Saturday, April 9, 2016

Texture packing algorithm


I was exporting some textures from an old game, and needed an algorithm to pack many small textures into a texture atlas. I found a very nicely explained algorithm on this page and wanted to share, if someone might need something like this:

http://www.blackpawn.com/texts/lightmaps/default.html

There are some other interesting articles from the same guy here:

http://www.blackpawn.com/texts

The packing algorithm is so simple, I didn't think it would work. You just create a node with the output texture size and loop trough your images and call Insert() with individual texture's width and height. The node recursively splits itself if its size doesn't match the input, and calls Insert() of the child nodes. The node that gets returned holds the final [x,y] position, and if it returns NULL it means the image couldn't fit.

The grey lines mark individual textures and animations that I grouped together in code, so it doesn't all get mixed, but that is another story.



Tip 1.

In the example above I sorted my images according to their size(width*height) before I started to call Insert().

Tip 2.

If you look at the examples from the link, you will see the images are stored in an upside-down L pattern starting from top left, so if your output image is too big the input images will be positioned in the L pattern and the rest of the space(bottom right) is left unused. To avoid that;
- I sum the size of all the textures and set node width and height to sqrt(sum), so I have a minimum square output.
- Then I check if some image has width higher than that, if so I take that value for the final node width, so it can fit.
- The final height is some arbitrary huge value. While calling Insert() I keep track of where the bottom corners of the images end up and take the maximum value when creating the actual output image, which, after all this mess, should be more or less a square.

C++

Here is my implementation in C++ to spare you 2 minutes of converting the pseudocode. Unfortunately Blogger doesn't have code tags or something to format it properly.

#include <vector>

class AR_Node
{
public:
    std::vector<AR_Node> child;    //child nodes
    int x, y, w, h;                //position and node size
    bool image;                    //image stored in the node

    AR_Node* Insert(int img_w, int img_h);

    AR_Node()
    {
        this->x = y = w = h = 0;
        this->image = false;
    }
};

AR_Node* AR_Node::Insert(int img_w, int img_h)
{
    //if we are not a leaf then
    if(!this->child.empty())
    {
        //try inserting into first child
        AR_Node *newNode = this->child[0].Insert(img_w,img_h);
        if(newNode!=NULL) return newNode;

        //no room in first child, insert into second
        return this->child[1].Insert(img_w,img_h);
    }
    else
    {
        //if there is already a image here, return
        if(this->image) return NULL;

        //if image doesn't fit, return
        if(this->w<img_w || this->h<img_h) return NULL;

        //if we're just right, accept
        if(this->w==img_w && this->h==img_h)
        {
            this->image = true;
            return this;
        }

        //otherwise split this node and create some children
        this->child.push_back(AR_Node());
        this->child.push_back(AR_Node());

        //decide which way to split
        int dw = this->w - img_w;
        int dh = this->h - img_h;

        if(dw>dh)
        {
            //vertical split           
            //left

            this->child[0].x = this->x;
            this->child[0].y = this->y;
            this->child[0].w = img_w;
            this->child[0].h = this->h;
            //right
            this->child[1].x = this->x + img_w;
            this->child[1].y = this->y;
            this->child[1].w = this->w - img_w;
            this->child[1].h = this->h;
        }
        else
        {
            //horizontal split
            //up

            this->child[0].x = this->x;
            this->child[0].y = this->y;
            this->child[0].w = this->w;
            this->child[0].h = img_h;
            //down
            this->child[1].x = this->x;
            this->child[1].y = this->y + img_h;
            this->child[1].w = this->w;
            this->child[1].h = this->h - img_h;
        }

        //insert into first child we created
        return this->child[0].Insert(img_w,img_h);
    }
}

Tuesday, April 5, 2016

Random #6 - Youtube channel statistics

This January my YouTube channel exploded ! Well, at least one video did. My "The Bloodcrafter - Minecraft 2D shooter" video is getting in average almost 1000 views DAILY, and is rapidly approaching 400 000 views, with the channel surpassing 500 000 total views. If I were monetizing this I might have made enough money to buy the actual game :/


Other videos are not that successful, and the total view count of all the videos I uploaded in the last two years is only around 15 000, and that number would be much, much lower if Bloodcrafter wasn't attracting so much attention to the channel.

Since the most popular videos are based on Minecraft, Serious Sam and Quake, and with so many views in the period of over 5 years, the channel statistics are worth looking at to see some trends. I think it would make sense to say that the viewer demographics represent the actual player base.

The most significant change in the last 5 years is how Minecraft has become more popular among female players. In the first year(2011-2012) only 5 percent of players were female, and the data from last year (2015-2016) shows it is currently at almost 40%.


The percentage of Serious Sam female viewers/players jumped from 5% to around 16%. Quake 2 continues to be a "man's" game, probably because 90s girls weren't interested in it, and new generations don't even know about it, since the series is dead since 2005. But, looking at Serious Sam, which is a similar game, the new entries don't help much. Serious Sam continues to be most popular in Eastern Europe.


Other videos share the same faith like Quake, it's such a sausage festival that YouTube doesn't even want to display the statistics and it shows an error. Since most of them are about video game development, and are shared on some game development and gaming forums, my conclusion would be that we wont see a rapid increase in the number of female game developers any time soon... unless it is forced.

Another sad statistic is Linux. I only have the data starting from January 2013, and they show only 1.3% of PC viewers are on Linux, and 1.8% use Mac. The stats from this blog are a bit more positive, where 5% of readers are on Linux, and 5% on Mac (but these are overall numbers, not just for PC).

Monday, December 21, 2015

Particle emitters #3

I think it is the third time already that I am rewriting my particle system. There is always some effect I want that is missing; lightning, velocity based deformation, pulsating size or color, physics...

Look at how many variables there are already ! And just when I thought I was done, I realized everything is flawed AGAIN. It's the randomization of values ! Those also need more control, so they can change like a sin wave, or linearly, or randomly, or in discrete values...


Tuesday, December 8, 2015

Dynamic 2D light and shadows

This video shows my dynamic 2D light system based on Box2D (nothing to do with Box2DLights library) and Clipper:




This is how it all works:
- light is a textured quad/polygon
- Box2D is used to get all the sprites inside the light polygon by either using light as a sensor, or using b2World::QueryAABB()
- for convex shapes visible edges are calculated using a dotproduct of light and edge normals
- for concave shapes(which can only be chains in Box2D) all edges are considered visible
- shadow polygons are created by extending visible edges
- using polygon clipping library called Clipper a "difference" operation is performed between the light polygon and shadows to get the final light polygon
- UV mapping is performed on the resulting vertices
- the resulting polygon is rendered as a GL_TRIANGLE_FAN

No raycasting is performed, which you usually find in tutorials.

The "depth" mask uses the green channel to mark which pixels should be illuminated, and for the intensity (ex. transparent smoke should be illuminated less than a solid surface). Blue channel is used for depth, and works basically the same. It's not perfect, but I can get some nice effects with it.

Soft shadows are generated by rendering the lights on a FBO smaller than the screen size and rendering it with a blur shader.


Wednesday, November 25, 2015

Depth mask

I realized that, since I am rendering a mask to use it for light rendering I could as well render it in different color values and use it as a depth mask too. Layers that are further away are rendered in darker tones of the color for the mask, and when I use multitexturing to mix the mask and light FBO I multiply the final light values with the values of the mask and get light with different intensity:


Saturday, November 21, 2015

Light and magic

Using FBOs, multitexturing, different blending modes and few simple shaders I managed to make very simple lighting system. I think the end results look pretty good:

No light

Example 1 - blue tint

Example 2 - orange tint

What were the requirements:
a) sprites in a layer should be illuminated with light of different colors
- front layers should block the light from the back 
b) light should "bleed" over the edges of front layers (eg. solid walls)
- not affect back layers that are in distance (eg. sky)
c) different layers should be able to be illuminated seperatelly
d) layers should also be able to get darker

a) To illuminate sprites in a certain layer using some color I render them on a black FBO using a shader that colors the pixels of the sprite's texture in a set color. Then I can render that FBO over the final scene using (GL_DST_COLOR,GL_ONE) blend mode and color the sprites. But, not so fast.

After the sprites that will be illuminated are rendered on the light FBO in color,  I also render the front layers that should block that light in black to cover up those pixels. In the examples above the outside light should be blocked by the ground and the building on the left, the light from the building should be blocked by the building walls and ground.

b) Notice how the edges of the darker front sprites are also colored. To get the "bleeding" effect I blur the light FBO, so the light expands a bit:


To know which layers the blurred light should affect and which not, I need a FBO which serves as a mask. On that FBO I render all the sprites on which the final blurred light can fall, and using multitexturing and a shader I only render the pixels from the light FBO where the pixel is "on" on the mask FBO. Since the "sky" is not rendered on the mask, it wont be affected by the light, but, like seen above, it affects the front layers, which were rendered on the mask.

Mask FBO - green means light should be rendered there

 The sky didn't turn blue, but the wall on the bottom-left did

c) As you can see on the first images the outside area and the area inside the building on the left are illuminated differently. I can use the technique above on as many layers I want, then take the light FBO of the certain layer and render it all on the final light FBO using (GL_SRC_ALPHA,GL_ONE) blend mode to get alpha blending, then I can finally render that light FBO over the final scene using (GL_DST_COLOR,GL_ONE) blend mode.

Final light FBO is a mix of 2 FBOs

d) To darken certain layers(ground and building walls on the images above) I can take the above light FBO and render it over the scene using a mask where I previously rendered all the layers/sprites that should be darken up. Then I render the "dark" pixels where the pixel of the mask FBO is "on". Since the light FBO was already blurred and stuff, I get a nice fade to black at the edges, by using the RGB color values of the light FBO pixels to determine alpha value for the final dark pixel. Here the (GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) blend mode is used.
 

Tuesday, October 13, 2015

Linux ?

Using Eclipse I managed to compile and run the game on Ubuntu/Linux inside VirtualBox. I have little experience using Eclipse, virtual machines and not to mention Linux, but it works more or less.
 

 
There are some problems:

- I can't load shaders and make FBOs, but that is probably because in VirtualBox I can only get OpenGL 2.1.
- FMOD doesn't work in the game and even the examples provided with the API don't work. It plays a sound for only one frame and turns it off. It could be a problem with the library, the way I installed the library, or the audio drivers in VirtualBox.
- Game crashes randomly.