Some days ago, I said that November will be an awesome month for the Citrus Engine. So what is coming?
We’re the 2nd, November is already here and something huge is already happened to the CE : a tutorial on gotoandlearn made by Lee Brimelow, an Adobe Game Developer Evangelist! Definitely, an excellent tutorial to get start with the Citrus Engine. There’ll be other tutorials later, a new website and the forum’ll move on Starling website. Most of the future game will use Starling so that’s a good move! We’re not forgetting the 3D part, don’t worry 😉
Everything should be ready for the next Adobe Game Jam in Chicago. So that’s the plan. Now let’s go for a small tutorial.
Someone asked me what is the best way to create a combination of physics objects in CE, like this one. You have to create the Box2D physics bodies and associate arts. Firstly, don’t forget that view can lies, but never physics. For example, on the CE’s demo on the actual website the mill’s arms are one Box2D object (with two bodies) and a single picture.
Example :
The Box2D Alchemy code :
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 | package { import Box2DAS.Collision.Shapes.b2PolygonShape; import Box2DAS.Common.V2; import Box2DAS.Dynamics.Joints.b2RevoluteJoint; import Box2DAS.Dynamics.Joints.b2RevoluteJointDef; import Box2DAS.Dynamics.b2Body; import Box2DAS.Dynamics.b2BodyDef; import Box2DAS.Dynamics.b2Fixture; import Box2DAS.Dynamics.b2FixtureDef; import com.citrusengine.objects.PhysicsObject; public class WindmillArms extends PhysicsObject { private var _hingeBodyDef:b2BodyDef; private var _hingeBody:b2Body; private var _shape2:b2PolygonShape; private var _fixtureDef2:b2FixtureDef; private var _fixture2:b2Fixture; private var _jointDef:b2RevoluteJointDef; private var _joint:b2RevoluteJoint; public function WindmillArms(name:String, params:Object=null) { super(name, params); } override public function destroy():void { _hingeBody.destroy(); _hingeBodyDef.destroy(); _fixtureDef2.destroy(); super.destroy(); } override public function set x(value:Number):void { super.x = value; if (_hingeBody) { var pos:V2 = _hingeBody.GetPosition(); pos.x = _x; _hingeBody.SetTransform(pos, _hingeBody.GetAngle()); } } override public function set y(value:Number):void { super.y = value; if (_hingeBody) { var pos:V2 = _hingeBody.GetPosition(); pos.y = _y; _hingeBody.SetTransform(pos, _hingeBody.GetAngle()); } } override protected function defineBody():void { super.defineBody(); _hingeBodyDef = new b2BodyDef(); _hingeBodyDef.type = b2Body.b2_staticBody; _hingeBodyDef.position.v2 = new V2(_x, _y); } override protected function createBody():void { super.createBody(); _hingeBody = _box2D.world.CreateBody(_hingeBodyDef); _hingeBody.SetUserData(this); } override protected function createShape():void { super.createShape(); _shape2 = new b2PolygonShape(); _shape2.SetAsBox(_width / 2, _height / 2, null, _rotation + (Math.PI / 2)); } override protected function defineFixture():void { super.defineFixture(); _fixtureDef2 = new b2FixtureDef(); _fixtureDef2.shape = _shape2; _fixtureDef2.density = _fixtureDef.density; _fixtureDef2.friction = _fixtureDef.friction; _fixtureDef2.restitution = _fixtureDef.restitution; } override protected function createFixture():void { super.createFixture(); _fixture2 = _body.CreateFixture(_fixtureDef2); } override protected function defineJoint():void { super.defineJoint(); _jointDef = new b2RevoluteJointDef(); _jointDef.bodyA = _hingeBody; _jointDef.bodyB = _body; _jointDef.enableMotor = true; _jointDef.maxMotorTorque = 6 _jointDef.motorSpeed = 0; _jointDef.localAnchorA.v2 = new V2(); _jointDef.localAnchorB.v2 = new V2(); } override protected function createJoint():void { super.createJoint(); _joint = _box2D.world.CreateJoint(_jointDef) as b2RevoluteJoint; _joint.SetUserData(this); } } } |
Easier than it sounds, right? The key is to override defined methods and add your new boddie.
Let’s go back to the first example on Emanuele’s website.
Click here for the web demo.
As usal, all the source code is available on the CE’s GitHub dedicated to examples.
Actually, you see the Box2D debug view, to see arts use the console :
Press tab key, write :
set box2D visible false
And press enter.
There are two ways to achieve it, one is not better than the other, it’s just different. You will certainly prefer one depending what you’re trying to achieve.
Create lots of CE’s object
This is the game state :
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 | package complexbox2dobject{ import com.citrusengine.core.State; import com.citrusengine.objects.platformer.box2d.Platform; import com.citrusengine.physics.box2d.Box2D; /** * @author Aymeric */ public class ComplexBox2DObjectGameState extends State { public function ComplexBox2DObjectGameState() { super(); } override public function initialize():void { super.initialize(); var box2D:Box2D = new Box2D("box2D"); box2D.visible = true; add(box2D); var platTop:Platform = new Platform("platTop", {x:stage.stageWidth / 2, width:stage.stageWidth, y:20}); add(platTop); var chain:Chain; var vecChain:Vector.<Chain> = new Vector.<Chain>(); for (var i:uint = 0; i < 4; ++i) { if (i == 0) chain = new Chain("chain" + i, {x:stage.stageWidth / 2, y:70 + i * 40, width:15, hangTo:platTop, distance:i, view:new ChainGraphic()}); else chain = new Chain("chain" + i, {x:stage.stageWidth / 2, y:70 + i * 40, width:15, hangTo:vecChain[i - 1], distance:i, view:new ChainGraphic()}); vecChain.push(chain); add(chain); } chain = new Chain("chain" + i, {x:2, y:70 + (i + 1) * 40, radius:15, hangTo:vecChain[i - 1], distance:i + 1, last:true, registration:"topLeft", view:new ChainGraphic(15)}); add(chain); vecChain.push(chain); } } } |
Here we create many CE objects, with lots of similar informations. The good thing is all the arts are managed by SpriteView and SpriteArt classes.
The Box2D Chain class :
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 | package complexbox2dobject{ import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.Joints.b2RevoluteJointDef; import com.citrusengine.objects.Box2DPhysicsObject; /** * @author Aymeric */ public class Chain extends Box2DPhysicsObject { public var hangTo:Box2DPhysicsObject; public var distance:uint; public var last:Boolean = false; private var _revoluteJointDef:b2RevoluteJointDef; public function Chain(name:String, params:Object) { super(name, params); } override protected function defineJoint():void { _revoluteJointDef = new b2RevoluteJointDef(); if (last) { _revoluteJointDef.localAnchorA.Set(0, (distance + 75) / _box2D.scale); _revoluteJointDef.localAnchorB.Set(0, 0); _revoluteJointDef.bodyA = hangTo.body; _revoluteJointDef.bodyB = _body; } else _revoluteJointDef.Initialize(hangTo.body, _body, new b2Vec2(275 / _box2D.scale, (70 + distance * 40) / _box2D.scale)); } override protected function createJoint():void { _box2D.world.CreateJoint(_revoluteJointDef); } } } |
Nothing too complex, we just create a joint in the right method.
And finally the ChainGraphic class :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package complexbox2dobject{ import flash.display.Sprite; /** * @author Aymeric */ public class ChainGraphic extends Sprite { public function ChainGraphic(radius:uint = 0) { this.graphics.beginFill(Math.random() * 0xFFFFFF); if (radius != 0) this.graphics.drawCircle(0, 0, radius); else this.graphics.drawRect(0, 0, 15, 30); this.graphics.endFill(); } } } |
That’s it for the first method, the most obvious.
Only one complex Box2D and art classes
For the second method, like the mill’s arms we create several Box2D bodies in one Box2DPhysicsObject class.
The GameState :
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 | package complexbox2dobject{ import com.citrusengine.core.State; import com.citrusengine.objects.platformer.box2d.Platform; import com.citrusengine.physics.box2d.Box2D; /** * @author Aymeric */ public class ComplexBox2DObjectGameState extends State { public function ComplexBox2DObjectGameState() { super(); } override public function initialize():void { super.initialize(); var box2D:Box2D = new Box2D("box2D"); box2D.visible = true; add(box2D); var platTop:Platform = new Platform("platTop", {x:stage.stageWidth / 2, width:stage.stageWidth, y:20}); add(platTop); var ropeChain:RopeChain = new RopeChain("ropeChain", {hangTo:platTop, radius:15, y:150, x:30, view:new RopeChainGraphics(), registration:"topLeft"}); add(ropeChain); } } } |
The object created there is basically a circle hang to the platform top with several chains.
The RopeChain class :
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 | package complexbox2dobject{ import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Collision.Shapes.b2Shape; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.Joints.b2RevoluteJointDef; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2BodyDef; import Box2D.Dynamics.b2FixtureDef; import com.citrusengine.objects.Box2DPhysicsObject; import com.citrusengine.physics.PhysicsCollisionCategories; import flash.geom.Point; /** * @author Aymeric */ public class RopeChain extends Box2DPhysicsObject { public var hangTo:Box2DPhysicsObject; public var numChain:uint = 5; public var widthChain:uint = 15; public var heightChain:uint = 30; public var attach:Point = new Point(275, 60); public var distance:uint = 32; private var _vecBodyDefChain:Vector.<b2BodyDef>; private var _vecBodyChain:Vector.<b2Body>; private var _vecFixtureDefChain:Vector.<b2FixtureDef>; private var _vecRevoluteJointDef:Vector.<b2RevoluteJointDef>; private var _shapeChain:b2Shape; public function RopeChain(name:String, params:Object = null) { super(name, params); } override public function initialize(poolObjectParams:Object = null):void { super.initialize(poolObjectParams); if (view) (view as RopeChainGraphics).init(numChain, widthChain, heightChain); } override public function update(timeDelta:Number):void { super.update(timeDelta); if (view) (view as RopeChainGraphics).update(_vecBodyChain, _box2D.scale); } override protected function defineBody():void { super.defineBody(); _vecBodyDefChain = new Vector.<b2BodyDef>(); var bodyDefChain:b2BodyDef; for (var i:uint = 0; i < numChain; ++i) { bodyDefChain = new b2BodyDef(); bodyDefChain = new b2BodyDef(); bodyDefChain.type = b2Body.b2_dynamicBody; bodyDefChain.position = new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale); bodyDefChain.angle = _rotation; _vecBodyDefChain.push(bodyDefChain); } } override protected function createBody():void { super.createBody(); _vecBodyChain = new Vector.<b2Body>(); var bodyChain:b2Body; for each (var bodyDefChain:b2BodyDef in _vecBodyDefChain) { bodyChain = _box2D.world.CreateBody(bodyDefChain); bodyChain.SetUserData(this); _vecBodyChain.push(bodyChain); } } override protected function createShape():void { super.createShape(); _shapeChain = new b2PolygonShape(); b2PolygonShape(_shapeChain).SetAsBox(widthChain / 2 / _box2D.scale, heightChain / 2 / _box2D.scale); } override protected function defineFixture():void { super.defineFixture(); _vecFixtureDefChain = new Vector.<b2FixtureDef>(); var fixtureDefChain:b2FixtureDef; for (var i:uint = 0; i < numChain; ++i) { fixtureDefChain = new b2FixtureDef(); fixtureDefChain.shape = _shapeChain; fixtureDefChain.density = 1; fixtureDefChain.friction = 0.6; fixtureDefChain.restitution = 0.3; fixtureDefChain.filter.categoryBits = PhysicsCollisionCategories.Get("Level"); fixtureDefChain.filter.maskBits = PhysicsCollisionCategories.GetAll(); _vecFixtureDefChain.push(fixtureDefChain); } } override protected function createFixture():void { super.createFixture(); var i:uint = 0; for each (var fixtureDefChain:b2FixtureDef in _vecFixtureDefChain) { _vecBodyChain[i].CreateFixture(fixtureDefChain); ++i; } } override protected function defineJoint():void { _vecRevoluteJointDef = new Vector.<b2RevoluteJointDef>(); var revoluteJointDef:b2RevoluteJointDef; for (var i:uint = 0; i < numChain; ++i) { revoluteJointDef = new b2RevoluteJointDef(); if (i == 0) revoluteJointDef.Initialize(hangTo.body, _vecBodyChain[i], new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale)); else revoluteJointDef.Initialize(_vecBodyChain[i - 1], _vecBodyChain[i], new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale)); _vecRevoluteJointDef.push(revoluteJointDef); } revoluteJointDef = new b2RevoluteJointDef(); revoluteJointDef.localAnchorA.Set(0, distance / _box2D.scale); revoluteJointDef.localAnchorB.Set(0, 0); revoluteJointDef.bodyA = _vecBodyChain[numChain - 1]; revoluteJointDef.bodyB = _body; _vecRevoluteJointDef.push(revoluteJointDef); } override protected function createJoint():void { for each (var revoluteJointDef:b2RevoluteJointDef in _vecRevoluteJointDef) { _box2D.world.CreateJoint(revoluteJointDef); } } } } |
We create Vectors with all our new chains’ bodies.
And finally the most complex part on this second way, the graphic class :
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 | package complexbox2dobject{ import Box2D.Dynamics.b2Body; import com.citrusengine.math.MathUtils; import flash.display.Sprite; import flash.geom.Point; /** * @author Aymeric */ public class RopeChainGraphics extends Sprite { private var _numChain:uint; private var _vecSprites:Vector.<Sprite>; private var _width:uint; private var _height:uint; public function RopeChainGraphics() { this.graphics.beginFill(0xFF0000); this.graphics.drawCircle(0, 0, 15); this.graphics.endFill(); } public function init(numChain:uint, width:uint, height:uint):void { _numChain = numChain; _width = width; _height = height; _vecSprites = new Vector.<Sprite>(); var sprite:Sprite; for (var i:uint = 0; i < _numChain; ++i) { sprite = new Sprite(); sprite.graphics.beginFill(Math.random() * 0xFFFFFF); sprite.graphics.drawRect(0, 0, _width, _height); sprite.graphics.endFill(); addChild(sprite); _vecSprites.push(sprite); } } public function update(vecBodyChain:Vector.<b2Body>, box2DScale:Number):void { var i:uint = 0; for each (var body:b2Body in vecBodyChain) { _vecSprites[i].x = body.GetPosition().x * box2DScale - this.parent.x - _width * 0.5; _vecSprites[i].y = body.GetPosition().y * box2DScale - this.parent.y - _height * 0.5; MathUtils.RotateAroundExternalPoint(_vecSprites[i], new Point(_width * 0.5 + _vecSprites[i].x, _height * 0.5 + _vecSprites[i].y), body.GetAngle() * 180 / Math.PI - _vecSprites[i].rotation - this.parent.rotation); ++i; } } } } |
Since chains’ graphics aren’t managed by SpriteView and the SpriteArt (only the circle), we have to update them manually! Note that there is a little bug on the rotation, I’m missing something here but can’t find the issue… Damn math knowledge 😀 Anyway it shouldn’t prevent you to use this method!