Home
Features
Images
Soundtrack
Journal
Progress Press Contribute Contact


Journal


Client Rendering Engine Rewritten

February 7, 2011

Rework rendering logic and code to increase efficiency when player transgresses landscape cells

As a refresher, the game world stored server-side is divided into cells which are 32x32 tiles each. One tile is 128x128 pixels and represents one square foot. The client is only aware of a 3x3 grid of cells at any given time, with the player always located in the center cell. This area represents a 96x96 foot squared area of the game world.

Previously what is now called the “tile grid” was called the “cell grid” and was a full-resolution image buffer of the entire 9x9 cell cell grid (12288x12288 pixels). When the player crossed from the center cell into an adjacent cell, the cell grid buffer had to be shifted an entire cell in any of eight directions. Due to the inefficiency of this operation (5+ second lag) it was required that the rendering engine be redesigned so that such large image blits (transfers) were not required.

Another consideration was the amount of memory consumed by maintaining such large images, which would prove significant should the game client be ported to other platforms in the future (game consoles, portable devices, etc.).

To achieve this I decided to discard the cell grid buffer and instead use what I call the tile grid buffer. It is essentially a smaller version of the cell grid buffer, just large enough to overlap the render window by a few tiles in each direction. The buffer is updated every time the player transgresses a tile, rather than every time the player transgresses a cell. If the player moves north and crosses over a tile, the tile grid buffer if shifted south one tile and a new row of tiles is drawn on the north edge of the tile grid buffer.

Items that were improved as a side effect of the rendering engine rewrite

The client now supports completely arbitrary full-screen and windowed resolutions, automatically adjusting at run time the tile grid width and height to what is appropriate to the specified resolution. Now you can run the game in a small 640x480 window, full screen at 1920x1080 (1080p), or whatever crazy screen resolution your computer setup may require.

Items that worked previously but are now slightly broken

An unfortunate side effect of the engine rewrite is that a small lag (100 – 250 ms) was introduced every time the player transgresses a tile. The lag that occurred when transgressing a cell, however, was virtually non-existent. Work is currently being performed to minimize the lag when transgressing tiles.

The proper display of elevation hill shading and the water layer are no longer working correctly due to the rendering engine redesign.

Further modify the camera algorithm to be more natural

Since the “camera” which determines what part of the tile grid to render to the render window is detached from the player's position, I also further refined the camera movement algorithm so that it speeds up and slows down realistically as it follows the player.

Draw the player's avatar (graphical representation) on the landscape

The player is now graphically represented on the landscape, so you can see yourself move around when using the gamepad. Currently player movement physics have not been implemented, though it's awesome to see your character move around and the camera try to keep up with him.

Improve techniques for creating seamless megatextures for landscape layers

I discovered a GIMP plugin called Resynthesizer and successfully tested it in creating a new seamless texture for one of the vegetation landscape layers. Will probably use it in the future to create new versions of the other landscape layer megatextures.

Miscellaneous

Server module variable for storing players' positions was an integer datatype when it needed to be a floating point datatype, which caused updated player positions to be rounded and the client and server to become out of sync. Problem was fixed and extensively tested by flailing the hell out of the gamepad for extended periods of time and comparing the client and server player position values.

Several other bugs were fixed but I can't remember what they were.

Experiemented with the imlib component of GAMBAS 3 (gb3), creating a frames per second test app to compare rendering efficiency with the normal gb3 image component. Imlib proved to be anywhere from 8.5 to 35+ times faster, which is incredible. Attempted to implement the library in code but experienced a bug which I reported to Benoit Minisini (lead dev of GAMBAS) for correction. He hasn't yet responded to it, but hopefully will as using that component should greatly increase the frame rate of the rendering engine.

Contributed about $68 (50 EURO) to Benoit Minisini via PayPal to encourage him to fix the damn bug in the imlib component.


C/C++ Method Backport Granted

November 7, 2010

An Ultima Aiera contributor has heeded the call to arms and backported the DrawAlpha method from the gb.image component of GAMBAS 3 to GAMBAS 2. After applying the patch and recompiling GAMBAS 2 the test app worked as I'd hoped. The addition to GAMBAS hasn't yet been submitted upstream since it is a backport, but will be upon request. It is available here, however:

Index: gb.image/src/CImage.cpp
===================================================================
--- gb.image/src/CImage.cpp    (revision 3275)
+++ gb.image/src/CImage.cpp    (working copy)
@@ -324,7 +324,59 @@
 
 END_METHOD
 
+BEGIN_METHOD(CIMAGE_drawAlpha, GB_OBJECT img; GB_INTEGER x; GB_INTEGER y; GB_INTEGER sx; GB_INTEGER sy; GB_INTEGER sw; GB_INTEGER sh)
+        int x, y, sx, sy, sw, sh;
+        int i, j;
+        QImage dest (THIS);
+        QImage src (VARG(img));
 
+        x = VARGOPT (x, 0);
+        y = VARGOPT (y, 0);
+
+        sx = VARGOPT (sx, 0);
+        sy = VARGOPT (sy, 0);
+        sw = VARGOPT (sw, src.width ());
+        sh = VARGOPT (sh, src.height ());
+
+        if (sw + sx > src.width ())
+            sw = src.width () - sx;
+
+        if (sh + sy > src.height ())
+            sh = src.height () - sy;
+
+        int dest_width = dest.width ();
+        int dest_height = dest.height ();
+        int destI, destJ;
+        for (i = 0; i < sh; i++)
+        {
+            for (j = 0; j < sw; j++)
+            {
+                //Calculate destination
+                destJ = x + j;
+                destJ %= dest_width;
+                destI = y + i;
+                destI %= dest_height;
+
+                //Get color
+                QRgb baseColor = dest.pixel (destJ, destI);
+
+                //Now separate them
+                int r = qRed (baseColor);
+                int g = qGreen (baseColor);
+                int b = qBlue (baseColor);
+
+                //Now get alpha of src
+                baseColor = src.pixel (j + sx, i + sy);
+                int a = qAlpha (baseColor);
+
+                QRgb color = qRgba (r, g, b, a);
+
+                dest.setPixel (destJ, destI, color);
+            }
+        }
+END_METHOD
+
+
 GB_DESC CImageDesc[] =
 {
   GB_DECLARE("Image", 0),
@@ -377,6 +429,7 @@
     GB_METHOD("Wave", "Image", CIMAGE_wave, "[(Amplitude)f(WaveLength)f(Background)i]"),
     GB_METHOD("Noise", "Image", CIMAGE_noise, "(Noise)i"),
     GB_METHOD("Implode", "Image", CIMAGE_implode, "[(Factor)f(Background)i]"),
+    GB_METHOD("DrawAlpha", NULL, CIMAGE_drawAlpha, "(Image)Image;(X)i(Y)i[(SrcX)i(SrcY)i(SrcWidth)i(SrcHeight)i]"),
 
   GB_END_DECLARE
 };

The command to execute the patch is something like:

patch ./gb.image/src/CImage.cpp ./patch_drawalpha

Below is the result of the test app properly exposing the sand layer underneath the vegetation layer. It compiled and ran without error on two different systems. It shows a simple test with a 4x4 grid of tiles, each tile being 128x128 pixels. The surrounding ten tiles used a fully-transparent interconnective alpha tile. The four center tiles used four partially-transparent interconnective alpha tiles. While this is a simple demonstration, ultimately the seamless dynamic layering will produce stunning real-time results.



This is probably the most important bit of news that will ever exist regarding the progress and power of Sanctimonia's client rendering engine. This enables, as I poorly attempted to describe earlier:

Everything in the client landscape data arrays and graphics rendering breaks down nicely into predictable tiles of 128x128 pixels. While a polished, "release ready" method would probably include clipping and such, in our case it isn't needed as no DrawAlpha blits will fall outside the range of the target images. I'm guessing that adding clipping or anything else would probably slow the routine down, so fast and dirty is probably preferable in this case. Especially because it's software rendering. Frame rate is one of my primary concerns at this point and it'll require a lot of tweaking on my part to keep it around 30 fps.

As far as how Sanctimonia will render a scene, the client knows of a 3x3 "cell" grid of landscape data (nine cells). Each cell is 32x32 tiles. Each tile is 128x128 pixels. When the player starts the game, the server sends the player the landscape data for the five layers for the 3x3 cell grid. Each tile of each of the layers is one byte, the values (0-255) representing the depth or density of the tile for the layer. The client stores this landscape data in five arrays.

Initially the entire screen must be rendered, so the client renders the visible subset of the grid (variable based on zoom level). As the player moves around the map, only the new tiles will be rendered. The existing on-screen tiles will be shifted to make room for the new. A "camera" will keep track of which part of the small buffer to render to the DrawingArea so it appears as though smooth scrolling is occurring.

Each landscape layer has eight "sub" layers with a different variation of the texture. The vegetation layer for example will look like:

Tile Depth/Density  Texture File Name

000-031             vegetation_0.png
032-063             vegetation_1.png
064-095             vegetation_2.png
096-127             vegetation_3.png
128-159             vegetation_4.png
160-191             vegetation_5.png
192-223             vegetation_6.png
224-255             vegetation_7.png

These sub layers will be individually modified by DrawAlpha using an algorithm that examines the adjacent tiles and the sub layer range that they fall in to. The algorithm will make each sub layer seamless, choosing the appropriate interconnecting tile based on its neighboring tiles. After each sub layer has been modified, the next layer will undergo the same treatment. When all the sub layers for each of the five main layers have been made seamless with DrawAlpha, they'll all be combined, stacked like a pancake, and sent to the main rendering pipeline. Things like water, fog, rain, etc., will be added per frame on top of the final landscape texture.

The next order of business is integrating the method into the client rendering engine and allowing proper movement and scrolling.

C/C++ Method Backport Requested

November 3, 2010

Sanctimonia needs a single method from the gb.image library backported from GAMBAS 3 to GAMBAS 2. This method is critical to the further development of the client rendering engine. I'd use GAMBAS 3 but it is still in alpha and is entirely too unstable at this time to develop Sanctimonia with it. Ultimately Sanctimonia will be ported to GAMBAS 3, but the game cannot wait. The method is called
Image.DrawAlpha (Image_DrawAlpha) and is in the file 3.0.x/trunk/main/lib/image/CImage.c. There are some other files associated with the method, detailed below.

The source files, dependency and GAMBAS installation scripts for GAMBAS 2 and GAMBAS 3 are located here. While these scripts may work on other distributions, they are designed for Ubuntu 10.04 (Lucid Lynx). If you are a Windows user you may easily install Lucid in VirtualBox.

I am not a C or C++ programmer, but I believe these are the GAMBAS 3 source files and code
exerpts that need to be backported to enable this method in GAMBAS 2:

3.0.x/trunk/main/lib/image/CImage.c

BEGIN_METHOD(Image_DrawAlpha, GB_OBJECT image; GB_INTEGER x; GB_INTEGER y; GB_INTEGER srcx; GB_INTEGER srcy; GB_INTEGER srcw; GB_INTEGER srch)

    CIMAGE *image = VARG(image);
   
    if (GB.CheckObject(image))
        return;
   
  IMAGE_draw_alpha(THIS_IMAGE, VARGOPT(x, 0), VARGOPT(y, 0), &image->image, VARGOPT(srcx, 0), VARGOPT(srcy, 0), VARGOPT(srcw, -1), VARGOPT(srch, -1));
    GB.ReturnObject(THIS);

END_METHOD

3.0.x/trunk/main/lib/image/image.h

void IMAGE_draw_alpha(GB_IMG *dst, int dx, int dy, GB_IMG *src, int sx, int sy, int sw, int sh);

The comparable function in GAMBAS that
Image.DrawAlpha is based upon is Image.Draw, which copies a rectangular portion of one image to another and includes all four channels (red, green, blue and alpha). The Image.DrawAlpha method however only copies the alpha channel of an image to another.

For specific questions about this method and how it is integrated into the GAMBAS 3 code base, please refer questions to me or, more directly, to the GAMBAS dev mailing list here. Benoît Minisini is the lead developer of GAMBAS.

If you'd like to know why this method is of such importance, please see the following article about its imminent implementation:


http://www.ultimaaiera.com/blog/sanctimonia-updates-client-progress-coder-needed/

The programmer who is successful will be rewarded greatly, and I sincerely appreciate any time or effort expended in making this possible.

Tentative Title Sequence Penned

September 10th, 2010

An initial introductory sequence has been created in code and finely tuned. The theme borrows heavily from the dramatic intro of Silpheed for DOS and MT-32 by Game Arts. The famous quote, "How many ages hence..." from Shakespeare's Julius Caesar is ironically as appropriate for Sanctimonia's themes as it was for Silpheed's, despite being worlds apart in most every respect.

Here it is saved frame-by-frame in code (h264, 1280x720 or 720p):

Decent Quality

and here it is saved by YouTube's transcoders:

YouTube Quality


Gamepad Input Cornered, Allowing Taps and Holds

September 7th, 2010

Per-button/axis gamepad input is accepted and interpreted with respect to the previous corresponding input, meaning button "taps" and "holds" are differentiated, enabling two actions per button. This has enabled the current default control scheme, which is as follows:

Button        Action     Result

Start         any        main menu
Select        any        toggle spectator/interactive modes
D-Pad         any        cardinal/traditional player movement
Left Analog   any        player movement/strafing
Right Analog  any        player orientation/rotation
1             ?          ?
2             any        jump/okay
3             ?          ?
4             ?          ?
L1            tap        use object in left hand
L1            hold       use left hand
R1            tap        use object in right hand
R1            hold       use right hand
L2            tap        pick up object with left hand
L2            hold       drop object in left hand
R2            tap        pick up object with right hand
R2            hold       drop object in right hand
L3            tap        unequip object in left hand or re-equip object previously in left hand
L3            hold       inventory menu to select object to equip in left hand
R3            tap        unequip object in right hand or re-equip object previously in right hand
R3            hold       inventory menu to select object to equip in right hand


Using the four shoulder buttons (L1, L2, R1, R2) with taps and holds creates eight commands exclusively for a player's arms and hands. This will allow players to quickly pick up or drop objects with a specific hand, move objects from one place to another, carefully arrange objects and will increase the detail level of combat.

For example, a player may attack another player, knock the weapon out of their hand, pick up their weapon with their free hand, then continue to attack them using both weapons with independent control for the right and left hands. Tapping R1 will attack using the weapon in the player's right hand, while holding R1 will punch them while still gripping the weapon.

Another example would be a player with a flaming oil flask in their hand. Tapping the button for that hand will use/throw the flaming oil but holding the button will punch with the oil still in hand, effectively burning the opponent and the player upon impact. This should make for fast and chaotic combat depending on the weapons and players involved.

Littoral Generates Usable World Data, Client-Server Coding to Resume

September 1st, 2010

Littoral, the procedural world generator for Sanctimonia, is now able to generate usable data files for the server and client modules. The elevation (bedrock layer) and tile (sand, soil, vegetation and snow layers) data files may be created from an elevation template image to create world maps ranging from 1024x1024 to 65536x65536 units. Smaller maps take less than a minute to generate and larger maps take about 24 hours on a 3 GHz 64-bit CPU running Ubuntu 10.04. Sanctimonia will ultimately be using a 65536x65536 vertex map, which at one square foot per vertex amounts to a 12x12 mile world.

While work still needs to be performed on Littoral for truly outstanding results, intense coding is commencing tonight on the client-server application now that viable landscape data files are available. Since the basic client-server networking code is finished, priority one is to create the client input and rendering engine that will make Sanctimonia playable. For the time being tile graphics from Ultima IV will be used to represent the various landscape features, both to save time and for my own amusement. Ultimately high-resolution "mega-textures" will be used for each landscape layer, blended seamlessly using a GAMBAS function created by Benoît Minisini to bit-blit only the alpha channel of an image.

I still consider Littoral to be in an alpha state, although it is completely usable for various game projects that require a vertex- (3D) or tile-based (2D) landscape. In the spirit of open source software I'm releasing it in its early state here. All feedback and possible code improvements are greatly appreciated. To run Littoral you will need to use GAMBAS 2, available in the Ubuntu software repositories or by running the following script in a terminal in Ubuntu 10.04:

sudo apt-get install build-essential autoconf libbz2-dev libfbclient2 libmysqlclient15-dev unixodbc-dev libpq-dev libsqlite0-dev libsqlite3-dev libgtk2.0-dev libldap2-dev libcurl4-gnutls-dev libgtkglext1-dev libpcre3-dev libsdl-sound1.2-dev libsdl-mixer1.2-dev libsdl-image1.2-dev libsage-dev libxml2-dev libxslt1-dev libbonobo2-dev libcos4-dev libomniorb4-dev librsvg2-dev libpoppler-dev libpoppler-glib-dev libasound2-dev libesd0-dev libesd-alsa0 libdirectfb-dev libaa1-dev libxtst-dev libffi-dev kdelibs4-dev firebird2.1-dev libqt4-dev libglew1.5-dev libimlib2-dev libv4l-dev libsdl-ttf2.0-dev
mkdir 2.0
svn checkout https://gambas.svn.sourceforge.net/svnroot/gambas/gambas/branches/2.0
cd 2.0
./reconf-all
./configure
make
sudo make install
gambas2

Littoral, First Release (source tarball)

Littoral, First Release (Ubuntu .deb package)
Littoral, First Release (GAMBAS2 executable)

Here are some screenshots (minus window decoration) to give you an idea of what Littoral currently does.

Elevation template created in The GIMP from the Ultima V cloth and game maps, used by Littoral to generate both elevation data using the Diamond-Square algorithm and the tile data for surface type coverage 


   
Completed rendering of the elevation template from the previous image along with an elevation-based preview of the four tile layers 


Test elevation template 


Rendered preview of the test elevation template 



All the options in the screenshots with the exception of the "Persistent World Objects" tab are usable and contain no significant bugs.

Chris "Absynth" Waugh to Score Sanctimonia Soundtrack

July 4th, 2010

Drum and bass composer and DJ Chris Waugh, known to his fans as Absynth, has already begun work on the Sanctimonia soundtrack.

Combining traditional eastern and western instrumentation with cutting edge electronic sounds Chris is creating a unique blend of the ancient and the modern.

Here are a few examples of early works-in-progress being composed for Sanctimonia:
   
How to Contribute if Strapped for Cash

June 24th, 2010

While pledges are the bread and butter that will allow Sanctimonia to be completed in a timely manner, you can also help greatly by simply spreading the word. Here are some ways you can help without having to dip into your purse:
Soon I will be releasing screen shots and the source code and binary for Littoral, the world generation app that is largely responsible for creating Sanctimonia's initial game world. It needs to be mostly finished before work can continue on the client-server app that will make the game playable.
 
Thanks so much to everyone for their support and encouragement, and keep up the great work.

Thoughts on Allowing Total Freedom in an Online World

June 22nd, 2010

This is an excerpt from a reply to a backer, Toops of www.geeks.co.uk:

Communication is one of the key elements of Sanctimonia, largely because it can be expressed more than just verbally. The idea is to put in place very simple sets of game mechanics, both social and physical, that ultimately allow the same basic freedoms and self-imposed restrictions people have in real life. Once those basic building blocks of player freedom and interaction are discovered they can be used by individuals or groups to facilitate the more complex systems of human behavior and creative endeavors.

Sanctimonia's gameplay is being designed from the bottom up rather than the top down. A big mistake I think a lot of developers make is doing the opposite. They think first of story, genre, graphics or levels and work their way down to the gameplay specifics. That kind of top down thinking ends up constraining the gameplay within the confines of the initial concept and gives us things like "invisible walls", instantiated quests and game rules that don't make sense within the game world but only exist to curb griefing or other undesirable gameplay.

As in real life, the dark side of total freedom may be mitigated in two ways:

First constrain players to the physical realities of being human. If a human must eat, drink, use the bathroom and sleep then they need to work and find reliable shelter or they will die. That leaves less time to do things like wait around for new players and repeatedly kill them or perform random acts of vandalism and theft. Humans also can't reach level 50 and crush stones between their pectorals, which means picking a fight even with a new player may get you killed if he picks up a shovel and is good with a gamepad.

Second allow the same social developments that prevent bad behavior in real life. We all have the freedom to go out and steal, assault or kill other people, yet we're constrained by our conscience and the fear of punishment if caught. The sense of accomplishment players will feel working together to raise livestock, tend farms and build their homes will create a sense of community. That community will then naturally want to protect itself from outsiders with bad intentions and will take advantage of the ability to pass, post and enforce laws. One example would be to build a wall around the village, post guards at the entrances and require all those entering to leave their weapons in a guard tower before entering. Any unarmed griefer who started trouble would quickly be knocked unconscious by the townspeople and placed in custody, exiled or worse. All without invisible walls or restricted PVP. ;)


© 2011 Eight Virtues