cocos2d-x 3.0 alpha 0 chipmunk debug draw

Was wanting to get a quick demo up with cocos2d-x and chipmunk to play around with chipmunk physics and had a bit of train wreck trying to get a debug node to work properly. It’s really not complicated, but hopefully this will keep someone else from stumbling through it like I did. I should’ve kept link to the person that inspired this. It wasn’t completely my idea.

ChipmunkDebugNode.h

//
// desc: ChipmunkDebugNode.h

#ifndef _CHIPMUNKDEBUGNODE_H_
#define _CHIPMUNKDEBUGNODE_H_

//
// includes
#include "cocos2d.h"
#include "chipmunk.h"

//
// struct ChipmunkDebugNode
struct ChipmunkDebugNode : cocos2d::Node
{
    //
    // static methods
    static ChipmunkDebugNode *create(cpSpace *space);
    static void DrawShape(cpShape *shape, void *data);
    
    //
    // init
    virtual bool init(cpSpace *space);
    
    //
    // overload draw
    virtual void draw();
    
    //
    // variables
    cpSpace *_space;
};

#endif // #ifndef _CHIPMUNKDEBUGNODE_H_

ChipmunkDebugNode.cpp

//
// desc: ChipmunkDebugNode.cpp

//
// includes
#include "ChipmunkDebugNode.h"

// cocos2d namespace
USING_NS_CC;

ChipmunkDebugNode *ChipmunkDebugNode::create(cpSpace *space)
{
    ChipmunkDebugNode *chipmunkDebugNode = new ChipmunkDebugNode();
    if (chipmunkDebugNode && chipmunkDebugNode->init(space)) {
        chipmunkDebugNode->autorelease();
        return chipmunkDebugNode;
    }
    
    // else cleanup
    delete chipmunkDebugNode;
    return NULL;
}

void ChipmunkDebugNode::DrawShape(cpShape* shape, void *data)
{
    if (!shape) {
        return;
    }
    
    glLineWidth(5.0f);
    DrawPrimitives::setDrawColor4F(0.5f, 0.5f, 0.5f, 1.0f);
    
    if (shape->klass_private->type == CP_CIRCLE_SHAPE) {
        cpCircleShape *circle = (cpCircleShape *)shape;
        cpVect center = cpvadd(shape->body->p, cpvrotate(circle->c, shape->body->rot));
        DrawPrimitives::drawCircle(Point(center.x, center.y), circle->r, shape->body->a, 20, true);
        return;
    }
    
    if (shape->klass_private->type == CP_POLY_SHAPE) {
        cpPolyShape *poly = (cpPolyShape *)shape;
        
        // convert chipmunk points to coco points
        Point *pointArray = new Point[poly->numVerts];
        for (int i=0; i  < poly->numVerts; i++) {
            pointArray[i] = Point(poly->tVerts[i].x, poly->tVerts[i].y);
        }
        
        DrawPrimitives::drawPoly(pointArray, poly->numVerts, true);
        delete pointArray;
        return;
    }
    
    if (shape->klass_private->type == CP_SEGMENT_SHAPE) {
        cpSegmentShape* segment = (cpSegmentShape*)shape;
        DrawPrimitives::drawLine(Point(segment->ta.x, segment->ta.y),
                                 Point(segment->tb.x, segment->tb.y));
        return;
    }
    
    cpSegmentShape *segment = (cpSegmentShape *)shape;
    DrawPrimitives::drawLine(Point(segment->ta.x, segment->ta.y),
                             Point(segment->tb.x, segment->tb.y));
}

//
// descP init
bool ChipmunkDebugNode::init(cpSpace *space)
{
    if (!Node::init()) {
        return false;
    }
    
    // init variables
    _space = space;
    
    return true;
}

//
// desc: override draw
void ChipmunkDebugNode::draw()
{
    cpSpaceEachShape(_space, &DrawShape, this);
}

And for completeness, here is example of using it inside my root layer. Should all be pretty straight forward once you see it.

// setup chipmunk stuff
cpSpace *space = cpSpaceNew();
cpSpaceSetGravity(space, cpv(0.0f, -1.0f));

cpBody *body = cpBodyNew(100.0f, INFINITY);
cpShape *shape = cpCircleShapeNew(body, 30.0f, cpvzero);

body->p = cpv(visibleSize.width / 2.0f, visibleSize.height / 2.0f);

cpSpaceAddBody(space, body);
cpSpaceAddShape(space, shape);

// create and add chipmunk debug node
this->addChild(ChipmunkDebugNode::create(space));

S BAT 2 is now Open Source!

It only took me forever to finally get this all posted. Since S BAT 2 had total of around 200 downloads over a year, I decided to cancel my developers license. I quickly realized I am too introverted to learn how to sell myself and my game to others. It’s either the introversion, or the lack of self confidence, or both, or that I’m not cut out for game dev, or that was my first game and you can’t expect your first to be great, or however else you want to frame it. So, for that year and a half of work doesn’t go to complete waste. I decided to release the code on github (https://github.com/PingPongRhino/sbat2) as open source under the MIT license in hopes it might help someone else. I mainly hope someone will improve upon how I did my beizer lasers. Heck, I’d be super excited if someone just steals the logic outright. As long as there are more awesome 2D bendy lasers in games, then I will feel I’ve had some small impact on the world. I do still have it running on my phone and I do hope to show it at the next Juegos Rancheros to get some word out about the code being available on github. I haven’t gotten in touch with anyone yet, so I will let you know.

And also, for those who thought you couldn’t beat the game. Chris and Brian helped me make a full playthrough video along with commentary track. Since it’s hard to record iOS high quality video, we cloned it on the apple tv and captured it, which meant the video FPS and resolution isn’t the best. It does run at a full 60fps on the 4S. Well, that was back on iOS 6, so maybe things have changed with iOS 7. Note, there is some explicit language in the commentary. You can find video here. I recommend downloading it as it’s kind of large and streaming doesn’t work so well. I will wait for site5 to get mad at me for uploading such a large video…

Big thanks to Chris Pellett, Brian Langevin, and Brian Stark for all the help on the game and a huge thanks for anyone who actually played the game! This pretty much brings S BAT 2 to a close finally!

Realizations as an iOS hobbyist

Read this article, Some Depressing Thoughts On The App Store, this morning and had some thoughts I needed to get out there. I’ve always thought of the app store being more of a marketing tool for Apple, but I think someone validating the claim really makes it hit home. I know I haven’t done my part to market my game and I created S BAT 2 to complete a life goal, not to supplement my income. I was not expecting to become a millionaire or even hundred-aire based on one silly idea for a game. It would be nice to get reimbursed the fees for the dev license, but even then, I considered it the cost of a hobby. Between seeing articles like this, XCode and LLDB making my job a complete nightmare this week, and the fact that when I let my dev license expire I’ll never be able to play my game again (well I could do some jailbreaking), I honestly don’t know why any hobbyist would want to develop on iOS. Outside of Objective-C, which is amazing, Apple development is pretty painful process in my book. If I do tackle another game project, it’ll be on Windows or Linux. At least there I won’t have to pay just to be able to develop and play my own game. Also the indie game community is way more supportive in the Windows and Linux world. End rant.

S BAT 2 GXO: The OS HD Now Available!

Finally got HD version for iPad released on the App Store. I apologize that the framerate isn’t as good as I had hoped for. If I find time, I will try to correct that. Also just submitted v1.1 for iPhone which contains various fixes.

S BAT 2 GXO: The OS version 1.1 fixes

  • fix, now decrypts and plays background track on a low priority background thread, this is to fix a performance hiccup that occurs when it is decrypting the track
  • fixed some typos in tutorial menus
  • added pause bar to tutorial mode so players can back out of it
  • fixed the repeater beams from popping from their old location when being reinitialized
  • fix where first wave stage would sometimes continue spawning off the play field

Now I just need to make a sad attempt at marketing this guy ^____^

Getting back to it

After a long hiatus which included beating Bastion, VVVVV, and Botanicula, I’m finally getting back into some S BAT 2 development. Got a little bit of feedback, but not much. Which is what I expected since I’m virtually unknown to anyone and it’s just a few friends that mention it every once in a while. Goal is to try to get iPad paid version out sometime in August. I will also make some sad attempt at marketing, and if I don’t see any interest after that, then I will probably drop the project. Anywho, here is what I got done today.

  • Move decryption and loading of background files to low priority background thread so it will no longer interrupt gameplay with the performance hit
  • Added pause terminal to top of the tutorial mode so you can back out and adjust options when in the tutorial
  • Fixed typos in tutorial terminal text

S BAT 2 GXO: The OS is now available!

And even better, it’s free! Download it here! Starting to get some feedback from people so starting a list of fixes and changes for the next update. I plan to take a break for the next couple of weeks and play through Bastion (I’m sooo far behind on gamming) and then I’ll start on small update for iPhone followed by working on hi res iPad version. And somewhere in there, I plan to start scripting the story mode. So after I take a short hiatus, here is the list of stuff I want to tackle. Thanks for playing and please feel free to give me feedback.

S BAT 2 GXO: The OS v2

  • Fix bug where game pauses when loading next background track, this hiccup sometimes causes the mobile towers to move outside of the play field
  • Add terminal to top of tutorial mode so user can exit tutorial mode at any point and adjust options without having to complete the tutorial
  • Track down bug when emulating on iPad where sometimes the emissions will reach their target, but disappear preventing the next wave from spawning
  • Fix typos in the tutorial menu text (I’m sure Marc is laughing it up on this one…)

Loading sound from memory into cocos2d’s SimpleAudioEngine

This was seriously way more work than I anticipated. In the end, it really wasn’t that complicated, but just took a while to put all the pieces together. Having to wrap the ExtAudioFileRef around an AudioFileID and then having to setup callback functions to get the AudioFileID from memory felt like a bit of overkill… Here is the code I added to CDOpenALSupport.m. I’m sure there is a better solution, but this works so I’m gonna stick with it for now ^_^.

//
// desc: for storing the decrypted audio file data in memory
typedef struct {
    unsigned char *data;    // pointer to audio data
    SInt64 dataLength;      // length of audio data
} AudioFileMemory;

//
// desc: callback for reading the audio data, 
//       check AudioFileOpenWithCallbacks doc for more details
OSStatus AudioFileReadProc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount)
{
    // check parameters
    if (!inClientData || !buffer || !actualCount) {
        return EINVAL;
    }
    
    AudioFileMemory *audioFileMemory = (AudioFileMemory *)inClientData;
        
    // make sure position is within bounds
    if (inPosition < 0 || inPosition >= audioFileMemory->dataLength) {
        *actualCount = 0; // don't read anything and tell them everything is just friggin fine,
                          // this is called passive aggressive error handling ^_^
        return noErr;
    }
    
    // see if we need to cap requested length
    *actualCount = requestCount;
    SInt64 endPosition = inPosition + requestCount;
    if (endPosition >= audioFileMemory->dataLength) {
        *actualCount = requestCount - (endPosition - audioFileMemory->dataLength);
    }
    
    memcpy(buffer, audioFileMemory->data + inPosition, *actualCount);
    return noErr;
}

//
// desc: callback for getting size of audio data 
//       check AudioFileOpenWithCallbacks doc for more details
SInt64 AudioFileGetSizeProc(void *inClientData) {
    if (!inClientData) {
        return EINVAL;
    }
    
    AudioFileMemory *audioFileMemory = (AudioFileMemory *)inClientData;
    return audioFileMemory->dataLength;
}

//
// desc: assumes this is encrypted or packaged file and that
//       the file naming convention is filename.xxx.enc or filename.xxx.tar, etc...
//       determines type based off xxx, only coded for checking for wave and mp3
//       cause that's all I care about right now
//
// params: inFileURL[in] - file url to get audio file type on
//
// returns: audio file type id for file if successful
//          returns 0 if file type is unknown
AudioFileTypeID GetAudioFileTypeId(CFURLRef inFileURL)
{
    CFURLRef pathWithoutEncExtension = CFURLCreateCopyDeletingPathExtension(kCFAllocatorDefault, inFileURL);
    CFStringRef extension = CFURLCopyPathExtension(pathWithoutEncExtension);
    AudioFileTypeID audioFileTypeId = 0;
    if (CFStringCompare(extension, (CFStringRef)@"wav", kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
        audioFileTypeId = kAudioFileWAVEType;
    }
    else if (CFStringCompare(extension, (CFStringRef)@"mp3", kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
        audioFileTypeId = kAudioFileMP3Type;
    }
    
    CFRelease(pathWithoutEncExtension);
    CFRelease(extension);
    return audioFileTypeId;
}

//
// desc: creates an audio file id, checks to see if file type is encrypted.
//       if it's not encrypted, then it loads it like normal and returns audioFileId.
//       if is its encrypted, it will decrypt the file in memory and load it into a AudioFileId.
//       if it decrypts, then it will return pointer to the audio data in audioFileMemory.  when
//       you close the audioFileId, be sure to free() the data pointer inside audioFileMemory.
//       I'm not really sure if we need to keep the data around, but I'm doing it to be safe.
//
// params: inFileURL[in] - url of file to load
//         audioFileId[out] - returns audio file id if file was successfully loaded
//         audioFileMemory[out] - returns pointer to audio memory if the audio file was decrpyted and loaded from mem
//                                BE SURE TO CALL free() ON THE DATA POINTER WHEN YOU ARE DONE WITH THE AUDIO FILE ID
//
// returns: returns  0 if opened file like normal (not encrypted)
//          returns  1 if dectypred file and loaded it from memory
//          returns -1 if failed to open unencrypted file
//          returns -2 if failed to open encrypted file
//          returns -3 if failed to decrypt file
//          returns -4 if failed to load audio file id with decrypted memory
int CreateAudioFileId(CFURLRef inFileURL, AudioFileID *audioFileId, AudioFileMemory *audioFileMemory)
{
    CFStringRef extension = CFURLCopyPathExtension(inFileURL);
    OSStatus err = noErr;
    
    // reset audioFileMemory
    memset(audioFileMemory, 0, sizeof(AudioFileMemory));
    
    // if not an encrypted file, then get audio id like always
    if (CFStringCompare(extension, (CFStringRef)@"enc", kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
        CFRelease(extension);
        
        err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, audioFileId);
        if (err) {
            CDLOG(@"CreateAudioFileId: AudioFileOpenURL FAILED, Error = %ld\n", err);
            return -1;
        }
        
        return 0;
    }
    
    CFRelease(extension);
    
    // else we are dealing with encrypted file, so decrypt it
    @autoreleasepool {
        NSData *encData = [NSData dataWithContentsOfURL:(NSURL *)inFileURL];
        
        if (!encData) {
            CDLOG(@"CreateAudioFileId: Failed to load %@, could not find file.", CFURLGetString(inFileURL));
            return -2;
        }
        
        audioFileMemory-&gt;dataLength = CCDecryptMemory((unsigned char *)[encData bytes], [encData length], &amp;audioFileMemory->data);
        if (!audioFileMemory->data) {
            CDLOG(@"CreateAudioFileId: Failed to load %@, failed to decrypt.", CFURLGetString(inFileURL));
            return -3;
        }
    }
    
    err = AudioFileOpenWithCallbacks(audioFileMemory,
                                     AudioFileReadProc, NULL,
                                     AudioFileGetSizeProc, NULL,
                                     GetAudioFileTypeId(inFileURL), audioFileId);
        
    if (err) {
        char bytes[4];
        bytes[0] = (err >> 24) &amp; 0xFF;
        bytes[1] = (err >> 16) &amp; 0xFF;
        bytes[2] = (err >> 8) &amp; 0xFF;
        bytes[3] = err & 0xFF;
        CDLOG(@"CreateAudioFileId: AudioFileOpenWithCallbacks FAILED, Error = %ld, %c%c%c%c\n", err, bytes[0], bytes[1], bytes[2], bytes[3]);
        return -4;
    }
    
    return 1;
}

And after adding all that, just had to change CDloadWaveAudioData() and CDloadCafAudioData to use the new CreateAudioFileId() calls.

CDloadWaveAudioData() change:

// Taken from oalTouch MyOpenALSupport 1.1
void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*	outSampleRate)
{
	OSStatus						err = noErr;	
	UInt64							fileDataSize = 0;
	AudioStreamBasicDescription		theFileFormat;
	UInt32							thePropertySize = sizeof(theFileFormat);
	AudioFileID						afid = 0;
	void*							theData = NULL;
    AudioFileMemory                 audioFileMemory;
	
	// Open a file with ExtAudioFileOpen()
    if (CreateAudioFileId(inFileURL, &afid, &audioFileMemory) < 0) {
        goto Exit;
    }

CDloadCafAudioData change:

// Taken from oalTouch MyOpenALSupport 1.4
void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate)
{
	OSStatus						status = noErr;
	BOOL							abort = NO;
	SInt64							theFileLengthInFrames = 0;
	AudioStreamBasicDescription		theFileFormat;
	UInt32							thePropertySize = sizeof(theFileFormat);
	ExtAudioFileRef					extRef = NULL;
	void*							theData = NULL;
	AudioStreamBasicDescription		theOutputFormat;
	UInt32							dataSize = 0;
    AudioFileID                     audioFileId = 0;
    AudioFileMemory                 audioFileMemory;
    
    // create audio file id
    if (CreateAudioFileId(inFileURL, &audioFileId, &audioFileMemory) < 0) {
        goto Exit;
    }
	
	// Open a file with ExtAudioFileOpen()
    status = ExtAudioFileWrapAudioFileID(audioFileId, false, &extRef);
	if (status != noErr)
	{
		CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status);
		abort = YES;
	}
	if (abort)
		goto Exit;

Momentum Lost

Sorry for lack of updates, I’ve lost some momentum due to Thanksgiving and Christmas stuff coming up. Starting to get back into the swing of things. Have a plan to tackle tutorial which is next major thing on the list. In the meantime, I got some artwork done. Three of the ten characters are done. Here is a shot (from left to right) of Coco, Bango Blue, and Spin Lock. Click on image to see high rez.

S BAT 2 beta 2.0 now with fancy retina display

Sorry for lack of updates. Lost some momentum last few weeks (mainly due to Warhammer 40k: Space Marine and Dragon Age…). I got me a fancy iPhone 4S and got the artwork up-converted for retina display. Cocos2d made it relatively easy. Had one issue with z axis, but it wasn’t too hard to track down. As far as I can remember, the only change for this is retina. So no changelog this time.