📜 ⬆️ ⬇️

Developing a game using Box2D in ActionScript 3

Good day, dear reader!
Recently an article was published, which described work with the Box2D engine.
However, the engine on the site was version 2.1a, which, unfortunately, differed from the work provided in the article. Unfortunately, the meager foreign documentation on this version of the engine made me understand myself in many ways. Part of my research I would like to share with you, dear reader.


First of all I would like to talk about the changes from version 2.0.


For the development of a new version, let us analyze an example.
')
First we need to create a new project (I use FlashDevelop) and see something like this:
 package { import flash.display.Sprite; import flash.events.Event; public class Main extends Sprite { public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } public function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); } } } 

Next, we load the 2.1a version of the engine and throw it into the folder with the code of our project.
We import the classes we need:
 import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.*; import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Collision.Shapes.b2CircleShape; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.utils.getTimer; 


Create the variables we need:
 public var PIXELS_TO_METRE:Number = new Number(30); //   .  ,   Box2D     . public var world:b2World; // . public var ball:b2Body; //. public var timer:Number = new Number(0); //      . 


After removing the ADDED_TO_STAGE handler, we create our world:
 world = new b2World(new b2Vec2(0, 10), true); 

The first parameter that accepts an object of type b2Vec2 is gravity in our world. In this case, I have it directed down the y-axis by 10 points, which suits me.
The second parameter is the resolution \ prohibition of sleep for objects in the world.

Next you need to create objects in our world. I decided to create a function that creates a rectangle:
 public function addBox(x:Number, y:Number, width:Number, height:Number, dyn:int = 0):b2Body { var bodyDef:b2BodyDef = new b2BodyDef(); bodyDef.position.Set((x + width / 2) / PIXELS_TO_METRE, (y + height / 2) / PIXELS_TO_METRE); if (dyn == 1) bodyDef.type = b2Body.b2_dynamicBody; var content:Sprite = new Sprite(); content.graphics.beginFill(0x000000); content.graphics.drawRect(0 - width / 2, 0 - height / 2, width, height); bodyDef.userData = content; addChild(bodyDef.userData); var boxShape:b2PolygonShape = new b2PolygonShape(); boxShape.SetAsBox(width / 2 / PIXELS_TO_METRE, height / 2 / PIXELS_TO_METRE); var fixtureDef:b2FixtureDef = new b2FixtureDef(); fixtureDef.shape = boxShape; fixtureDef.density = dyn; var body:b2Body = world.CreateBody(bodyDef); body.CreateFixture(fixtureDef); return(body); } 

The bodyDef object stores the parameters of our rectangle: coordinates, rotation, and contents. Initially, the point of coordinates is the center of our rectangle. Since it is inconvenient for me, I calculated the upper left point, which will be entered as a parameter.
In our project there will be both dynamic (living) rectangles and static ones. We check our parameter (dyn) and set the type, if necessary.
Next, I draw the rectangle itself, its graphic component, starting from the center.
The SetAsBox method on an object of type b2PolygonShape assigns a rectangle to the physical part of the object. As parameters, it also takes meters and starts counting from the middle.
The fixtureDef object is the physical parameters of our rectangle: dynamism, density, elasticity, and a physical model. Such parameters as density and elasticity, I did not use.
Next, we send data about the object in our world.

Next, I created a function to create the ball, the main character of our project:
 public function addBall(x:Number, y:Number, radius:Number):void { var bodyDef:b2BodyDef = new b2BodyDef(); bodyDef.position.Set((x + radius) / PIXELS_TO_METRE, (y + radius) / PIXELS_TO_METRE); bodyDef.type = b2Body.b2_dynamicBody; var content:Sprite = new Sprite(); content.graphics.beginFill(0x000000); content.graphics.drawCircle(0, 0, radius); bodyDef.userData = content; addChild(bodyDef.userData); var circShape:b2CircleShape = new b2CircleShape(radius / PIXELS_TO_METRE); var fixtureDef:b2FixtureDef = new b2FixtureDef(); fixtureDef.shape = circShape; fixtureDef.density = 1; ball = world.CreateBody(bodyDef); ball.CreateFixture(fixtureDef); } 

Creating a ball is extremely similar to creating a rectangle and therefore I will not consider it.

Create a triangle that we will have a ladder:
 public function addTriangle(x:Number, y:Number, size:Number):void { var bodyDef:b2BodyDef = new b2BodyDef(); bodyDef.position.Set(x / PIXELS_TO_METRE, y / PIXELS_TO_METRE); var content:Sprite = new Sprite(); content.graphics.beginFill(0x000000); content.graphics.moveTo(size, 0); content.graphics.lineTo(0, size); content.graphics.lineTo(size, size); bodyDef.userData = content; addChild(bodyDef.userData); var polyDef:b2PolygonShape = new b2PolygonShape(); polyDef.SetAsArray([ new b2Vec2(size / PIXELS_TO_METRE, size / PIXELS_TO_METRE), new b2Vec2(0, size / PIXELS_TO_METRE), new b2Vec2(size / PIXELS_TO_METRE, 0) ]); var fixtureDef:b2FixtureDef = new b2FixtureDef(); fixtureDef.shape = polyDef; fixtureDef.density = 0; var body:b2Body = world.CreateBody(bodyDef); body.CreateFixture(fixtureDef); } 

It's all the same, except for the creation of a physical model. The physical model here is an object of type b2PolygonShape. Since there is no template (like addBox), we ourselves draw a triangle using an array of vectors. Countdown begins from the last element of the array to the first.

Next, we create a function called creteObjects, where we write:
 public function creteObjects():void { addBall(10, 400, 50); addBox(0, 580, 450, 20, 0); addBox(250, 540, 200, 40, 0); addTriangle(210, 540, 40); addBox(610, 540, 190, 60); addBox(-10, 0, 10, 600); addBox(800, 0, 10, 600); var m1:b2Body = addBox(430, 540, 20, 5, 0); var m2:b2Body = addBox(451, 540, 20, 5, 1); var m3:b2Body = addBox(472, 540, 20, 5, 1); var m4:b2Body = addBox(493, 540, 20, 5, 1); var m5:b2Body = addBox(514, 540, 20, 5, 1); var m6:b2Body = addBox(535, 540, 20, 5, 1); var m7:b2Body = addBox(556, 540, 20, 5, 1); var m8:b2Body = addBox(577, 540, 20, 5, 1); var m9:b2Body = addBox(598, 540, 20, 5, 0); } 

The last elements are our future bridge.
Next we need to connect the elements of our bridge.
We write the following to the end of creteObjects:
 var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef(); jointDef.enableLimit = true; jointDef.lowerAngle = 0; jointDef.upperAngle = 0.1; jointDef.Initialize(m1, m2, m1.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m2, m3, m2.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m3, m4, m3.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m4, m5, m4.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m5, m6, m5.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m6, m7, m6.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m7, m8, m7.GetWorldCenter()); world.CreateJoint(jointDef); jointDef.Initialize(m8, m9, m8.GetWorldCenter()); world.CreateJoint(jointDef); 

First, we create the configuration of our bundle to which we apply limits, so as not to shake much.
Further we connect all elements among themselves.

All objects in the world are ready and working. But if you compile the application, we will not see anything good. Why? We have not completed the render.
At the end of the init function, we create the ENTER_FRAME event:
 stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); 

Actually the onEnterFrame function code:
 public function onEnterFrame(e:Event = null):void { world.Step(1 / 30, 10, 10); for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext()){ if (bb.GetUserData() is Sprite) { var sprite:Sprite = bb.GetUserData() as Sprite; sprite.x = bb.GetPosition().x * PIXELS_TO_METRE; sprite.y = bb.GetPosition().y * PIXELS_TO_METRE; sprite.rotation = bb.GetAngle() * (180 / Math.PI); } } } 

Step function updates our world. As a parameter, it takes how often to update objects. In seconds.
Next, we get all the objects in the world and update their position and rotation.

You can test. Everything works fine, not taking into account the fact that we can not control our character. Now fix this.



Add a keystroke handler:
 stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); 

Create an onKeyDown function:
 public function onKeyDown(e:KeyboardEvent):void { if (e.keyCode == 39 && getTimer() - p > 200) { ball.ApplyImpulse(new b2Vec2(10, 0), ball.GetPosition()); p = getTimer(); }else if (e.keyCode == 37 && getTimer() - p > 200) { ball.ApplyImpulse(new b2Vec2( -10, 0), ball.GetPosition()); p = getTimer(); } } 

The ApplyImpulse function applies a push to an object with a given force vector and position.
I also remember keystroke and the following will happen no earlier than 200 milliseconds.

As a result, we get this . Download the source code of the project here .
We briefly discussed the creation of objects in the world of Box2D as standard and its own. They created a bridge with tangles and taught the character to move. Good luck, dear reader!

Source: https://habr.com/ru/post/128529/


All Articles