Some months ago, I bought two softwares from code ‘n’ web : TexturePacker to create SpriteSheet and PhysicsEditor to create easily box2d object from png file.
Today, I’m happy to share my first extension / template for PhysicsEditor’s software : I’ve created a template to export physics object for the Citrus Engine.
This is a quick usage example.
Click on the physical object and press tab. The console is now open. Then write : set Box2D visible false and you will see its picture. It matches really well to the physical object!
When I worked on Kinessia, I was sad that I couldn’t have physical object that I desired e.g. the catapulte which was a simple rectangle.
To create complex box2d physical object you need to define vertices which are quite complex, and determine convex / concave polygons. Actually PhysicsEditor do that. Thanks to its other templates (there is already a template for AS3 box2d but it is not based on the alchemy version), I have been able to create my template.
It is really easy to use an object created from my PhysicsEditor’s template in your code. In your State class just do :
1 2 | var pe:PhysicsEditorObjects = new PhysicsEditorObjects("Pe", {x:200, peObject:"muffin", view:"muffin.png", registration:"topLeft"}); add(pe); |
Be careful, the registration point is topLeft instead of center!
To use my template, download the PhysicsEditor and install it. You can use the free version. Then on Mac OS X right click on the application and go “inside”. Finally paste my template in this folder : Contents/Resources/exporters. I think it isn’t too different on Windows.
The software looks like this :
This is my exporter.xml template file, you may want to add some options (take a look on the other templates) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | <exporter> <!-- identifier of the exporter --> <name>box2d-alchemy</name> <!-- name for the dropbox --> <displayName>CitrusEngine ActionScript (FLASH)</displayName> <!-- description of the exporter --> <description>CitrusEngine ActionScript for Flash</description> <!-- exporter version --> <version>1.0</version> <!-- direction of the y axis: up / down --> <yAxisDirection>down</yAxisDirection> <!-- physics engine to use: box2d, chipmunk --> <physicsEngine>box2d</physicsEngine> <!-- name of the template file --> <template>citrusengine.as</template> <!-- file exension for the data file --> <fileExtension>as</fileExtension> <!-- anchor point settings --> <anchorPoint> <!-- are anchor points supported ? yes/no --> <enabled>no</enabled> <!-- relative position of the default anchor point --> <relX>0.0</relX> <relY>0.0</relY> </anchorPoint> <origin> <!-- origin (0/0) point of the polygons can be set to "anchorPoint" or "fixed" Use <relX>0.5</relX> <relY>0.5</relY> to specify a fixed origin. Default is 0.5/0.5 --> <type>fixed</type> <relX>0.0</relX> <relY>1.0</relY> </origin> <!-- custom global parameters --> <global> </global> <!-- custom body parameters --> <body> </body> <!-- custom fixture parameters --> <fixture> <parameter> <name>density</name> <displayName>Density</displayName> <description>Density of the shape. Used for calculating the mass.</description> <shortDescription>Density of the shape. Used for calculating the mass.</shortDescription> <type>float</type> <default>1</default> </parameter> <parameter> <name>friction</name> <description>Fricion.</description> <shortDescription>Friction.</shortDescription> <displayName>Friction</displayName> <type>float</type> <default>0.6</default> </parameter> <parameter> <name>restitution</name> <displayName>Restitution</displayName> <description>Restitution defines how much a shape bounces.</description> <shortDescription>Density of the shape. Used for calculating the mass.</shortDescription> <type>float</type> <default>0.3</default> </parameter> </fixture> </exporter> |
The ActionScript template :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | package { import Box2DAS.Collision.Shapes.b2PolygonShape; import Box2DAS.Common.V2; import com.citrusengine.objects.PhysicsObject; /** * @author Aymeric * <p>This is a class created by the software http://www.physicseditor.de/</p> * <p>Just select the CitrusEngine template, upload your png picture, set polygons and export.</p> * <p>Be careful, the registration point is topLeft !</p> * @param peObject : the name of the png file */ public class PhysicsEditorObjects extends PhysicsObject { [Inspectable(defaultValue="")] public var peObject:String = ""; private var _tab:Array; public function PhysicsEditorObjects(name:String, params:Object = null) { super(name, params); } override public function destroy():void { super.destroy(); } override public function update(timeDelta:Number):void { super.update(timeDelta); } override protected function defineFixture():void { super.defineFixture(); _createVertices(); _fixtureDef.density = _getDensity(); _fixtureDef.friction = _getFriction(); _fixtureDef.restitution = _getRestitution(); for (var i:uint = 0; i < _tab.length; ++i) { var polygonShape:b2PolygonShape = new b2PolygonShape(); polygonShape.Set(_tab[i]); _fixtureDef.shape = polygonShape; body.CreateFixture(_fixtureDef); } } protected function _createVertices():void { _tab = []; var vertices:Vector.<V2> = new Vector.<V2>(); switch (peObject) { {% for body in bodies %} case "{{body.name}}": {% for fixture in body.fixtures %}{% for polygon in fixture.polygons %} {% for point in polygon %}vertices.push(new V2({{point.x}}/_box2D.scale, {{point.y}}/_box2D.scale)); {% endfor %} _tab.push(vertices);{% if not forloop.last %} vertices = new Vector.<V2>();{% endif %} {% endfor %}{% endfor %} break; {% endfor %} } } protected function _getDensity():Number { switch (peObject) { {% for body in bodies %} case "{{body.name}}": {% for fixture in body.fixtures %}return {{fixture.density}};{% endfor %} break; {% endfor %} } return 1; } protected function _getFriction():Number { switch (peObject) { {% for body in bodies %} case "{{body.name}}": {% for fixture in body.fixtures %}return {{fixture.friction}};{% endfor %} break; {% endfor %} } return 0.6; } protected function _getRestitution():Number { switch (peObject) { {% for body in bodies %} case "{{body.name}}": {% for fixture in body.fixtures %}return {{fixture.restitution}};{% endfor %} break; {% endfor %} } return 0.3; } } } |
And finally the AS3 export file which contains a muffin and a icecream :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | package { import Box2DAS.Collision.Shapes.b2PolygonShape; import Box2DAS.Common.V2; import com.citrusengine.objects.PhysicsObject; /** * @author Aymeric * <p>This is a class created by the software http://www.physicseditor.de/</p> * <p>Just select the CitrusEngine template, upload your png picture, set polygons and export.</p> * <p>Be careful, the registration point is topLeft !</p> * @param peObject : the name of the png file */ public class PhysicsEditorObjects extends PhysicsObject { [Inspectable(defaultValue="")] public var peObject:String = ""; private var _tab:Array; public function PhysicsEditorObjects(name:String, params:Object = null) { super(name, params); } override public function destroy():void { super.destroy(); } override public function update(timeDelta:Number):void { super.update(timeDelta); } override protected function defineFixture():void { super.defineFixture(); _createVertices(); _fixtureDef.density = _getDensity(); _fixtureDef.friction = _getFriction(); _fixtureDef.restitution = _getRestitution(); for (var i:uint = 0; i < _tab.length; ++i) { var polygonShape:b2PolygonShape = new b2PolygonShape(); polygonShape.Set(_tab[i]); _fixtureDef.shape = polygonShape; body.CreateFixture(_fixtureDef); } } protected function _createVertices():void { _tab = []; var vertices:Vector.<V2> = new Vector.<V2>(); switch (peObject) { case "icecream": vertices.push(new V2(30.5/_box2D.scale, 45.5/_box2D.scale)); vertices.push(new V2(97.5/_box2D.scale, 246.5/_box2D.scale)); vertices.push(new V2(93.5/_box2D.scale, 270.5/_box2D.scale)); vertices.push(new V2(62.5/_box2D.scale, 302.5/_box2D.scale)); vertices.push(new V2(21.5/_box2D.scale, 126.5/_box2D.scale)); vertices.push(new V2(16.5/_box2D.scale, 62.5/_box2D.scale)); vertices.push(new V2(18.5/_box2D.scale, 50.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(62.5/_box2D.scale, 302.5/_box2D.scale)); vertices.push(new V2(93.5/_box2D.scale, 270.5/_box2D.scale)); vertices.push(new V2(81.5/_box2D.scale, 306.5/_box2D.scale)); vertices.push(new V2(73.5/_box2D.scale, 312.5/_box2D.scale)); vertices.push(new V2(65.5/_box2D.scale, 310.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(9.5/_box2D.scale, 111.5/_box2D.scale)); vertices.push(new V2(16.5/_box2D.scale, 62.5/_box2D.scale)); vertices.push(new V2(21.5/_box2D.scale, 126.5/_box2D.scale)); vertices.push(new V2(10.5/_box2D.scale, 121.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(44.5/_box2D.scale, 32.5/_box2D.scale)); vertices.push(new V2(135.5/_box2D.scale, 65.5/_box2D.scale)); vertices.push(new V2(144.5/_box2D.scale, 115.5/_box2D.scale)); vertices.push(new V2(135.5/_box2D.scale, 120.5/_box2D.scale)); vertices.push(new V2(30.5/_box2D.scale, 45.5/_box2D.scale)); vertices.push(new V2(28.5/_box2D.scale, 37.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(0.5/_box2D.scale, 81.5/_box2D.scale)); vertices.push(new V2(6.5/_box2D.scale, 69.5/_box2D.scale)); vertices.push(new V2(16.5/_box2D.scale, 62.5/_box2D.scale)); vertices.push(new V2(9.5/_box2D.scale, 111.5/_box2D.scale)); vertices.push(new V2(-0.5/_box2D.scale, 97.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(82.5/_box2D.scale, 3.5/_box2D.scale)); vertices.push(new V2(127.5/_box2D.scale, 42.5/_box2D.scale)); vertices.push(new V2(135.5/_box2D.scale, 65.5/_box2D.scale)); vertices.push(new V2(44.5/_box2D.scale, 32.5/_box2D.scale)); vertices.push(new V2(51.5/_box2D.scale, 14.5/_box2D.scale)); vertices.push(new V2(70.5/_box2D.scale, -0.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(151.5/_box2D.scale, 86.5/_box2D.scale)); vertices.push(new V2(144.5/_box2D.scale, 115.5/_box2D.scale)); vertices.push(new V2(135.5/_box2D.scale, 65.5/_box2D.scale)); vertices.push(new V2(146.5/_box2D.scale, 73.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(129.5/_box2D.scale, 129.5/_box2D.scale)); vertices.push(new V2(30.5/_box2D.scale, 45.5/_box2D.scale)); vertices.push(new V2(135.5/_box2D.scale, 120.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(101.5/_box2D.scale, 241.5/_box2D.scale)); vertices.push(new V2(97.5/_box2D.scale, 246.5/_box2D.scale)); vertices.push(new V2(30.5/_box2D.scale, 45.5/_box2D.scale)); vertices.push(new V2(129.5/_box2D.scale, 129.5/_box2D.scale)); _tab.push(vertices); break; case "muffin": vertices.push(new V2(195.5/_box2D.scale, 166.5/_box2D.scale)); vertices.push(new V2(188.5/_box2D.scale, 128.5/_box2D.scale)); vertices.push(new V2(199.5/_box2D.scale, 158.5/_box2D.scale)); vertices.push(new V2(199.5/_box2D.scale, 164.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(89.5/_box2D.scale, -0.5/_box2D.scale)); vertices.push(new V2(95.5/_box2D.scale, 8.5/_box2D.scale)); vertices.push(new V2(92.5/_box2D.scale, 14.5/_box2D.scale)); vertices.push(new V2(82.5/_box2D.scale, 3.5/_box2D.scale)); vertices.push(new V2(85.5/_box2D.scale, -0.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(2.5/_box2D.scale, 149.5/_box2D.scale)); vertices.push(new V2(23.5/_box2D.scale, 117.5/_box2D.scale)); vertices.push(new V2(9.5/_box2D.scale, 175.5/_box2D.scale)); vertices.push(new V2(-0.5/_box2D.scale, 161.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(120.5/_box2D.scale, 71.5/_box2D.scale)); vertices.push(new V2(120.5/_box2D.scale, 91.5/_box2D.scale)); vertices.push(new V2(24.5/_box2D.scale, 223.5/_box2D.scale)); vertices.push(new V2(18.5/_box2D.scale, 199.5/_box2D.scale)); vertices.push(new V2(74.5/_box2D.scale, 92.5/_box2D.scale)); vertices.push(new V2(96.5/_box2D.scale, 56.5/_box2D.scale)); vertices.push(new V2(101.5/_box2D.scale, 56.5/_box2D.scale)); vertices.push(new V2(113.5/_box2D.scale, 61.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(26.5/_box2D.scale, 251.5/_box2D.scale)); vertices.push(new V2(24.5/_box2D.scale, 223.5/_box2D.scale)); vertices.push(new V2(180.5/_box2D.scale, 202.5/_box2D.scale)); vertices.push(new V2(174.5/_box2D.scale, 226.5/_box2D.scale)); vertices.push(new V2(122.5/_box2D.scale, 283.5/_box2D.scale)); vertices.push(new V2(72.5/_box2D.scale, 280.5/_box2D.scale)); vertices.push(new V2(48.5/_box2D.scale, 273.5/_box2D.scale)); vertices.push(new V2(32.5/_box2D.scale, 264.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(156.5/_box2D.scale, 273.5/_box2D.scale)); vertices.push(new V2(122.5/_box2D.scale, 283.5/_box2D.scale)); vertices.push(new V2(174.5/_box2D.scale, 226.5/_box2D.scale)); vertices.push(new V2(172.5/_box2D.scale, 254.5/_box2D.scale)); vertices.push(new V2(168.5/_box2D.scale, 264.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(76.5/_box2D.scale, 66.5/_box2D.scale)); vertices.push(new V2(82.5/_box2D.scale, 60.5/_box2D.scale)); vertices.push(new V2(96.5/_box2D.scale, 56.5/_box2D.scale)); vertices.push(new V2(74.5/_box2D.scale, 92.5/_box2D.scale)); vertices.push(new V2(72.5/_box2D.scale, 76.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(101.5/_box2D.scale, 34.5/_box2D.scale)); vertices.push(new V2(97.5/_box2D.scale, 32.5/_box2D.scale)); vertices.push(new V2(92.5/_box2D.scale, 14.5/_box2D.scale)); vertices.push(new V2(95.5/_box2D.scale, 8.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(23.5/_box2D.scale, 117.5/_box2D.scale)); vertices.push(new V2(34.5/_box2D.scale, 107.5/_box2D.scale)); vertices.push(new V2(54.5/_box2D.scale, 97.5/_box2D.scale)); vertices.push(new V2(74.5/_box2D.scale, 92.5/_box2D.scale)); vertices.push(new V2(18.5/_box2D.scale, 199.5/_box2D.scale)); vertices.push(new V2(9.5/_box2D.scale, 175.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(180.5/_box2D.scale, 202.5/_box2D.scale)); vertices.push(new V2(24.5/_box2D.scale, 223.5/_box2D.scale)); vertices.push(new V2(120.5/_box2D.scale, 91.5/_box2D.scale)); vertices.push(new V2(154.5/_box2D.scale, 100.5/_box2D.scale)); vertices.push(new V2(175.5/_box2D.scale, 114.5/_box2D.scale)); vertices.push(new V2(188.5/_box2D.scale, 128.5/_box2D.scale)); vertices.push(new V2(195.5/_box2D.scale, 166.5/_box2D.scale)); _tab.push(vertices); vertices = new Vector.<V2>(); vertices.push(new V2(101.5/_box2D.scale, 34.5/_box2D.scale)); vertices.push(new V2(101.5/_box2D.scale, 56.5/_box2D.scale)); vertices.push(new V2(96.5/_box2D.scale, 56.5/_box2D.scale)); vertices.push(new V2(97.5/_box2D.scale, 32.5/_box2D.scale)); _tab.push(vertices); break; } } protected function _getDensity():Number { switch (peObject) { case "icecream": return 1; break; case "muffin": return 1; break; } return 1; } protected function _getFriction():Number { switch (peObject) { case "icecream": return 0.6; break; case "muffin": return 0.6; break; } return 0.6; } protected function _getRestitution():Number { switch (peObject) { case "icecream": return 0.3; break; case "muffin": return 0.3; break; } return 0.3; } } } |
There are lots of vertices stuff! Don’t forget, don’t use many vertices if it isn’t really necessary. In my case, I’ve put lots of vertices for the example, but it may be quite good with 11 vertices instead of 40. You have to think about optimization, complex shape are heavy to manage for box2d.
Finally, I would like to thanks Andreas Löw for helping me to start this template and his support on TexturePacker mac version which works really great now with SWF file!
Oh, and I forgot to mention that the CitrusEngine is now totaly free!
PhysicsEditor template zip file.
PhysicsEditor example zip file.
One thing on my todo list is achieved now 🙂
However you may have noticed the Inspectable metadata tag, I haven’t finish yet with this cool engine 😉
Hello,
I was wondering if you could tell me more about the syntax you used for your for…in loops. I’ve never seen it that way before. How does it work? What does it do? This is the code I mean:
switch (peObject) {
{% for body in bodies %}
case “{{body.name}}”:
{% for fixture in body.fixtures %}{% for polygon in fixture.polygons %}
{% for point in polygon %}vertices.push(new V2({{point.x}}/_box2D.scale, {{point.y}}/_box2D.scale));
{% endfor %}
_tab.push(vertices);{% if not forloop.last %}
vertices = new Vector.();{% endif %}
{% endfor %}{% endfor %}
break;
{% endfor %}
}
Hi Owen,
The syntax come from the software. I don’t know if it is related to a specific programming language. Before I started my template, I had looked the original exporter and saw this syntax ; so I tried and it works!
{% for fixture in body.fixtures %}{% endfor %} this for…in loops means that for each box2d fixtures in the body its generate the code between the braces. Same with {% if %}{% endif %}.
{% if not forloop.last %} means if it not the last element of your loop. It was luck for this one, I have seen {% if not forloop.first %} in the exporter’s example, so I tried its opposite.
{{body.name}} comes from the software too, it picks up the png file name, {{point.x}} the vertice x point, etc…
It was a bit hard at first, but finally it works like a charm 😉
Hello there! Just posting to say thanks for sharing your experiments and knowledge.