DinoMage on Twitter RSS Feed

Author Archive

Pixel Proof is our newest app for desktop computer platforms.  It’s a great new editor that approaches pixel art in a special way. The interface is simple, the tools are fresh, and much of it is customizable. Set up your palette and toolset and the layered file format will save them along with the image. With Twitter integration, you can even tweet your art directly from the app!

pixel_proof_banner

The Pirate Edition is free and fully-featured. Give it a try and let me know what you think! If you like it, buy it!
https://dinomage.itch.io/pixel-proof

With all of the exciting features packed into Pixel Proof and the future plans, Pixel Proof is lined up to be the cleanest, most flexible pixel editor available.  Help us get there by trying it out and providing feedback.  We appreciate all input!

frog

Tags: ,

2
Apr

C string explode

   Posted by: in Dev, Libraries

I was working on porting some code from C++ to C and I couldn’t really find a simple explode() function for C online.  So, of course, I had to make it myself.  I’m just putting it here for general reference.  This implementation uses malloc(), so it’s going to be a little less performant than a static one, though easier to use.  At least it can’t be any worse than my C++ implementation!

No guarantees, you’re granted the code under the MIT license.

typedef struct StringList
{
    char* value;
    struct StringList* next;
} StringList;
void StringListFree(StringList* node)
{
    // Delete the nodes in order
    while(node != NULL)
    {
        StringList* last = node;
        node = node->next;
        free(last->value);
        free(last);
    }
}
// Returns a linked list of malloc'ed NULL-terminated strings.  The delimiter is erased.
static StringList* explode(const char* text, char delimiter)
{
    StringList* head;
    StringList* new_node;
    StringList** node;
    const char* start;
    const char* end;
    unsigned int size;
    if(text == NULL)
        return NULL;
    head = NULL;
    node = &head;
    // Doesn't technically support UTF-8, but it's probably fine, right?
    size = 0;
    start = end = text;
    while(1)
    {
        if(*end == delimiter || *end == '\0')
        {
            *node = (StringList*)malloc(sizeof(StringList));
            new_node = *node;
            new_node->value = (char*)malloc(size + 1);
            memcpy(new_node->value, start, size);
            new_node->value[size] = '\0';
            new_node->next = NULL;
            if(*end == '\0')
                break;
            node = &((*node)->next);
            start = end+1;
            size = 0;
        }
        else
            ++size;
        ++end;
    }
    return head;
}

This is just a quick post to let everyone know that Tangible Math is available now! I’ll have a more complete introduction to it soon (update: see the product page), but you can already get it on iTunes, Google Play, the Amazon Appstore, and the NOOK Store.

Tangible Math is a full set of virtual manipulatives for preschool, kindergarten, and 1st grade math. These are physical objects you’d use to teach math concepts with your child. Instead of setting a kid down in front of an app and walking away, this app facilitates interaction between the teacher and the student. Just check out the screenshots and you’ll see what it’s all about!

iTunes_available_110x40_0824google_play_badgekindle_badge_whitenook_badge

 

Screenshot_2015-02-13-14-41-26Screenshot_2015-02-13-15-39-37
Screenshot_2015-02-13-15-41-40Screenshot_2015-02-22-18-43-36
Screenshot_2015-03-18-14-39-47     Screenshot_2015-03-18-14-40-47

Hey there!  This is a tutorial on some of the basics of using SDL_gpu.  If you are comfortable with C or C++ and SDL, then you’re ready for this.  I’ll post installation instructions for SDL_gpu some other time, though if you use SDL then you probably already have that down.

To start things off, here’s a listing of the functions we’ll be using:

GPU_SetDebugLevel
GPU_Init
GPU_LoadImage
GPU_Clear
GPU_Blit
GPU_Flip
GPU_Quit

These will be everything we need to put together a simple simulation to play with.

If you’ve used SDL’s built-in rendering before, then SDL_gpu shouldn’t feel too different.  They do the same basic things, but SDL_gpu has a lot more functionality and a few different conventions.  The most blatant convention difference you’ll see is that GPU_Blit() and the other blitting functions draw sprites at their center instead of from the upper-left corner.  This doesn’t actually change much in your code, but makes scaling and rotating sprites work without hassle.

Initialization

void GPU_SetDebugLevel(GPU_DebugLevelEnum level);
GPU_Target* GPU_Init(Uint16 w, Uint16 h, GPU_WindowFlagEnum SDL_flags);

There are a couple of ways that SDL_gpu lets you know when something goes wrong.  Some functions can have an obvious return value when there’s an error.  E.g. GPU_Init() will return a NULL render target on error.  SDL_gpu also maintains an error stack that you can check manually (sometimes a silent program is bliss). But we’re just starting off! We need to know right when errors occur. With GPU_SetDebugLevel(), we can tell SDL_gpu to print out the errors just as they happen.  That will help us catch some mistakes that we might make along the way.

GPU_Init() is the easy way to get a window and render target created.  All you have to do is pass in the width and height of the window you want.  You can also pass in some SDL window flags if you want to.  This will set everything up and choose the best renderer that is available.

So here’s a snippet of those two functions. Once you have #include “SDL_gpu.h” and set up your main() function:

// Tell us whenever something bad happens
GPU_SetDebugLevel(GPU_DEBUG_LEVEL_MAX);
// Get a 800x600 window to render to
GPU_Target* screen = GPU_Init(800, 600, GPU_DEFAULT_INIT_FLAGS);

Rendering an image

Now we would like to draw something on the screen. To render to our new window, we need either an image or we can draw shapes.  Here’s what we need for drawing images:

GPU_Image* GPU_LoadImage(const char* filename);
void GPU_Clear(GPU_Target* target);
void GPU_Blit(GPU_Image* image, GPU_Rect* src_rect, GPU_Target* target, float x, float y);
void GPU_Flip(GPU_Target* target);

GPU_LoadImage() of course loads images. It currently supports .png, .jpg, .tga, .gif, and .bmp image files. Before each frame is drawn, we might want to use GPU_Clear() to erase the contents of the screen target. In some cases you don’t need to, especially when you are drawing over the entire screen every frame (e.g. with a background image).  When we pass our new image and the screen target to GPU_Blit(), we get the image rendered right where we want it.  To actually see what we rendered, we need to GPU_Flip() the screen buffer so it is shown in the window.

// Load the image...
// (Make sure that you have some image named test_image.png in the executable's directory)
GPU_Image* image = GPU_LoadImage("test_image.png");
// Clear the screen
GPU_Clear(screen);
// Remember that GPU_Blit() draws the image *centered* at the given position
GPU_Blit(image, NULL, screen, image->w/2, image->h/2);
// Show the result in the window
GPU_Flip(screen);

With that, we can technically see something drawn…

Shutting down

void GPU_Quit(void);

Let’s not forget to shut down SDL_gpu at the end. GPU_Quit() will free up the resources that SDL_gpu was using.

Source

Here’s what your complete program might look like now with some usual SDL stuff thrown in:

#include "SDL_gpu.h"
int main(int argc, char** argv)
{
    // Tell us whenever something bad happens
    GPU_SetDebugLevel(GPU_DEBUG_LEVEL_MAX);
    // Get a 800x600 window to render to
    GPU_Target* screen = GPU_Init(800, 600, GPU_DEFAULT_INIT_FLAGS);
    // Did initialization fail?
    if(screen == NULL)
        return 1;
    // Load the image...
    // (Make sure that you have some image named test_image.png in the executable's directory)
    GPU_Image* image = GPU_LoadImage("test_image.png");
    if(image == NULL)
        return 2;
    SDL_Event event;
    Uint8 done = 0;
    while(!done)
    {
        while(SDL_PollEvent(&event))
        {
            if(event.type == SDL_QUIT)
                done = 1;
        }
        // Clear the screen
        GPU_Clear(screen);
        // Remember that GPU_Blit() draws the image *centered* at the given position
        GPU_Blit(image, NULL, screen, image->w/2, image->h/2);
        // Show the result in the window
        GPU_Flip(screen);
    }
    GPU_Quit();
    return 0;
}

Tags: ,

SDL_gpu has its first binary release!   If you make games or applications in C or C++, you should give it a spin.

In case you aren’t familiar with it yet, SDL_gpu is an alternative rendering system for SDL 1.2 and SDL 2.0.   It includes everything you need to make amazing, high-performance 2D graphics:

  • Fast buffered blits
  • Shader support
  • Sprite rotation and scaling
  • Plenty of 2D primitives (e.g. lines, arcs, circles, rounded rectangles, annulus sectors)
  • Render-to-Texture
  • An enjoyable basic abstraction (targets and images)
  • Virtual resolution and viewports
  • 2D camera control
  • Built-in support for some popular image formats
  • Blend, filter, and wrap modes
  • Arbitrary 2D geometry (batched textured triangles)
  • Can be used with native OpenGL calls

You can get started at the Google Code page.  There’s some introductory info, documentation, and downloads there.

Tags: , ,

Gladiator is a lot of fun and it’s nice to play an updated throwback to the classic MS-DOS days. However, the video game market is not quite so nice!

The OUYA represents a change to the way games are discovered by players. The traditional method of finding games in the local brick & mortar shop or even the recent way that Google Play and the iOS App Store interface with the user are not proper solutions to the modern problem of finding the right game/app for you.

I’d love to say that the OUYA has the answer, but it’s not there yet. There’s promise though. One promising feature is the Sandbox. The Sandbox is where all new games go on OUYA. It prevents the store from being overrun by tons of junk, which is great. The issue is that the games that stay in the Sandbox for a little while end up having their own discoverability problems. That’s where Gladiator is now.

Considering that, we’re making Gladiator available for free now on the OUYA, the best place to play this kind of 4-player co-op game. If all we get out of this is that more people get to play this game, then that’s a job well done! We hope you’ll help us spread the word so that the community of this great gaming classic will continue to grow.

tic_tac_1

Tags: ,

9
Dec

Multiple windows with SDL2

   Posted by: in Dev

Just messing around getting ready to put multiple window support into SDL_gpu…  I couldn’t find a good reference for how to use multiple windows, even in SDL2’s demos.  So here’s what I put together for a functioning demo (warning: no error-checking).  This uses SDL2’s built-in rendering system (SDL_Renderer) so I could explore the limitations that I need to work with.

#include "SDL.h"
typedef struct Sprite
{
    SDL_Texture* texture;
    float x, y;
    float velx, vely;
} Sprite;
typedef struct Group
{
    Uint32 windowID;
    SDL_Renderer* renderer;
    Sprite sprite;
} Group;
#define screen_w 300
#define screen_h 300
#define sprite_w 100
#define sprite_h 100
Group create_group()
{
    Group g;
    SDL_Window* window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, 0);
    g.windowID = SDL_GetWindowID(window);
    SDL_Log("New windowID: %u\n", g.windowID);
    g.renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    SDL_Surface* surface = SDL_LoadBMP("data/test.bmp");
    g.sprite.texture = SDL_CreateTextureFromSurface(g.renderer, surface);
    g.sprite.x = rand()%screen_w;
    g.sprite.y = rand()%screen_h;
    g.sprite.velx = 10 + rand()%screen_w/10;
    g.sprite.vely = 10 + rand()%screen_h/10;
    SDL_FreeSurface(surface);
    return g;
}
int main(int argc, char* argv[])
{
    int max_groups = 30;
    Group groups[max_groups];
    memset(groups, 0, sizeof(Group)*max_groups);
	int num_groups = 0;
	groups[num_groups] = create_group();
	num_groups++;
	int i = 0;
	float dt = 0.010f;
	Uint32 startTime = SDL_GetTicks();
	long frameCount = 0;
    Uint8 done = 0;
	SDL_Event event;
	while(!done)
	{
		while(SDL_PollEvent(&event))
		{
			if(event.type == SDL_QUIT)
				done = 1;
			else if(event.type == SDL_KEYDOWN)
			{
				if(event.key.keysym.sym == SDLK_ESCAPE)
					done = 1;
				else if(event.key.keysym.sym == SDLK_EQUALS || event.key.keysym.sym == SDLK_PLUS)
				{
					if(num_groups < max_groups)
                    {
                        groups[num_groups] = create_group();
                        num_groups++;
                        SDL_Log("num_groups: %d\n", num_groups);
                    }
				}
				else if(event.key.keysym.sym == SDLK_MINUS)
				{
					if(num_groups > 0)
                    {
                        for(i = max_groups-1; i >= 0; i--)
                        {
                            if(groups[i].windowID != 0)
                            {
                                SDL_DestroyTexture(groups[i].sprite.texture);
                                SDL_DestroyRenderer(groups[i].renderer);
                                SDL_DestroyWindow(SDL_GetWindowFromID(groups[i].windowID));
                                groups[i].windowID = 0;
                                num_groups--;
                                SDL_Log("num_groups: %d\n", num_groups);
                                break;
                            }
                        }
                        if(num_groups == 0)
                            done = 1;
                    }
				}
			}
			else if(event.type == SDL_WINDOWEVENT)
            {
                if(event.window.event == SDL_WINDOWEVENT_CLOSE)
                {
                    Uint8 closed = 0;
                    for(i = 0; i < max_groups; i++)
                    {
                        if(groups[i].windowID != 0 && groups[i].windowID == event.window.windowID)
                        {
                            SDL_DestroyTexture(groups[i].sprite.texture);
                            SDL_DestroyRenderer(groups[i].renderer);
                            SDL_DestroyWindow(SDL_GetWindowFromID(groups[i].windowID));
                            groups[i].windowID = 0;
                            closed = 1;
                            num_groups--;
                            SDL_Log("num_groups: %d\n", num_groups);
                            break;
                        }
                    }
                    // The main window was closed, then.
                    if(!closed || num_groups == 0)
                        done = 1;
                }
            }
		}
		for(i = 0; i < max_groups; i++)
		{
			groups[i].sprite.x += groups[i].sprite.velx*dt;
			groups[i].sprite.y += groups[i].sprite.vely*dt;
			if(groups[i].sprite.x < 0)
			{
				groups[i].sprite.x = 0;
				groups[i].sprite.velx = -groups[i].sprite.velx;
			}
			else if(groups[i].sprite.x > screen_w)
			{
				groups[i].sprite.x = screen_w;
				groups[i].sprite.velx = -groups[i].sprite.velx;
			}
			if(groups[i].sprite.y < 0)
			{
				groups[i].sprite.y = 0;
				groups[i].sprite.vely = -groups[i].sprite.vely;
			}
			else if(groups[i].sprite.y > screen_h)
			{
				groups[i].sprite.y = screen_h;
				groups[i].sprite.vely = -groups[i].sprite.vely;
			}
		}
		for(i = 0; i < max_groups; i++)
		{
		    if(groups[i].windowID == 0)
                continue;
		    SDL_RenderClear(groups[i].renderer);
		    SDL_Rect dstrect = {groups[i].sprite.x, groups[i].sprite.y, sprite_w, sprite_h};
		    SDL_RenderCopy(groups[i].renderer, groups[i].sprite.texture, NULL, &dstrect);
		    SDL_RenderPresent(groups[i].renderer);
		}
		SDL_Delay(10);
	}
    for(i = 0; i < max_groups; i++)
    {
        if(groups[i].windowID == 0)
            continue;
        SDL_DestroyTexture(groups[i].sprite.texture);
        SDL_DestroyRenderer(groups[i].renderer);
        SDL_DestroyWindow(SDL_GetWindowFromID(groups[i].windowID));
    }
    SDL_Quit();
    return 0;
}

In case that doesn’t display well in blog format… Download the source here: sdl2_multiwindow

I totally pressed that publish button!

do_it_button

Seriously though, it’s a big deal.  Gladiator is now officially on the OUYA!  If you happen to have one of these nice $99 microconsoles, you can find our latest game in the Sandbox.

Now the trouble is getting out of the Sandbox!  We’re working on spreading the word about this great classic game so it will be more discoverable in the storefront.

talwood_1

Tomorrow is the release day for Gladiator!  Woo!  A lot has gone into the code to get it ready, but it’s finally there.

I’m looking through the changelog and it’s pretty crazy.  I spent a lot of time making a whole new level editor, but in order to do that, I had to refactor a ton of data structures to hold character, level, and campaign info.  New features came in that changed the feel of the game in several positive ways.  The player is given much more information both in and out of combat so they can control their team more effectively and track and enjoy the team’s progress.  And of course, real controller integration on OUYA is sweet.  Overall, at least a hundred bugs were squashed, and I think I only made 80% of them! 😀

So, it’s nice to bring the Openglad project to this point.  I love playing it on OUYA with my kids and I really hope it catches on with a significant audience.  I’m just waiting for another approval of this latest version to be put into the OUYA market (Discover), then we should be seeing it live tomorrow!

Lately, as I’ve gotten the time, I’ve been pounding away at Gladiator (Openglad), a cool retro action RPG that I loved as a kid.  It’s amazing that after all these years (um, 18 since the original release?), I can pick up the code and improve the game I played so much so long ago.

I released a version on Google Play, the Amazon Appstore, and the NOOK Store.  I have been working on a new level editor for a bit.  It’s incredibly improved from the old one; it feels much better to use and has more features.  Best of all, it is now integrated into the game itself.  I’ll be releasing an update to the Android version that includes the level editor.  Also, there are plans to include a sharing system so you can create and share your own levels and campaigns as well as download and rate ones that other people make.

Another bit of news is that I bought an OUYA!  Incidentally, that also means I’m working on the OUYA port of Gladiator…  Yeah, it’s pretty awesome with 4 players smashing around on a big TV.  The downside to mobile Android really is the lack of local multiplayer, so I’m really pleased to have the OUYA as an outlet for the PvP and cooperative multiplayer experiences that I love.  OUYA porting is mostly controller integration work, which can get hairy when it comes to menus, but it’s already working well.  I have a few more details to smooth out, then it’ll be go time.

Tags: , , , , , , ,