First, I want to congratulate all the coders, level designers, and artists who are collaborating to make Quexlor: Lands of Fate a fun RPG! You guys have already started making your maps and have discovered and fixed a few bugs. Way to go!
There’s quite a few community members I will be thanking throughout this post. I want to start documenting the progress on our game with a developer’s diary. This will be the first post. It will go into detail about how some of the new items and effects were made. Get ready to explore the ambience effect, doors, and counters.
This Level Map (lof-example.tmx) Illustrates Some of the New Items and Effects
Ambience
Think about making an audio ambience effect. All it does is play a sound file with proximity to the player. It’s got to have a few properties that it loads from the TMX level file like the sound file to play, the radius of the ambience, and whether or not to loop it. Once we’ve got the properties loaded it’s simply a matter of coding an update method to play the sound file when the player walks within the radius and to vary the volume while the player is close enough. To emulate decibels, a logarithmic curve could be applied to the sound’s volume based on distance. Or it could be linear. It all depended on what felt better. This particular case depended on how the CocosDenshion sound engine (a part of Cocos2D and included in the iPhone Game Kit) handles volume. Is it in decibels already? After creating a “birds chirping” sound loop based on a wonderful effect submitted by users of Freesound.org, the first ambience was born. It turned out that using a linear volume drop-off sounded best. However, a hurdle had to be overcome. CocosDenshion had no way of changing a sound’s volume once it had been played. This had actually been nagging at me for quite some time. How to change an already-playing sound’s volume on an iPhone? The clue was in the OpenAL ALuint
sound identifier which was returned from CocosDension’s playSound
method. After digging through CocosDension for a bit, it became apparent that indeed it’s quite easy to mess with an already-playing sound. I added the following method to the iPhone Game Kit’s Sound
layer to be able to change a sound’s volume on the fly. This method was then utilized to make ambiences sound silky smooth.
-(void) setVolume:(ALuint)fx volume:(ALfloat)volume
{
alSourcef(fx, AL_GAIN, volume > 1.0f ? 1.0f : (volume < 0.0f ? 0.0f : volume));
}
After releasing the iPhone Game Kit 3.2, the community found a little bug with the ambiences. The sound kept on playing even after the player had switched from one level to another. Hat tip to stahlmanDesign for finding the bug and CyberGreg for fixing it so quick! Here’s the method CyberGreg submitted which stops the ambience playing when the old level is destroyed:
-(void) onExit
{
[super onExit];
// stop the ambience because of TMX unloading
if( ambience != CD_MUTE )
{
[[Sound get] stopSound:ambience];
NSLog(@"Stopped playing ambience %@", soundName);
ambience = CD_MUTE;
}
}
Doors
Doors are a pretty simple concept. The player gathers a key and then swings his sword at a door to open it. If the player doesn’t have a key a “this ain’t workin” sound effect is played. If he does have a key then it is decremented from his inventory and the door opens with a poof of smoke. The biggest challenge I ran into regarding making doors was one of bounding boxes and anchor points. When I first made a door, put it inside a level, and walked up next to it there seemed to be a small issue with the door image being higher than the collision bounding box. To start debugging this issue, I added the following method to the
LevelObject
class which draws a white square around each level object and a red circle around the attack point for characters. (The red attack point was added later when I was debugging touch-based movement.)
-(void) draw
{
CGPoint points[4];
[self getCorners:points dest:CGPointZero];
glLineWidth(2.0f);
glColor4f(1, 1, 1, 1);
ccDrawPoly(points, 4, YES);
// draw attack point
if( [self isKindOf:@"Character"] )
{
Character* c = (Character*)self;
if( [c isAttacking] )
{
CGPoint p = [c getAttackPoint];
p = ccpSub(p, [c position]);
glColor4f(1, 0, 0, 1);
ccDrawCircle(p, 3.0f, 0.0f, 8, NO);
}
}
// reset line width & color as to not interfere with draw code in other nodes that draw lines
glLineWidth(1.0f);
glColor4f(1, 1, 1, 1);
[super draw];
}
This made everything clear. Back when I first wrote the QuexlorLite game I had messed with each LevelObject
‘s collision bounding box. It was shifted upwards to account for characters appearing taller than their actual collision box. This was now causing a problem with the doors because they also inherit from LevelObject
. Think about the collision boxes. It wasn’t very natural for the hero’s head to collide with rocks above him. To give the 3D feel, the box worked best as a smaller rectangle aligning more with the hero’s feet. Check out this screenshot to see where the natural-feeling collision boxes fall:
Showing Sprite Collision Boxes With an Overridden
draw
MethodanchorPoints
were used to shift the Character
sprites a little higher. Using anchor points had been on my list for awhile anyway. It was the “proper” thing to do.
// create sprite sprite = [[CCZSprite spriteWithSpriteFrame:[profile getSpriteFrame:@"standing" index:0]] retain];
// move the anchor point a little lower to make the character seem taller
sprite.anchorPoint = ccp(0.5f, 0.31f);
Counters
Remember the game Zelda? Sometimes you get locked in a room and have to defeat all the monsters before a door magically opens. We wanted our community RPG to have this feature and it was actually really, really, really easy to implement. Open up your 3.2 Kit’s Quexlor/art/lof-example.tmx
to follow along, or see the screenshot at the top of this post. There are two skeletons locked inside a small room. A door blocks the way into the room and another door blocks the way to a final room with a big chest. The first door has no special properties and it opens with a regular key. The second door, however, has a few tricks up its sleeve and will not open with a key. Its tricks are the properties counter
=alpha
and count
=2
. This means the door opens only after its counter (codename alpha
) has been decremented twice. But how does the door’s counter get decremented? Both of the skeletons have a special property counter
=alpha
that corresponds with the door. When an enemy dies a piece of code looks for a door with the corresponding counter. If a door is found, then its count is decremented. Here’s the code that makes that happen:
// reduce counter
if( counter != nil )
{
for( Door* door in [[self getLevel] children] )
{
if( [door isKindOf:@"Door"] && [door hitCounter:counter] ) break;
}
}
Simple, eh? You just give the enemies a counter
property and the door the same counter
as well as a count
. I hope the counter object makes designing your levels a bit more fun!
Credits
Thanks to Mow-Mow for noticing another little bug. The flame animation accidentally had a white background. It’s fixed. Thanks again to stahlmanDesign for proposing to name all level objects so they can be easily identified at a glance. Thanks to CyberGreg for organizing all the level filenames, and thanks to juancasanueva for taking the reins with the troll AI. Props to everybody, and congratulations community for all the positive collaboration! That’s all for now.