You are here

Trent's blog


Stippled lines with GLSL

The new game we're making calls for drawing some stippled lines. This is for the map where connections will be drawn with stippled lines, like in Monster RPG 2. In Monster RPG 2, I didn't use shaders and didn't want to use OpenGL directly so opted to create images of all of the lines. This time I didn't want the extra work, and we're using shaders from the start.

Below is a shader that can be used to draw stippled lines. It takes a few parameters:

sx - line start x
sy - line start y
dx - line end x
dy - line end y
run_sz1 - size of the colored part of the stippled line
run_sz2 - size of the transparent part of the stippled line
viewport_h - height of the OpenGL viewport you're drawing to -- because glFragCoord.y is inverted if you use the top left as origin

The most critical part of this shader is taken from Paul Bourke's excellent guide, located here. The function, called closest_point in the shader, gets the closest point on the line (sx,sy -> dx,dy) to a given point -- this is important because we want to draw thick lines, and without this the stipples will look slanted the wrong way. Here is that function:

void closest_point(in float x1, in float y1, in float x2, in float y2, in float x3, in float y3, out float x, out float y) {
    float dx = x2 - x1;
    float dy = y2 - y1;
    float u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / (dx*dx + dy*dy);
    x = x1 + u * (x2 - x1);
    y = y1 + u * (y2 - y1);
}

x1,y1 is the start point of the line, x2,y2 is the end point, x3,y3 is the point and x,y are the output.

Now that we have the closest point on the line, we need to calculate which pixels should be drawn and which not drawn. Doing so is fairly simple. We get the distance of the point (after calculating the point on the main line) to the start point. A floating point modulus gives us the distance travelled in a run of run_sz1+run_sz2 length. Comparing that to run_sz1 tells us if the point should be drawn or not. Here is the main function:

void main()
{
    float x, y;
    closest_point(sx, sy, dx, dy, gl_FragCoord.x, viewport_h-gl_FragCoord.y, x, y);
    float total_run = run1_sz + run2_sz;
    float dist_x = sx - x;
    float dist_y = sy - y;
    float dist = sqrt(dist_x*dist_x + dist_y*dist_y);
    float single_segment_pos = mod(dist, total_run);
    if (single_segment_pos > run1_sz) {
        discard;
    }
    gl_FragColor = varying_color;
}

The only part that remains to be explained is the viewport_h-gl_FragCoord.y part. Since I'm using Allegro and Allegro positions the origin at the top left of the drawing target, to get the actual y position we need to invert it, since OpenGL's default origin (the one gl_FragCoord uses) has the origin at bottom left.

Here's a random screenshot of the function using 10 pixel run1_sz and 30 pixel run2_sz:

And here's a link to the full source code in a format easily usable in an Allegro 5 program:

Source code.

iCade Support for Allegro 5 iOS Port

There's very little to this and it can be done outside of the Allegro core, so there's no point in putting it there.

The way the iCade controllers all work is by sending keys (as in keyboard keys) via Bluetooth, but the situation on iOS isn't ideal. You can't read key up/down events on iOS at the moment (IOKit is a private framework) so the iCade sends letters only, and on top of that, different letter for key down than key up. It's documented in the iCade documentation, so check that out.

So what you need to do in a game is create a hidden UITextView to receive the text input and have a delegate attached to that that pumps events into an Allegro event queue. We user Allegro's user events for this purpose. We take care that the text view doesn't fill with many characters by removing them as we generate events for them.

I haven't received my "8-bitty" yet but I've tested this with a regular bluetooth keyboard. So first things first, we need a few event ids.

enum MyUserEvents {
    
USER_KEY_DOWN = ALLEGRO_GET_EVENT_TYPE('D','E','R','P'), // replace letters with something appropriate to your game
    USER_KEY_UP,
    
USER_KEY_CHAR
};

Using ALLEGRO_GET_EVENT_TYPE is important, kind of, because it should ensure your events are within an appropriate range and do not collide with Allegro core events.

When you read events with Allegro's al_get_next_event (and related functions), event.type will equal one of these constants when you get an iCade event. To make inter-operation with regular key events easier, I set event.keyboard.keycode to the generated keycode rather than using event.user.data*.

There is one thing you must remember when you use this! Every time you pull an event out of an event queue (even with al_peek_next_event!) you must call al_unref_user_event on it (only if it's a user event, which you can check with ALLEGRO_EVENT_TYPE_IS_USER()), otherwise you'll have a memory leak. So let's look at the rest of the code, which is quite short.

static UITextView *text_view;
ALLEGRO_EVENT_SOURCE user_event_source;

static void destroy_event(ALLEGRO_USER_EVENT *u)
{
        
/* Does nothing */
}

const char *downs = "WDXAYUIOHJKL";
const char *ups = "ECZQTFMGRNPV";

static int event_type(char c, int *index)
{
    
int i;

    
for (i = 0; downs[i]; i++) {
        
if (c == downs[i]) {
            
*index = i;
            
return USER_KEY_DOWN;
        
}
    
}

    
for (i = 0; ups[i]; i++) {
        
if (c == ups[i]) {
            
*index = i;
            
return USER_KEY_UP;
        
}
    
}

    
return -1;
}

static bool gen_event(ALLEGRO_EVENT *e, char c)
{
    
int index;
    
int type = event_type(c, &index);
    
if (type < 0) {
        
return false;
    
}

    
c = (type == USER_KEY_DOWN) ? c : downs[index];
    
c = (c-'A') + ALLEGRO_KEY_A;

    
e->user.type = type;
    
e->keyboard.keycode = c;

    
return true;
}

@interface KBDelegate : NSObject<UITextViewDelegate>
- (void)start;
- (void)textViewDidChange:(UITextView *)textView;
@end
@implementation KBDelegate
- (void)start
{
    
UIWindow *window = al_iphone_get_window(display);

    
CGRect r = CGRectMake(0, 0, 0, 0);
    
text_view = [[UITextView alloc] initWithFrame:r];
    
text_view.delegate = self;
    
text_view.hidden = YES;
    
    
CGRect r2 = CGRectMake(0, 0, 0, 0);
    
UIView *blank = [[UIView alloc] initWithFrame:r2];
    
blank.hidden = YES;
    
    
text_view.inputView = blank;

    
[window addSubview:text_view];
    
[text_view becomeFirstResponder];
}
- (void)textViewDidChange:(UITextView *)textView
{
    
while ([textView.text length] > 0) {
        
NSString *first = [textView.text substringToIndex:1];
        
NSString *remain = [textView.text substringFromIndex:1];
        
textView.text = remain;
        
const char *txt = [first UTF8String];
        
ALLEGRO_EVENT *e = new ALLEGRO_EVENT;
        
ALLEGRO_EVENT *e2 = NULL;
        
if (gen_event(e, toupper(txt[0]))) {
            
if (e->type == USER_KEY_DOWN) {
                
e2 = new ALLEGRO_EVENT;
                
e2->user.type = USER_KEY_CHAR;
                
e2->keyboard.keycode = e->keyboard.keycode;
            
}
            
al_emit_user_event(&user_event_source, e, destroy_event);
            
if (e2) {
                
al_emit_user_event(&user_event_source, e2, destroy_event);
            
}
        
}
        
else {
            
delete e;
        
}
    
}
}
@end

static KBDelegate *text_delegate;

void initiOSKeyboard()
{
    
text_delegate = [[KBDelegate alloc] init];
    
[text_delegate performSelectorOnMainThread: @selector(start) withObject:nil waitUntilDone:YES];
}

If you read this carefully you'll see that the events you get are always the ones listed as DOWN events in the iCade documentation, to make things easier. For example, an X means direction down is pressed and a Z means direction down is released in the iCade documentation. But in this implementation, you'll get a USER_KEY_DOWN and USER_KEY_UP, both with ALLEGRO_KEY_X as the keycode. This code will also generate a USER_KEY_CHAR event after each USER_KEY_DOWN event. In real life I'll probably wrap this in some code that makes USER_KEY_CHARs repeat like a real keyboard while the button is pressed, but that's left as an exercise to you.

To use this, first register the event source with the event queue you want the events to go to (that being user_event_source). Next, call initiOSKeyboard(). And that's it. You should start receiving those USER_KEY_* events in your event queue. Happy gaming!

P.S.: In case you didn't notice, this is Objective-C code, so it needs to be compiled as such. The rest of your program can still be C or C++.

Raw Input Method for Android for External Keyboards

Disclaimer: There may be an easier way to do this. I'm not an expert when it comes to Android. This is the first and only thing I managed to find that did what I want.

So what does it do? With this input method activated, key-presses and releases get sent to your app almost untouched and "raw". This is good for games. I found the default input methods would automatically send a key up event with MOST (but not ALL D:) key down events which is not good when it comes to games. The default is meant for text input, this is meant for games.

I said almost. The only other thing it does is insert a 150 millisecond (change one constant to change the delay) key repeat delay.

I still haven't looked into how, or if it's even possible, to bundle and automatically activate this from a game. Here's hoping!

This can also serve as a complete example of how to make a simple input method from scratch. You can build on it. Use it how you like! The structure is there which took me a little time to figure out so you don't have to. Make it better! Make the key repeat delay configurable and add an initial delay that's longer.

To use this in your application you can use the onKeyDown and onKeyUp methods of SurfaceView (or GLSurfaceView.)

The source code.

Updates to Monster RPG 2 1.13 So Far

I've been busy going through the bug/feature request list and fixing/adding things. Monster RPG 2 1.13 is going to have a lot of small and medium sized tweaks/fixes. I've got 15 left (some of the larger ones are still left to do) at the time of writing this. For those interested here's a list of what I've done for this release so far (the Raspberry Pi port already has the first few of these since it was done after and separate from 1.12 on other platforms):

  • Textured all of the 3D models
  • New Russian translation
  • Indiegogo sponsor credits at the end of the game
  • More believable/passable "Breath of Fire" attack from the dragon
  • Missing data no longer causes crashes (it will ask you if you want to proceed, which will usually crash if you say yes)
  • Autosaves on PC (they've been on the iOS and Android versions all along)
  • Custom mouse cursor
  • You can inspect the library shelves to see book titles
  • All party members close their eyes when sleeping
  • A notice is shown when you trade for an item
  • F8 (default) auto-sorts your inventory when in the pause->items screen
  • In the pause menu (and other places where player profile "mug shots" are shown), a purple border is shown around poisoned members
  • Red and green down/up arrows are shown when inspecting items, letting you know if the item is better or worse than what everyone already has
  • Game Center support was added on Mac OS X
  • All those thousands of tiles are in a zip file now (I'm embarrased about this, I was too lazy before)
  • The biggest window that's an integer scaling factor of the games native res will be chosen by default (if you have a config file already, delete it to get this behaviour)
  • Odd looking tree overhangs fixed in jungle
  • Some odd behaviour when using mouse or touch screen was fixed (clicking an object would not take you there)
  • Fixed two tiles that were solid and not solid when they should have been opposite in the forest
  • Screensaver will no longer activate when the game is running on Linux
  • Alien items are now all stronger than Light items
  • Item stacking behaviour is better in inventory
  • Bartender's juice stacks correctly now
  • Juice now has a distinct sound when used (used to be same as Cure)

And a few other minor ones.

Simple Allegro 5 SVG renderer

One of the Code Monkey requests from the Monster RPG 2 Indiegogo campaign was to allow using SVG with Monster RPG 2. I spent weeks on the problem, but it didn't look like it would come to an end any time soon. So I searched the web and found libsvgtiny, part of the Netsurf web browser project.

This is a perfectly competent library for most modest needs. I implemented an Allegro 5 renderer using the test program. Unfortunately, the code I wrote cannot handle self-intersecting paths. As far as I know that's the main limitation. It renders many SVGs perfectly.

This code is now in the Monster RPG 2 git repo. I'll also include the single C++ file as it stands right now at the bottom of this post. Hopefully at some point I will be able to solve the intersecting path problem. For now, it's good for many needs if you keep that in mind.

a5_svg.cpp

Quick and dirty TGUI 2 tutorial

NOTICE: You can download this tutorial from git along with the library itself. The version in git is the one which will likely receive the most TLC and updates. See below for the command to get TGUI from git.

TGUI has been around since I returned to making games in around 2002. It has changed a lot at times and I still consider it an evolving API. I've never written any reference or tutorial on how to use it -- until now (ok, I still haven't written it, but I'm getting to that.) This tutorial assumes the reader has knowledge in C++ and has an Allegro 5.1 development environment already set up.

The first step -- get TGUI2! You can do so with git: git clone git://nooskewl.com/tgui2.git

I presume you know how to build software from source, but the skinny is:

cd tgui2
mkdir build
cd build
cmake .. -DUSER_INCLUDE_PATH=<any extra include directories you need to specify>
make
cp libtgui2.a <your library path>
cp ../tgui2*.hpp <your include path>

Substitute for things in <>'s. The -D... part it wholey optional -- you should know if you need it.

Ok, so let's break down the fundamental concepts before making a hello world program. TGUI is split into some setup functions and a few core functions you'll call within your game loop. The setup functions create and add widgets to your view. A simple TGUI program with just two buttons you can click on would start out like this (rough pseudo-code in part so we focus on the TGUI stuff:)

#includes...
#include <tgui2.hpp>
#include <tgui2_widgets.hpp>
// if you're only using custom widgets, don't include this

int main(int argc, char **argv)
{
    
// initialize Allegro, its addons and create a display ...

    
ALLEGRO_COLOR bgColor = al_map_rgb(0, 0, 0);

    
tgui::init(display);

    
ALLEGRO_FONT *font = al_load_font("DejaVuSans.ttf", 18, 0);
    
tgui::setFont(font);

    
TGUI_Button *button1 = new TGUI_Button("Change Color", 10, 10, 150, 30); // text, x, y, width, height
    TGUI_Button *button2 = new TGUI_Button("Quit", 170, 10, 150, 30);

    
tgui::setNewWidgetParent(0);
    
tgui::addWidget(button1);
    
tgui::addWidget(button2);
    
tgui::setFocus(button1);

    
// game loop
}

A quick breakdown of each call is: tgui::init is always called before any other TGUI functions -- pass it the ALLEGRO_DISPLAY you're going to work with; tgui::setFont allows you to set the font TGUI will use for things with text, such as the buttons (this is not currently optional if you're using widgets that display text); The next two "new TGUI_Button" lines create two different buttons with different text and positions but the same size; tgui::setNewWidgetParent is not necessary here since the default is 0 (meaning the parent of added widgets is the display), but you will get into situations such as adding child widgets to a window where it's needed; The calls to tgui::addWidget will add the widgets to the scene so they are processed and drawn as we'll see soon; and finally, though also not necessary in this simple mouse driven example, tgui::setFocus gives the input focus to a widget.

Those are the main functions you'll deal with during setup, besides creating different types of widgets. There are other functions you can use of course, but they won't be covered here.

The next part of a TGUI program is pretty simple, and as I said it's the game loop integration.

First look at a typical Allegro 5 game loop:

    bool quit = false;

    
while (!quit) {
        
bool draw = false;

        
do {
            
ALLEGRO_EVENT event;
            
al_wait_for_event(queue, &event);
            
if (event.type == ALLEGRO_EVENT_TIMER && event.timer.source == drawTimer) {
                
draw = true;
            
}
        
} while (!al_event_queue_is_empty(queue));

        
if (draw) {
            
al_clear_to_color(bgColor);
            
al_flip_display();
        
}
    
}

TGUI integrates very easily with loops similar to this:

    bool quit = false;

    
while (!quit) {
        
bool draw = false;

        
do {
            
ALLEGRO_EVENT event;
            
al_wait_for_event(queue, &event);
            
tgui::handleEvent(&event); // *1*
            if (event.type == ALLEGRO_EVENT_TIMER) {
                
if (event.timer.source == drawTimer) {
                    
draw = true;
                
}
                
else if (event.timer.source == logicTimer) {
                    
tgui::update(); // *2*
                }
            
}
        
} while (!al_event_queue_is_empty(queue));

        
if (draw) {
            
al_clear_to_color(bgColor);
            
tgui::draw(); // *3*
            al_flip_display();
        
}
    
}

You'll see three main changes here, marked with *#* comments. tgui::handleEvent does all of the magic input handling, passing input events along to widgets to react to. You define these callbacks yourself when you create custom widgets or inherit from existing ones. tgui::update is special and we'll get to it in more detail in a minute, but you will place your game logic that reacts to the GUI elements after receiving the return value from this call. tgui::draw is simple, it just draws the GUI elements you added with tgui::addWidget.

So now onto responding to GUI events at the game level. We want button1 to change the clear color of the screen to something random, and button2 to quit the app. We modify the tgui::update call and add a few lines like so:

                    tgui::TGUIWidget *ret = tgui::update();
                    
if (ret == button1) {
                        
bgColor = al_map_rgb(rand()%255, rand()%255, rand()%255);
                    
}
                    
else if (ret == button2) {
                        
quit = true;
                        
break;
                    
}

So tgui::update returns a tgui::TGUIWidget. tgui::TGUIWidget happens to be the base class that all widgets used with TGUI must inherit from. What widget gets returned depends on what events were passed to tgui::handleEvent (for default widgets, you can come up with any logic you like for custom widgets.) If no widget needs action, tgui::update returns NULL. In our case TGUI_Buttons return a pointer to themselves when clicked, so if button1 is clicked, button1 is returned from tgui::update. We check for each button being pressed and react appropriately.

That is all there is to it. Below I'll put a complete code listing for a working demo using the bits from above. Happy coding!

#include <allegro5/allegro.h>

#include <tgui2.hpp>
#include <tgui2_widgets.hpp>
// if you're only using custom widgets, don't include this

int main(int argc, char **argv)
{
    
al_init();
    
al_init_font_addon();
    
al_init_ttf_addon();

    
al_install_mouse();
    
al_install_keyboard();

    
ALLEGRO_DISPLAY *display = al_create_display(640, 480);

    
ALLEGRO_EVENT_QUEUE *queue = al_create_event_queue();
    
al_register_event_source(queue, al_get_mouse_event_source());
    
al_register_event_source(queue, al_get_keyboard_event_source());

    
ALLEGRO_TIMER *logicTimer = al_create_timer(1.0/60.0);
    
ALLEGRO_TIMER *drawTimer = al_create_timer(1.0/30.0);
    
al_register_event_source(queue, al_get_timer_event_source(logicTimer));
    
al_register_event_source(queue, al_get_timer_event_source(drawTimer));

    
ALLEGRO_COLOR bgColor = al_map_rgb(0, 0, 0);

    
tgui::init(display);

    
ALLEGRO_FONT *font = al_load_font("DejaVuSans.ttf", 18, 0);
    
tgui::setFont(font);

    
TGUI_Button *button1 = new TGUI_Button("Change Color", 10, 10, 150, 30); // text, x, y, width, height
    TGUI_Button *button2 = new TGUI_Button("Quit", 170, 10, 150, 30);

    
tgui::setNewWidgetParent(0);
    
tgui::addWidget(button1);
    
tgui::addWidget(button2);
    
tgui::setFocus(button1);

    
al_start_timer(logicTimer);
    
al_start_timer(drawTimer);

    
bool quit = false;

    
while (!quit) {
        
bool draw = false;

        
do {
            
ALLEGRO_EVENT event;
            
al_wait_for_event(queue, &event);
            
tgui::handleEvent(&event);
            
if (event.type == ALLEGRO_EVENT_TIMER) {
                
if (event.timer.source == drawTimer) {
                    
draw = true;
                
}
                
else if (event.timer.source == logicTimer) {
                    
tgui::TGUIWidget *ret = tgui::update();
                    
if (ret == button1) {
                        
bgColor = al_map_rgb(rand()%255, rand()%255, rand()%255);
                    
}
                    
else if (ret == button2) {
                        
quit = true;
                        
break;
                    
}
                
}
            
}
        
} while (!al_event_queue_is_empty(queue));

        
if (draw) {
            
al_clear_to_color(bgColor);
            
tgui::draw();
            
al_flip_display();
        
}
    
}

    
return 0;
}

Skeled skeletal/keyframe animation software first release

After a month of work I have a skeletal animation software for use with Baryon. It was a really benefitial experience. TGUI improved a lot because of it, and now we can add some really interesting enemies to Baryon that won't consume massive amounts of disk space. As usual I like sharing the tools I make if I think they'd be useful for others, so I'll link below to the binaries for Windows, Mac and Linux. I'll also link to a Youtube video that shows off just a small bit of the potential of the software.

The editor will output some XML data. It expects to find images and XML files in data/skeletons/parts and data/skeletons/xml from the directory of the executable. The XML format is mostly self explanatory, but if someone wants the low-down I can post it. Also, there is code to load and display these things but it's in Baryon-core. If anyone is serious about using this in a game, I'll make that code available.

Skeled for multiple platforms (binary)

Fireworks 2013

I've been updating my fireworks demo each year for a couple years now, and I decided to update it again this year. No major changes, just some minor "bugs" fixed, and a small (303KB) standalone exe with icon.

WARNING! This can get LOUD.

Stand-alone fireworks demo (exe, 303KB)
7-zip archive including binary and source code (about the same size)

Pages