Box2D JSON loader - b2dJson
Intro
It is often useful to be able to save the state of the Box2D world and reproduce it later, whether for loading a game level, a saved game, tuning the parameters of your simulation, or just simply for debugging. A common method for doing this is to loop through all the bodies, fixtures and joints in the world and write them to a text file.
XML is often the first choice of file format since it's easily loadable in many different languages. Likewise, JSON (javascript object notation) which is in many ways the successor to XML is also handy choice. On this page you will find details on 'b2dJson', a text file dump/load utility for Box2D worlds written in C++ and using JSON for formatting.
Update (Nov 2 2012) The full specification of the JSON format used by b2dJson can be found here: JSON structure
Update (Nov 6 2012) A Javascript version of b2dJson is now available, based on box2dweb. Click here for a demo: Javascript vehicles demo, and here to download a simple example which you can run straight from your hard drive (no webserver needed): b2dJson-box2dweb-testbed.zip. Note that this version only loads scenes from JSON, saving scenes has not been implemented.
Update (Nov 11 2012) A Java version of b2dJson is now available, based on JBox2D. Usage has been kept as close as possible to the C++ version. Look for the "Click here for Java version" links below for details.
Update (Jan 10 2013) Source code has been updated to support custom properties. See the 'Custom properties' section below for details.
Update (Mar 9 2013) Source code is now managed at github. See the 'Source code' section below for details.
Features
The b2djson utility has two main functions. Firstly, it can take a b2World* pointer and convert it to a textual representation. This is essentially just one big string which you can pass around in your program as a string, or write to a file. The second function is the reverse of this - to take one of these strings and create a Box2D b2World from it. Along the way, there are other handy features which are also possible:
- attach names to objects of interest in the world (joint, body, fixture)
- add arbitrary custom properties to objects (body, fixture, joint, world)
- replicate individual parts of the world (body, fixture)
b2dJson uses the lightweight jsoncpp to handle the actual JSON reading and writing in a very stress-free way - many thanks to the jsoncpp devs!
For Java, the org.json implementation of JSON is used.
Basic usage
The b2djson utility is typically used by declaring an instance in local scope and calling functions on this instance. To write a Box2D world to a file, you would do this:
1 2 | b2dJson json; json.writeToFile(myWorld, "myfile.json"); |
1 2 3 4 | Jb2dJson json = new Jb2dJson(); StringBuilder errorMsg = new StringBuilder(); if ( ! json.writeToFile(myWorld, "myfile.json", 4, errorMsg) )//4-space indent System.out.println(errorMsg); |
1 2 3 | string errorMsg; b2dJson json; b2World* myWorld = json.readFromFile("myfile.json", errorMsg); |
1 2 3 | Jb2dJson json = new Jb2dJson(); StringBuilder errorMsg = new StringBuilder(); World world = json.readFromFile("snapshot.json", errorMsg); |
1 2 3 4 5 6 7 8 | b2dJson json; string mystring = json.writeToString(myWorld); //later... string errorMsg; b2dJson json; b2World* myWorld = json.readFromString(mystring, errorMsg); |
1 2 3 4 5 6 7 8 | Jb2dJson json = new Jb2dJson(); String mystring = json.worldToString(myWorld); //later... Jb2dJson json = new Jb2dJson(); StringBuilder errorMsg = new StringBuilder(); World myWorld = json.readFromString(mystring); |
Retrieving objects of interest
If you want to attach names to certain objects of interest in the world, you will need to do that before you create the textual representation. You can attach names to joints, bodies and fixtures. Each object can have only one name. Multiple objects can have the same name. Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 | b2Joint* axleJoint; b2Body* carBody; b2Fixture* rearBumperFixture; b2Fixture* frontBumperFixture; b2dJson json; json.setJointName(axleJoint, "drive axle"); json.setBodyName(carBody, "chassis"); json.setFixtureName(rearBumperFixture, "bumper"); json.setFixtureName(frontBumperFixture, "bumper"); json.writeToFile(myWorld, "myfile.json"); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Joint axleJoint; Body carBody; Fixture rearBumperFixture; Fixture frontBumperFixture; Jb2dJson json = new Jb2dJson(); json.setJointName(axleJoint, "drive axle"); json.setBodyName(carBody, "chassis"); json.setFixtureName(rearBumperFixture, "bumper"); json.setFixtureName(frontBumperFixture, "bumper"); StringBuilder errorMsg = new StringBuilder(); json.writeToFile(myWorld, "myfile.json", 4, errorMsg);//4-space indent |
1 2 3 4 5 6 7 8 9 10 | vector<b2Joint*> axleJoints; vector<b2Body*> carBodies; vector<b2Fixture*> bumperFixtures; b2dJson json; b2World* myWorld = json.readFromFile("myfile.json"); json.getJointsByName("drive axle", axleJoints); json.getBodiesByName("chassis", carBodies); json.getFixturesByName("bumper", bumperFixtures); |
1 2 3 4 5 6 7 | Jb2dJson json = new Jb2dJson(); StringBuilder errorMsg = new StringBuilder(); World myWorld = json.readFromFile("myfile.json", errorMsg); Joint[] axleJoints = json.getJointsByName("drive axle"); Body[] carBodies = json.getBodiesByName("chassis"); Fixture[] bumperFixtures = json.getFixturesByName("bumper"); |
Merging files into the same world
(Added Nov 3 2013, currently for C++ version only) Please use the latest source from github!
Sometimes it's useful to keep things separated in different files, and then load them into the same world. For example you might want to keep your game levels in one file, and load in the player and enemies from other files. You can do this by giving an existing world as a parameter to the readFromFile function, like this:
1 2 3 4 5 6 7 | string errorMsg; b2dJson json1; b2World* world = json1.readFromFile( "level1.json", errorMsg ); b2dJson json2; json2.readFromFile( "bike.json", errorMsg, world ); |
1 2 | b2Fixture* finishLine = json1.getFixtureByName( "finish" ); // use json1 (level) b2Fixture* bikeFrame = json2.getFixtureByName( "frame" ); // use json2 (bike) |
1 2 3 4 5 6 7 8 9 | b2Vec2 delta( 1.2, 3.4 ); // move all bodies by this offset vector<b2Body*> bikeBodies; json2.getAllBodies(bikeBodies); for (int i = 0; i < bikeBodies.size(); i++) { b2Body* body = bikeBodies[i]; body->SetTransform( body->GetPosition() + delta, body->GetAngle() ); } |
Using custom properties
You can add your own named properties to the main items: body, fixture, joint, world. The property types can be int, float, string, b2Vec2 or bool. Here is an example of setting each of these types in a body:
1 2 3 4 5 6 7 8 9 | b2dJson json; json.setCustomInt(myBody, "region", 5); json.setCustomFloat(myBody, "damage", 2.5); json.setCustomString(myBody, "role", "hazard"); json.setCustomVector(myBody, "offset", b2Vec2(2,5)); json.setCustomBool(myBody, "respawn", true); json.writeToFile(myWorld, "myfile.json"); |
1 2 3 4 5 6 7 8 9 10 | Jb2dJson json = new Jb2dJson(); json.setCustomInt(myBody, "region", 5); json.setCustomFloat(myBody, "damage", 2.5); json.setCustomString(myBody, "role", "hazard"); json.setCustomVector(myBody, "offset", new Vec2(2,5)); json.setCustomBool(myBody, "respawn", true); StringBuilder errorMsg = new StringBuilder(); json.writeToFile(myWorld, "myfile.json", 4, errorMsg);//4-space indent |
1 2 | vector<b2Body*> enemies; json.getBodiesByCustomString("category", "enemy", enemies); |
1 2 | Vector<Body> enemies = new Vector<Body>(); json.getBodiesByCustomString("category", "enemy", enemies); |
1 | b2Fixture* f = json.getFixtureByCustomInt("damage", 5); |
1 | Fixture f = json.getFixtureByCustomInt("damage", 5); |
1 2 3 4 5 6 7 | bool hasRegion = json.hasCustomInt(myBody, "region"); //returns zero if the property does not exist float damage = json.getCustomFloat(myBody, "damage"); //returns 25 if the property does not exist float health = json.getCustomFloat(myBody, "health", 25); |
1 2 3 4 | boolean hasRegion = json.hasCustomInt(myBody, "region"); //returns 25 if the property does not exist float health = json.getCustomFloat(myBody, "health", 25); |
If no default value is given and the property does not exist, the default value will be zero,
b2Vec2(0,0), empty string, or false (whichever is applicable to the property type being requested).
Replicating objects using JSON
As well as saving and loading an entire world you can also make use of the 'piecewise' functions that b2dJson itself uses, to copy and replicate individual parts of the world. Specifically, you can copy bodies and fixtures. Here is an example of copying a body:
1 2 3 4 5 6 7 8 9 | b2Body* carBody; b2dJson json; Json::Value bodyValue = json.b2j( carBody ); //later... b2World* myWorld; b2dJson json; b2Body* body = json.j2b2Body(myWorld, bodyValue); |
1 2 3 4 5 6 7 8 9 | Body carBody; Jb2dJson json = new Jb2dJson(); JSONObject bodyValue = json.b2j( carBody ); //later... World myWorld; Jb2dJson json = new Jb2dJson(); Body body = json.j2b2Body(myWorld, bodyValue); |
1 2 3 4 5 6 7 8 9 | b2Fixture* bumperFixture; b2dJson json; Json::Value fixtureValue = json.b2j( bumperFixture ); //later... b2Body* carBody; b2dJson json; b2Fixture* myFixture = json.j2b2Fixture(carBody, fixtureValue); |
1 2 3 4 5 6 7 8 9 | Fixture bumperFixture; Jb2dJson json = new Jb2dJson(); JSONObject fixtureValue = json.b2j( bumperFixture ); //later... Body carBody; Jb2dJson json = new Jb2dJson(); Fixture myFixture = json.j2b2Fixture(carBody, fixtureValue); |
Custom properties will also be duplicated by this method.
Other details
Although the files produced by this utility are text files and are somewhat human-readable and occasionally it can be handy to delve in there and cut bits out, the intention is not to edit these files manually.
By default, floating point values are written to JSON as their hex string representation to perfectly preserve the floating point value, as opposed to writing an ascii representation which can be rounded off and must be parsed back into a float when loading. If you do want to edit the files manually, you can set the b2dJson to use human readable values by passing true to the constructor like this:
1 2 | b2dJson json(true); //enable human-readable floats json.writeToFile(myWorld, "myfile.json"); |
Java version
The Java version does not support non-human-readable floats, so all floating point numbers will be their ascii representation.
Source code
You can find the source code for all versions of b2dJson here: http://github.com/iforce2d/b2dJsonC++ version
The source code will work for v2.3.0 of Box2D.
You can also find the supporting jsoncpp source at sourceforge separately.
Java version
The source code will work for v2.2.1 of JBox2D.
You can find the supporting implementation of JSON for Java at org.json.
Binary download
You can download a windows binary of the testbed including the b2dJson utility. This includes some neat test scenes using b2dJson to load JSON data, and you can use Ctrl+S and Ctrl+L to save and load a 'snapshot' of the current world in any of the regular tests, including your own. Just bear in mind that the filename 'snapshot.json' is hardcoded so if you want to save different scenes, you'll have to rename the file.
Each of the demos loads JSON data, and some of them do a little more, for example retrieving named objects or replicating existing bodies. See the source code of each test for comments on these methods.
Java version
You can download a compiled JBox2D testbed which loads the vehicles tests here: Jb2dJson-JBox2D-compiled.zip.
View YouTube video