Skip to content

Instantly share code, notes, and snippets.

@gintsmurans
Created March 21, 2014 04:05
Show Gist options
  • Save gintsmurans/9679330 to your computer and use it in GitHub Desktop.
Save gintsmurans/9679330 to your computer and use it in GitHub Desktop.
Cocos2d v3 Physics Editor shape loader
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>metadata</key>
<dict>
<key>format</key>
<integer>1</integer>
<key>designScale</key>
<real>{{global.designScale}}</real>
</dict>
<key>bodies</key>
<dict> {% for body in bodies %}
<key>{{body.name}}</key>
<dict>
<key>size</key>
<string>{ {{body.size.width|floatformat:5}},{{body.size.height|floatformat:5}} }</string>
<key>anchorpoint</key>
<string>{ {{body.anchorPointRel.x|floatformat:5}},{{body.anchorPointRel.y|floatformat:5}} }</string>
<key>physicsBodyTypeDynamic</key>
{% if body.physicsBodyTypeDynamic %}<true/>{% else %}<false/>{% endif %}
<key>canRotate</key>
{% if body.canRotate %}<true/>{% else %}<false/>{% endif %}
<key>obeyGravity</key>
{% if body.obeyGravity %}<true/>{% else %}<false/>{% endif %}
<key>fixtures</key>
<array> {% for fixture in body.fixtures %}
<dict>
<key>mass</key>
<real>{{fixture.mass}}</real>
<key>elasticity</key>
<real>{{fixture.elasticity}}</real>
<key>friction</key>
<real>{{fixture.friction}}</real>
<key>surface_velocity</key>
<string>{ {{fixture.surface_velocity_x|floatformat:5}},{{fixture.surface_velocity_y|floatformat:5}} }</string>
<key>collisionGroup</key>
<string>{{fixture.collision_group}}</string>
<key>collisionType</key>
<string>{{fixture.collision_type}}</string>
<key>isSensor</key>
{% if fixture.isSensor %}<true/>{% else %}<false/>{% endif %}
<key>fixture_type</key>
<string>{{fixture.type}}</string>
{% if fixture.isCircle %}
<key>circle</key>
<dict>
<key>radius</key>
<real>{{fixture.radius|floatformat:3}}</real>
<key>position</key>
<string>{ {{fixture.center.x|floatformat:3}},{{fixture.center.y|floatformat:3}} }</string>
</dict>
{% else %}
<key>isPolylines</key>
{% if fixture.isPolylines %}<true/>{% else %}<false/>{% endif %}
<key>cornerRadius</key>
<real>{{fixture.cornerRadius}}</real>
<key>hull</key>
<array>{% for point in fixture.hull %}
<string>{ {{point.x|floatformat:5}},{{point.y|floatformat:5}} }</string>{% endfor %}
</array>
<key>polygons</key>
<array>{% for polygon in fixture.polygons %}
<array>{% for point in polygon %}
<string>{ {{point.x|floatformat:5}},{{point.y|floatformat:5}} }</string>{% endfor %}
</array>{% endfor %}
</array>
{% endif %}
</dict> {% endfor %}
</array>
</dict> {% endfor %}
</dict>
</dict>
</plist>
<?xml version="1.0"?>
<exporter>
<name>ccphysics</name>
<displayName>Cocos2D v3.X CCPhysics</displayName>
<description>Exporter for generating physics data for CCPhysics in Cocos2d v3.X.</description>
<version>0.1</version>
<yAxisDirection>up</yAxisDirection>
<physicsEngine>chipmunk</physicsEngine>
<template>ccphysics.plist</template>
<fileExtension>plist</fileExtension>
<anchorPoint>
<enabled>yes</enabled>
<relX>0.5</relX>
<relY>0.5</relY>
</anchorPoint>
<origin>
<type>anchorPoint</type>
</origin>
<global>
<parameter>
<name>designScale</name>
<displayName>Design scale?</displayName>
<description>Indicates the scale of the design (retina/non-retina).</description>
<type>float</type>
<default>2.0</default>
</parameter>
</global>
<body>
<parameter>
<name>physicsBodyTypeDynamic</name>
<displayName>Dynamic Body</displayName>
<description>Indicates whether body is dynamic or static</description>
<type>bool</type>
<default>false</default>
</parameter>
<parameter>
<name>canRotate</name>
<displayName>Able to Rotate?</displayName>
<description>Sets ability for body/shape to rotate</description>
<type>bool</type>
<default>true</default>
</parameter>
<parameter>
<name>obeyGravity</name>
<displayName>Affected by Gravity?</displayName>
<description>Indicates if this body/shape respects gravity</description>
<type>bool</type>
<default>true</default>
</parameter>
</body>
<fixture>
<parameter>
<name>mass</name>
<displayName>Mass</displayName>
<type>float</type>
<default>2.0</default>
</parameter>
<parameter>
<name>elasticity</name>
<displayName>Elasticity</displayName>
<type>float</type>
<min>0</min>
<max>1000</max>
<default>0.0</default>
</parameter>
<parameter>
<name>friction</name>
<displayName>Friction</displayName>
<type>float</type>
<min>0</min>
<max>1000</max>
<default>0.0</default>
</parameter>
<parameter>
<name>surface_velocity_x</name>
<displayName>Surface velocity X</displayName>
<type>float</type>
<min>-1000</min>
<max>1000</max>
<default>0.0</default>
</parameter>
<parameter>
<name>surface_velocity_y</name>
<displayName>Surface velocity Y</displayName>
<type>float</type>
<min>-1000</min>
<max>1000</max>
<default>0.0</default>
</parameter>
<parameter>
<name>cornerRadius</name>
<displayName>Corner Radius</displayName>
<type>float</type>
<min>-1000</min>
<max>1000</max>
<default>0.0</default>
</parameter>
<parameter>
<name>isSensor</name>
<displayName>Is Sensor</displayName>
<description>If set the polygon is a sensor</description>
<type>bool</type>
<default>false</default>
</parameter>
<parameter>
<name>isPolylines</name>
<displayName>Is Polylines</displayName>
<description>If set the polygon will be from polyLines</description>
<type>bool</type>
<default>false</default>
</parameter>
<parameter>
<name>collision_type</name>
<displayName>Collision Type</displayName>
<description>Collision Type</description>
<shortDescription>Collision Type</shortDescription>
<type>string</type>
<default>default</default>
</parameter>
<parameter>
<name>collision_group</name>
<displayName>Collision Group</displayName>
<description>Collision Group</description>
<shortDescription>Collision Group</shortDescription>
<type>string</type>
<default>nil</default>
</parameter>
</fixture>
</exporter>
//
// PEShapeCache.h
//
// Put together form the sources all over the internet by Gints Murans.
// No guarantees, of course.
//
/*
This class is used to load physics shapes from a plist created by the
Physics Editor. It will work only when used together with a custom exporter.
Custom exporters can be added to Physics Editor in its Preferences.
*/
#import "cocos2d.h"
typedef enum {
POLYGON_FIXTURE,
CIRCLE_FIXTURE,
POLYLINE_FIXTURE
} FixtureType;
#pragma mark - CCNode (PhysicsEditor)
@interface CCNode (PhysicsEditor)
// Shortcut to add physics body to a CCNode
- (void)setPhysicsBodyWithName:(NSString *)name;
@end
#pragma mark - PEShapeCache
@interface PEShapeCache : NSObject
// ShapeCache is a singleton, and this will return that single instance
+(PEShapeCache*)sharedShapeCache;
// reads the plist created with PE and creates a dictionary of body definitions
// with shape definitions in tow
-(BOOL)addPhysicsShapesWithFile:(NSString*)plist;
// Returns a CCPhysicsBody with accompanying shapes as well as other physics
// attributes
-(CCPhysicsBody*)bodyWithName:(NSString*)name;
// Returns the anchor point for use with a CCSprite
-(CGPoint)anchorPointForShape:(NSString*)shape;
@end
//
// PEShapeCache.m
//
// Put together form the sources all over the internet by Gints Murans.
// No guarantees, of course.
//
#import "PEShapeCache.h"
#pragma mark - CCNode (PhysicsEditor)
@implementation CCNode (PhysicsEditor)
- (void)setPhysicsBodyWithName:(NSString *)name
{
[self setPhysicsBody:[[PEShapeCache sharedShapeCache] bodyWithName:name]];
[self setAnchorPoint:[[PEShapeCache sharedShapeCache] anchorPointForShape:name]];
}
@end
static float area(CGPoint* verts, int vertCount) {
int n = (vertCount-1);
// calculate triangle between last and first vert
float area = (verts[0].x* verts[n].y) - (verts[n].x * verts[0].y);
// calculate each quad individually and add to area
for (int i=0; i<vertCount-1; ++i) {
area += (verts[n-i].x * verts[n-(i+1)].y) - (verts[n-(i+1)].x * verts[n-i].y);
}
// multiply by half to get the triangle area from the quad
area = area * 0.5f;
return area;
}
#pragma mark - Polygon
//**********************************************************************
// Holds details on individual polygons that make up shape
@interface Polygon : NSObject
@property CGPoint* vertices;
@property int numVertices;
@property float area;
@end
//************
@implementation Polygon
@end
#pragma mark - FixtureNode
//*********************************************************************
// Fixture definition to hold fixture data. Most attributes are stored
//here, as well as polygons and hull verts.
@interface FixtureNode : NSObject
@property FixtureType fixtureType;
@property float mass;
@property float density;
@property float area;
@property float elasticity;
@property float friction;
@property CGPoint surfaceVelocity;
@property NSString* collisionType;
@property NSString* collisionGroup;
@property BOOL isSensor;
@property float cornerRadius;
// for circles
@property CGPoint center;
@property float radius;
// for solid polygons
@property NSMutableArray* polygons;
// for polygons made of polyLines
@property BOOL isPolylines;
@property CGPoint* polyPoints;
@property int numPolyPoints;
@end
//***************
@implementation FixtureNode
-(id)init {
self = [super init];
if (!self) return nil;
self.polygons = [NSMutableArray array];
return self;
}
@end
#pragma mark - BodyNode
//***********************************************************************
// Body definition holds the ficture data, as well as booleans telling
// us if the body can rotate and respects gravity. Also has anchor point for
// use when lining up a sprite
@interface BodyNode : NSObject
@property CGPoint anchorPoint;
@property NSMutableArray* fixtures;
@property BOOL canRotate;
@property BOOL obeyGravity;
@property BOOL dynamicBody;
@end
//************
@implementation BodyNode
-(id)init {
self = [super init];
if (!self) return nil;
self.fixtures = [NSMutableArray array];
return self;
}
@end
#pragma mark - PEShapeCache
//***********************************************************************
// this is where all the body definitions are stored once the plist
// has been read and parsed.
@implementation PEShapeCache {
NSMutableDictionary* _bodyNodes;
}
static PEShapeCache* shapeCache = nil;
-(id)init {
self = [super init];
if (!self) return nil;
_bodyNodes = [[NSMutableDictionary alloc] init];
return self;
}
+(PEShapeCache*)sharedShapeCache {
if(shapeCache == nil) shapeCache = [[PEShapeCache alloc] init];
return shapeCache;
}
// returns anchor point for use with a corresponding sprite
-(CGPoint)anchorPointForShape:(NSString*)shape {
BodyNode *bn = [_bodyNodes objectForKey:shape];
if (!bn) {
CCLOG(@"Body not found for shape: %@",shape);
return CGPointZero;
}
return bn.anchorPoint;
}
// Meat and potatoes here. Returns a fully set up CCPhysicsBody, which also
// has its shapes attached. This method creates the bodies as CCPhysicsShapes
// and uses the bodywithshapes: method for creating the body. Most attributes
// are implemented through the shape, except for allowsRotation and
// affectedByGravity, which are exclusive to CCPhysicsBody
-(CCPhysicsBody*)bodyWithName:(NSString*)name {
BodyNode* bn = [_bodyNodes objectForKey:name];
if (!bn) {
CCLOG(@"Body not found for name: %@",name);
return nil;
}
// it puts the lotion (shapes) in the basket (array)
NSMutableArray* shapes = [NSMutableArray array];
// iterate over fixtures
for (FixtureNode* fn in bn.fixtures) {
if (fn.fixtureType == CIRCLE_FIXTURE) {
CCPhysicsShape* shape = [CCPhysicsShape circleShapeWithRadius:fn.radius
center:fn.center];
// set property values
shape.mass = fn.mass;
shape.elasticity = fn.elasticity;
shape.friction = fn.friction;
shape.surfaceVelocity = fn.surfaceVelocity;
shape.sensor = fn.isSensor;
shape.collisionType = fn.collisionType;
shape.collisionGroup = NSClassFromString(fn.collisionGroup);
[shapes addObject:shape];
} else if (fn.fixtureType == POLYGON_FIXTURE) {
// iterate over polygons
for (Polygon* poly in fn.polygons) {
CCPhysicsShape* shape = [CCPhysicsShape polygonShapeWithPoints:poly.vertices
count:poly.numVertices
cornerRadius:fn.cornerRadius];
// this one is different in that in order to keep the mass the
// original amount, I calculated a density and apply that to the area
// of each polygon in order to
shape.mass = poly.area * fn.density;
shape.elasticity = fn.elasticity;
shape.friction = fn.friction;
shape.surfaceVelocity = fn.surfaceVelocity;
shape.sensor = fn.isSensor;
shape.collisionType = fn.collisionType;
shape.collisionGroup = NSClassFromString(fn.collisionGroup);
[shapes addObject:shape];
}
} else if (fn.fixtureType == POLYLINE_FIXTURE) {
for (int i = 0; i < fn.numPolyPoints; i++) {
CGPoint point1 = fn.polyPoints[i];
CGPoint point2 = fn.polyPoints[i+1];
if (i + 1 == fn.numPolyPoints) point2 = fn.polyPoints[0];
CCPhysicsShape* shape = [CCPhysicsShape pillShapeFrom:point1
to:point2
cornerRadius:fn.cornerRadius];
shape.mass = fn.mass;
shape.elasticity = fn.elasticity;
shape.friction = fn.friction;
shape.surfaceVelocity = fn.surfaceVelocity;
shape.sensor = fn.isSensor;
shape.collisionType = fn.collisionType;
shape.collisionGroup = NSClassFromString(fn.collisionGroup);
[shapes addObject:shape];
}
}
}
CCPhysicsBody* body = [CCPhysicsBody bodyWithShapes:shapes];
body.allowsRotation = bn.canRotate;
body.affectedByGravity = bn.obeyGravity;
body.type = (bn.dynamicBody ? CCPhysicsBodyTypeDynamic : CCPhysicsBodyTypeStatic);
return body;
}
-(BOOL)addPhysicsShapesWithFile:(NSString*)plist{
NSString *path = [[NSBundle mainBundle] pathForResource:plist ofType:nil inDirectory:nil];
NSDictionary* dictionary = [NSDictionary dictionaryWithContentsOfFile:path];
if(!dictionary) {
CCLOG(@"Unable to load file: %@", plist);
return FALSE;
}
NSDictionary *metadataDict = [dictionary objectForKey:@"metadata"];
int format = [[metadataDict objectForKey:@"format"] intValue];
if(format != 1) {
CCLOG(@"Format not supported");
return FALSE;
}
float designScale = [[metadataDict objectForKey:@"designScale"] floatValue];
NSDictionary* bodyDict = [dictionary objectForKey:@"bodies"];
for(NSString* bodyName in bodyDict) {
// get the body data
NSDictionary *bData = [bodyDict objectForKey:bodyName];
// create body object
BodyNode* bNode = [[BodyNode alloc] init];
// add the body element to the cache
[_bodyNodes setObject:bNode forKey:bodyName];
// set anchor point
bNode.anchorPoint = CGPointFromString([bData objectForKey:@"anchorpoint"]);
bNode.canRotate = [[bData objectForKey:@"canRotate"] boolValue];
bNode.obeyGravity = [[bData objectForKey:@"obeyGravity"] boolValue];
bNode.dynamicBody = [[bData objectForKey:@"physicsBodyTypeDynamic"] boolValue];
CGSize bodySize = CGSizeFromString([bData objectForKey:@"size"]);
// iterate through the fixtures
NSArray* fixtureList = [bData objectForKey:@"fixtures"];
for(NSDictionary *fData in fixtureList) {
// create fixture
FixtureNode* fNode = [[FixtureNode alloc] init];
if(!fNode) {
return FALSE;
}
// add the fixture to the body
[bNode.fixtures addObject:fNode];
// vitals for shape
fNode.friction = [[fData objectForKey:@"friction"] floatValue];
fNode.elasticity = [[fData objectForKey:@"elasticity"] floatValue];
fNode.mass = [[fData objectForKey:@"mass"] floatValue];
fNode.surfaceVelocity = CGPointFromString([fData objectForKey:@"surface_velocity"]);
fNode.collisionGroup = [fData objectForKey:@"collision_group"];
fNode.collisionType = [fData objectForKey:@"collision_type"];
fNode.isSensor = [[fData objectForKey:@"isSensor"] boolValue];
fNode.isPolylines = [[fData objectForKey:@"isPolylines"] boolValue];
fNode.cornerRadius = [[fData objectForKey:@"cornerRadius"] intValue];
NSString* fixtureType = [fData objectForKey:@"fixture_type"];
// read polygon fixtures. One concave fixture may consist of several convex polygons
if([fixtureType isEqual:@"POLYGON"] && (!fNode.isPolylines)) {
fNode.fixtureType = POLYGON_FIXTURE;
fNode.area = 0.0f;
NSArray* polygonsArray = [fData objectForKey:@"polygons"];
for(NSArray* polygonArray in polygonsArray) {
Polygon* poly = [[Polygon alloc] init];
if(!poly) {
return FALSE;
}
// add the polygon to the fixture
[fNode.polygons addObject:poly];
// size the pointer to the vertices
poly.numVertices = (int)[polygonArray count];
CGPoint* vertices = poly.vertices = malloc(sizeof(CGPoint) * poly.numVertices);
if(!vertices) {
return FALSE;
}
// iterate through the strings in the array and extract points
int vindex = 0;
for(NSString *pointString in polygonArray) {
CGPoint offset = CGPointFromString(pointString);
vertices[vindex].x = offset.x / designScale + bodySize.width / designScale * bNode.anchorPoint.x;
vertices[vindex].y = offset.y / designScale + bodySize.height / designScale * bNode.anchorPoint.y;
vindex++;
}
poly.area = area(poly.vertices, poly.numVertices);
fNode.area += poly.area;
}
fNode.density = fNode.mass/fNode.area;
} else if([fixtureType isEqual:@"POLYGON"] && (fNode.isPolylines)) {
fNode.fixtureType = POLYLINE_FIXTURE;
NSArray* polyVerts = [fData objectForKey:@"hull"];
// size the pointer to the vertices
int numVertices = (int)[polyVerts count];
CGPoint* points = fNode.polyPoints = malloc(sizeof(CGPoint) * numVertices);
if(!fNode.polyPoints) {
return FALSE;
}
// iterate through the strings in the array and extract points
int vindex = 0;
for (NSString *pointString in polyVerts) {
CGPoint offset = CGPointFromString(pointString);
points[vindex].x = offset.x / designScale + bodySize.width / designScale * bNode.anchorPoint.x;
points[vindex].y = offset.y / designScale + bodySize.height / designScale * bNode.anchorPoint.y;
vindex++;
}
fNode.numPolyPoints = vindex;
} else if([fixtureType isEqual:@"CIRCLE"]) {
fNode.fixtureType = CIRCLE_FIXTURE;
NSDictionary* circleData = [fData objectForKey:@"circle"];
CGPoint center = CGPointFromString([circleData objectForKey:@"position"]);
center.x += bodySize.width / designScale * bNode.anchorPoint.x;
center.y += bodySize.height / designScale * bNode.anchorPoint.y;
fNode.center = center;
fNode.radius = [[circleData objectForKey:@"radius"] floatValue] / designScale;
} else {
// unknown type
assert(0);
}
}
}
return TRUE;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment