Sunday, October 23, 2011

Making Tools for Making Games

I've been creating apps and games for sometime, and each time I seem to make a second app/tool to help in the creation. Defining layouts, levels, settings, data, designs. I could define all this pre-existing data in code, and then muddle through it when I need to change or upgrade it. But often I find myself working with others that haven't devoted themselves to understanding cryptic programing languages and scripts. This prompts them to send me change request that seam so minor I prioritize them low and let them stack up until major code changes have been finished. I try to encourage the co-workers to decipher my xml,json,or sqlite files to change the simple but critical design features themselves, but this just brews difficulties in a project's lifecycle time and again. Even on an off day I'd rather not plot out x and y points in my head only to find that I needed to subtract other offsets and half widths when defining the property. Maybe it's all those years I spend developing in Flash and the authoring stage space that allowed me to place and go.
When I first started using Xcode and InterfaceBuilder, I understood it fairly well because it would help me place and then code. But unlike Flash, it did not strike me as the type of layout app that I should be defining each and every lesson, level, or scene. I combined my experiences with each of these IDEs and leveraged laying out in Flash, while bringing the raw values to the iOS. My co-workers also know their way around the Flash stage so less learning curve there too.

I've used this approach when developing a e-learning engine,  and I'm refining it on my next point-and-click adventure game, but today I will cover how it helped making levels in Petunk.
ActionScript has grown since it limited commands from '98. Today you can access just about every property, which is nice for even breaking down per frame properties. For now I'm only going to go about moving screen elements to get some initial definitions. What I start with is making an ActionScript class that will write out the movieclip properties I'm interested in.


public class Obstacle extends MovieClip {

public function Obstacle() {}

public function getDef():String {
return '{'+
'type = "'+getQualifiedClassName(this)+'";'+
'cord = "'+int(this.x) +','+int(480-this.y)+','+int(this.rotation)+'";'+
'size = "'+this.scaleX+','+this.scaleY+'";'+
'},';
}
}


The design behind this class gets the stage properties I'm interested in so they can be bundled in an iOS app. Each instance of this Obstacle class will define the settings into a simple format that un-translate later in Objective-c. The getQualifiedClassName() is kinda a trick with the library, it will return the subclass name. In this case I define the subclass by the item or image I place in the movieclip at the library level.
This class returns: {type = "goal";cord = "160,417,0";size = "1,1";}

The main timeline needs to aggregate all these instances that laying around the stage, so the movieclip of the FLA file itself needs to be subclassed as well. The main purpose here to let the frame have time to draw the clips that I've laid out, and then read the values back and write in a plist friendly format so I don't have to change the values by hand and make the mistake of missing a bracket or semicolon. The LevelPlist.as file looks like this:

public class LevelPlist extends MovieClip {

public function LevelPlist() {
//Clips aren't on stage yet
this.addEventListener(Event.EXIT_FRAME, onFrame);
}

public function onFrame(evt:Event) {
   //Remove event so this does not call repeatedly
this.removeEventListener(Event.EXIT_FRAME, onFrame);
   var count:int = this.numChildren;
trace("{");
trace("levelId = "+this.currentFrame+";");
trace("obstacles = (");
for(var i:int = 0; i < count; ++i) {
var obj:Obstacle = this.getChildAt(i) as Obstacle;
obj.getDef());
}
trace(");");
trace("}");
}
}
}

With just a little bit of script I can now drag new clips out of the library, place them on stage, move them around on stage, use Flash interface to scale, rotate, and align. Then just simply run a "Test Movie" to cause the script to run and trace string data to the output window. Cut, Paste, Save Plist file and the changes are ready to be bundled in the next build of the Xcode project.

Because the setup of a level is being disconnected from the compiled code, there needs to be a simple way to map the obstacle names back to types that can be interpreted into physics objects. I have a Factory class help me with this, so as I load a level plist file I then loop through the array of obstacles and have a 'else if' stack translate the names to a enum type that helps in switch statements later in the the game loop.


+ (displayType)StringToType:(NSString*)aStr {
    if ([aStr isEqualToString:@"ball"]) return display_ball;
else if ([aStr isEqualToString:@"wall00"]) return obst_wall00;
else if ([aStr isEqualToString:@"wall01"]) return obst_wall01;
else if ([aStr isEqualToString:@"angle"]) return obst_angle;
else if ([aStr isEqualToString:@"bumper"]) return obst_bumper;
    else if ([aStr isEqualToString:@"goal"]) return sens_goal;
return display_null;
}

Then because earlier I saved the X, Y and Rotation into a comma delimited string I use the nsstring separator to make a quick array of the 3 float values I need to use to define position. This next method gives me back my PhysicBase objects which have references to both Box2d objects and the Cocos2d objects.


+ (PhysicBase*)CreateObject:(NSDictionary*)objData inMapSpace:(CGSize)mapsize {
displayType dType = [ObjectFactory StringToType:[objData objectForKey:@"type"]];
    physicType pType = [ObjectFactory DisplayToPhysicType:dType];
PhysicBase* base = [[PhysicBase alloc] initWithDisplay:dType];
    base->m_basetype = pType;
    NSArray* values = [[objData objectForKey:@"cord"] componentsSeparatedByString:@","];
base.position = ccp([[values objectAtIndex:0] floatValue], [[values objectAtIndex:1] floatValue]);
base.rotation = [[values objectAtIndex:2] floatValue];
[base autorelease];
return base;
}


So what does this really solve?
Well I mainly found myself editing plist files and changing the x or y value, only to find that I miss calculated or want to nudge it a bit more, and then a bit more. Changing a value like x = 124 was not a instance change and gave me know way of "Eye balling it" for spacing until I compiled and ran the project. Seeing the placement drawn as you move and rotate something is clearer to see than just numbers.
Why not make a level builder on the iPhone?
As nice of a framework UIKit and Cocos2d are they do not have out of the box, move, scale, and rotate tools, I could spend time making them. Then there is the confined workspace of the 320x480. Software like Photoshop and Flash are great for comping out designs. (And if I could export propertied from Photoshop, I would.) So why not levels, Flash already has move, scale, and rotate.  Has a stage area and extra workspace area this zoom controls already hooked up.


No comments:

Post a Comment