[PLUGIN] agsbox2d 0.5.0 - realistic physics for 2D worlds

Started by eri0o, Sun 08/09/2019 23:51:42

Previous topic - Next topic

eri0o

agsbox2d  version 0.5.0

Get Latest Release agsbox2d.dll | libagsbox2d.so | libagsbox2d.dylib | GitHub Repo | Demo Windows | Demo Linux



AgsBox2D is a physics plugin for Adventure Game Studio that gives access to the Box2D library created by Erin Catto.
Because I never used Box2D directly before, I tried to make the API similar to Love physics module.



AgsBox2D is still in early development.

In development warning

AgsBox2D is in development. Still, if you want to experiment with it and report your findings, this post should prove itself useful and I appreciate any help in making this plugin work well with AGS.

Usage example

Below we will do a very simple example that creates a ground, and adds a box and a ball. The ball is controlled by keyboard input. The game is supposed 320x200 in this example.

Code: ags
// room script file
World* world;
Overlay* ov;

struct Physical {
  Body* body;
  Shape* shape;
  Fixture* fixture;
};

Physical ground;
Physical ball;

function room_Load()
{
  if(world == null){
    AgsBox2D.SetMeter(32.0);
    world = AgsBox2D.CreateWorld(0.0, 9.8*AgsBox2D.GetMeter());

    ground.body = AgsBox2D.CreateBody(world, 160.0, 160.0, eBodyStatic);
    ground.shape = AgsBox2D.CreateRectangleShape(320.0, 40.0);
    ground.fixture = AgsBox2D.CreateFixture(ground.body, ground.shape);

    ball.body = AgsBox2D.CreateBody(world, 160.0, 40.0, eBodyDynamic);
    ball.shape = AgsBox2D.CreateCircleShape(20.0);
    ball.fixture = AgsBox2D.CreateFixture(ball.body, ball.shape, 1.0);
    ball.fixture.Restitution = 0.5;

    AgsBox2D.CreateFixture(AgsBox2D.CreateBody(world, 80.0, 60.0, eBodyDynamic),
                           AgsBox2D.CreateRectangleShape(30.0, 20.0), 5.0);
  }
}

function room_RepExec()
{
  if(IsKeyPressed(eKeyLeftArrow)) ball.body.ApplyForce(-500.0, 0.0);
  if(IsKeyPressed(eKeyRightArrow)) ball.body.ApplyForce(500.0, 0.0);
  if(IsKeyPressed(eKeyUpArrow) && ball.body.IsTouching(ground.body)){
    ball.body.ApplyForce(0.0, -6000.0);
    ball.body.SetLinearVelocity(0.0, 0.0);
  }

  if(ov!=null && ov.Valid) ov.Remove();
  ov = Overlay.CreateGraphical(0, 0, world.GetDebugSprite(), true);

  world.Step(1.0/IntToFloat(GetGameSpeed()), 8, 3);
}


There's a breakdown of this code here.

Script API

Spoiler
AgsBox2D


  • void AgsBox2D.SetMeter(float meter)
    Sets how many pixels equals to a meter. Default is 32 pixels per meter.

    Do this only once, before using any other functions, it doesn't apply retroactively

    You want the size of your moving objects roughly between 0.1 and 10 meters.

    For the default 32px this enables objects between 3 and 320 pixels, so this usually needs to scale along with your game resolution and character size.

    Internally, Box2D uses Meter, Kilograms and Seconds as it's main units.
  • float AgsBox2D.GetMeter()
    Get's previously passed meter in pixels.

  • World* AgsBox2D.CreateWorld(float gravityX, float gravityY)
    Creates a World object, this should be done before creating bodies.

    A positive gravityY is directed to ground, and a negative gravityY is directed upwards.

    Similarly, a positive gravityX is directed to right and a negative gravityX is directed to left.

  • Body* AgsBox2D.CreateBody(World* world,  float x, float y, BodyType bodytype)
    Creates a body object in the world, at specified x and y positions.
    These positions correspond to the center of the body.

    The bodytype can be any of the types below:

    - eBodyStatic : An object that does not move under simulation, usually the
    ground in a platformer is a static body. It doesn't collide with other static
    or kinematic bodies. A static body has zero velocity.

    - eBodyDynamic : A fully simulated body, can collide with all body type,
    this body moves according to forces. It always has finite non-zero mass.

    - eBodyKinematic : A kinematic body moves according it's velocity, it doesn't
    move according to forces. A Kinematic body behaves as if it has infinite mass.
    It doesn't collide with other Kinematic bodies or with static bodies.

    Any bodytype can be moved by user input, but you have to specific code the behavior in AGS.

    You don't need to keep the returned pointer if you aren't going to need to access this body anymore, since the world will hold it, but you will be unable to destroy it unless the world is destroyed.

    The specifics on a body form and mass are defined by using a Shape and Fixture.

  • void AgsBox2D.DestroyBody(World* world,  Body* body)
    Removes a body from the world, and marks it with the property IsDestroyed true.

  • Shape* AgsBox2D.CreateRectangleShape(float w,  float h,  float x=0, float y=0)
    Creates a RectangleShape with Width w and Height h, and returns a Shape object.

    You can also change it's relative center which will be mapped to the body center.
    An x of 0.0 and y of 0.0, which are defaults, maps to the shape center.

  • Shape* AgsBox2D.CreateCircleShape(float radius,  float x=0, float y=0)
    Creates a Circle shape, and similar to RectangleShape, you can also translate it's center.

  • Fixture* AgsBox2D.CreateFixture(Body* body, Shape* shape, float density=0)
    Creates a Fixture, and attachs a body a shape, and specifies a density.

    You should always pass finite non-zero densities for dynamic bodies.

    You don't need to keep the pointer to the shape attached to a body through a fixture, since the body will hold a copy of the shape.
    Similarly, you also don't need to keep a pointer to the fixture, because the body will hold it too.

  • Joint* AgsBox2D.CreateDistanceJoint(Body* bodyA, Body* bodyB, float a_x, float a_y, float b_x, float b_y, bool collideConnected = 0)

    Create Distance Joint, pass anchors on bodies A and B using world coordinates. The two bodies are assumed to be in place when this joint is created.

    This joint constrains the distance between two points on two bodies to be constant. The first anchor point is connected to the first body and the second to the second body, and the points define the length of the distance joint.

  • Joint* AgsBox2D.CreateMotorJoint(Body* bodyA, Body* bodyB, float correction_factor,  bool collideConnected = 0)

    Create Motor Joint. This is a joint between two bodies which controls the relative motion between them.

    Position and rotation offsets can be specified once the MotorJoint has been created, as well as the maximum motor force and torque that will be be applied to reach the target offsets.

  • Joint* AgsBox2D.CreateMouseJoint(Body* bodyA, float x, float y)

    Create Mouse Joint between body and a target point in the world. To make it follow the mouse, the fixed point must be updated every time-step.

    The advantage of using a MouseJoint instead of just changing a body position directly is that collisions and reactions to other joints are handled by the physics engine.

  • Joint* AgsBox2D.CreatePulleyJoint(Body* bodyA, Body* bodyB, PointF* groundAnchorA, PointF* groundAnchorB, PointF* localAnchorA, PointF* localAnchorB, float ratio, bool collideConnected = 0)

    Creates a PulleyJoint to join two bodies to each other and the ground.

    The pulley joint simulates a pulley with an optional block and tackle. If the ratio parameter has a value different from one, then the simulated rope extends faster on one side than the other. In a pulley joint the total length of the simulated rope is the constant length1 + ratio * length2, which is set when the pulley joint is created.

    Pulley joints can behave unpredictably if one side is fully extended. It is recommended that the method setMaxLengths  be used to constrain the maximum lengths each side can attain.

  • void AgsBox2D.DestroyJoint(World* world,  Joint* body)

    Removes a joint from the world, it should no longer return true to isValid.



PointF


  • PointF* PointF.Create(float x, float y)
    Creates a PointF object with x and y values.

  • float PointF.X
    The X coordinate property of a PointF.

  • float PointF.Y
    The Y coordinate property of a PointF.

  • float PointF.Length()
    Returns distance from point (X,Y) coordinates to origin (0,0).

  • float PointF.SquaredLength()
    Returns  squared distance from point (X,Y) coordinates to origin (0,0). Slightly faster than Length.

  • PointF* PointF.Add(PointF* pointF)
    Returns a new point with the sum of this with pointF.

  • PointF* PointF.Sub(PointF* pointF)
    Returns a new point with the subtraction of pointF from this.

  • PointF* PointF.Scale(float scale)
    Returns a new point that is a copy of this point multiplied by a scalar.

  • PointF* PointF.Rotate(float angle, float pivot_x = 0, float pivot_y = 0)
    Returns a new point with this point treated as a vector to a pivot point, rotated by an angle in radians. If you don't specify, pivot is origin (0,0).

  • Point* PointF.ToPoint()
    Rounds this point as integer and returns a standard AGS Point object.


Body


  • int Body.X
    The X position property of a body as integer.

    Avoid setting this property directly. Bodies coordinates are actually float values in the simulation, this is provided as convenience.

  • int Body.Y
    The Y position property of a body as integer.

    Avoid setting this property directly. Bodies coordinates are actually float values in the simulation, this is provided as convenience.

  • float Body.fX
    The X position property of a body as float.

  • float Body.fY
    The Y position property of a body as float.

  • float Body.Angle
    The Body Angle property. AGS can't easily rotate Sprites so avoid using angles with bodies that you expect to map directly in someway to screen sprites.

  • bool Body.FixedRotation
    By default, bodies are created with FixedRotation set to true.

    A body with FixedRotation set to true does not rotate, causing it's rotational inertia and it's inverse to be set to zero.

  • bool Body.Bullet
    By default, bodies are created with Bullet set to false.

    Set bullet to true when the body has a small shape and moves really fast, this will prevent the body from having wrong collisions with thin bodies.

  • readonly bool Body.IsDestroyed
    Returns true if it's destroyed by AgsBox2D.DestroyBody().

  • float Body.LinearDamping
    The LinearDamping property of a body. Damping occurs independently from contact and is different than friction.
    Normally  the value for damping is between 0.0 and 0.1.

  • float Body.AngularDamping
    The AngularDamping property of a body, the angular drag, also happens
    independently from contact.

  • float Body.AngularVelocity
    The AngularVelocity property of a body.

  • float Body.Inertia
    Rotational Inertia, body's resistance to changes in angular velocity.

  • readonly float Body.LinearVelocityX
    Gets the X vector from the body's Linear Velocity.

  • readonly float Body.LinearVelocityY
    Gets the Y vector from the body's Linear Velocity.

  • void Body.SetLinearVelocity(float fx, float fy)
    Set's the body LinearVelocity vector.

  • void Body.ApplyForce(float fx, float fy)
    Applies a force on a body from it's center 0.0, 0.0 to the specified fx, fy
    direction.

  • void Body.ApplyAngularImpulse(float impulseIntensity)
    Applies an angular impulse on the body.

  • void Body.ApplyLinearImpulse(float intensity_x, float intensity_y)
    Applies an impulse from the body center with the specified vector.

  • void Body.ApplyTorque(float torque)
    Applies a torque on the body. Positive values are counter clockwise.

  • bool Body.IsTouching(Body* otherBody)
    Returns true when a body is in contact (being touched) by other body.
    This function only evaluates at the current time, so prefer using it for resting states.

World

The world holds all the information needed for the physics simulation.
Once a world is destroyed, the previous pointers (Bodies, Fixtures, ...) will be of no use and you will need to recreate any objects you need in the new world.


  • void World.Step(float dt, int velocityIteractions = 8, int positionIteractions = 3)
    Advances a step in the World physics simulation of dt seconds.

    Because AGS uses fixed game steps, a good value is  dt = 1.0/IntToFloat(GetGameSpeed()).

    velocityIteractions and positionIteractions relates to how Box2D simulates the world, so for information on these values I recommend looking into Box2D own documentation.

  • int World.GetDebugSprite(int camera_x = 0, int camera_y = 0)
    Returns a sprite of the size of the screen with the objects in the world drawn on it.

    A common usage is to create a GUI of the size of the screen and set the background graphic of it with the sprite this function outputs. Set this GUI transparency between 1 and 99.

    You can pass a camera x and y value to scroll the camera on the world.

  • int FixtureArray*  World.BoundingBoxQuery(float lower_x, float lower_y, float upper_x, float upper_y)
    Returns array of fixtures which their bounding boxes are overlapped by the supplied box.

    A fixture bounding box is the non rotated smallest rectangle that contains it's shape, this means a rotate rectangle or a circle, a empty area is part of the bounding box.
    This is usually good enough for a first stage of a detection, but may require additional steps.

  • int RaycastResult* World.Raycast(float x0, float y0, float x1, float y1, RaycastType rc_type = 0, FixtureArray* stopping_fixtures = 0)
    Returns RaycastResult with fixtures hit by a line, along with the hit normals.
    The raycast goes through all fixtures on the line if you supply eRaycastPassthrough (this is the default).

    You can use eRaycastUntilHit for it to stop at the first fixture hit, or additionally supply an array of target fixtures so that the raycast only stops if hit any fixture on the array.

  • readonly int World.ContactCount
    How many contacts are available. Use it to know the range to access World.Contacts[].

  • readonly Contact* World.Contacts[]
    Gets the contacts in the world by index. These only contain fixtures in contact right now.


Shape



  • ShapeRectangle* Shape.AsRectangle
    If the shape is a ShapeRectangle, it returns it. Otherwise, it returns null.
    You should not hold pointers to it, and instead access it directly like myshape.AsRectangle.Width as needed.

  • ShapeCircle* Shape.AsCircle
    If the shape is a ShapeCircle, it returns it. Otherwise, it returns null.
    You should not hold pointers to it, and instead access it directly like myshape.AsCircle.Radius as needed.


Fixture
Fixtures are used when linking a shape to an object and assigning it's density.



  • float Fixture.Density
    Density is used to compute the mass of the linked body. It's preferable to use similar densities to all your fixtures, because this will improve the simulation.

  • float Fixture.Friction
    Friction is used to make objects slide along each other realistically.

    It's usually a value between 0.0 and 1.0, but can be any non-negative value.

    Box2D uses the square root of the multiplication of two contacting fixtures to calculate the contact friction. This means if one fixture has 0.0 friction, the contact will have no friction.

  • float Fixture.Restitution
    Restitution is used to make objects bounce, and is usually a value between 0.0 and 1.0. A value of 0.0 means the object won't bounce, and a value of 1.0 means the object velocity will be exactly reflected.

  • readonly Body* Fixture.Body
    Returns Body if it's defined for this fixture, otherwise null.

  • int Fixture.GroupIndex
    Group the fixture belongs to, from -32768 to 32767. Fixtures with the same group will always collide if group is positive or never collide if it's negative.
    Zero means no group, and is default.

  • int Fixture.CategoryBits
    Category of this fixture, from 16 possible categories encoded as 16-bit integer (1, 2, 4, 8, ... 32768). 65535 means all categories.

  • int Fixture.MaskBits
    Mask of this fixture, encoded as 16-bit integer. Categories selected will collide with this fixture (ex: 5, means category 1 and 4 will collide). Default is 65535 - collide with all categories.

  • bool Fixture.TestPoint(float x, float y)`
    Returns true if a point is inside the shape of the fixture.

  • bool Fixture.IsSensor
    Whether this fixture is a sensor. Sensors do not cause collision responses, but generate begin-contact and end-contact events.



Joint
Joints are used to link a body to another to create relative movements.



  • JointDistance* Joint.AsDistance
    If this joint is a distance joint, returns the JointDistance interface; otherwise null.

  • JointMotor* Joint.AsMotor
    If this joint is a motor joint, returns the JointMotor interface; otherwise null.

  • JointMouse* Joint.AsMouse
    If this joint is a mouse joint, returns the JointMouse interface; otherwise null.

  • JointPulley* Joint.AsPulley
    If this joint is a pulley joint, returns the JointPulley interface; otherwise null.

  • bool Joint.IsValid
    If this joint is valid, returns true.

  • bool Joint.IsActive
    If this joint is active, returns true.

  • Body* Joint.BodyA
    Returns Body A if it's defined, otherwise null, for this joint.

  • Body* Joint.BodyB
    Returns Body B if it's defined, otherwise null, for this joint.

  • JointType Joint.Type
    Returns this joint type.


JointDistance



  • float JointDistance.Length
    The equilibrium distance between the two Bodies.

  • float JointDistance.DampingRatio
    The damping ratio, typically between 0 and 1. At 1, the damping is critical.

  • float JointDistance.Frequency
    The frequency of a harmonic oscillator. Should be smaller than half the frame rate.



JointMotor



  • void JointMotor.SetLinearOffset(float fx, float fy)
    Sets the target linear offset between the two bodies the joint is attached to.

  • float JointMotor.LinearOffsetX
    The target linear offset X axis between the two bodies the joint is attached to.

  • float JointMotor.LinearOffsetY
    The target linear offset Y axis between the two bodies the joint is attached to.

  • float JointMotor.AngularOffset
    The target angular offset between the two bodies the joint is attached to.

  • float JointMotor.MaxForce
    The Maximum Force applied to reach target position.

  • float JointMotor.MaxTorque
    The Maximum Torque applied to reach target rotation.



JointMouse



  • void JointMouse.SetTarget(float fx, float fy)
    Sets the target point.

  • float JointMouse.TargetX
    The target point X axis.

  • float JointMouse.TargetY
    The target point Y axis.

  • float JointMouse.DampingRatio
    The damping ratio, typically between 0 and 1. At 1, the damping is critical.

  • float JointMouse.Frequency
    The frequency of a harmonic oscillator. Should be smaller than half the frame rate.

  • float JointMouse.MaxForce
    The Maximum Force applied to reach target position.



JointPulley



  • float JointPulley.LengthA
    The current length of the segment attached to the first body.

  • float JointPulley.LengthB
    The current length of the segment attached to the second body.

  • float JointPulley.Ratio
    The pulley ratio.



Contact
Contact are returned to inform a contact is happening, between two fixtures. They also include additional useful information.



  • readonly bool Contact.IsValid
    Whether the Contact is still valid.

  • readonly PointF* Contact.Normal
    The normal vector between two shapes that are in contact.

  • readonly PointF* Contact.Positions[]
    The contact points of the two colliding fixture, use PositionsCount to find out how many. Index starts at 0.

  • readonly int Contact.PositionsCount
    How many position of contact are available.

  • readonly Fixture* Contact.FixtureA
    One of the Fixtures that hold the shapes in contact.

  • readonly Fixture* Contact.FixtureB
    The other of the Fixtures that hold the shapes in contact.

  • bool Contact.Enabled
    Whether the contact is enabled.

  • float Contact.Restitution
    The restitution between two shapes that are in contact.

  • float Contact.Friction
    The friction between two shapes that are in contact.

[close]

Download AgsBox2D

Spoiler
This plugin is available as agsbox2d.dll under assets, in the latest release, for usage with Windows and the AGS Editor. You can also find it there built for Linux as libagsbox2d.so and for MacOS as libagsbox2d.dylib.
[close]

Building agsbox2d

Spoiler

AgsBox2D both Makefile and the VS Solution file, expects to find Adventure Game Studio source code in a folder ../ags/. After you do this, you need to clone this source code.

Code: ags
  ~/git/ags/
  ~/git/agsbox2d/

Navigate then to the directory where you cloned this repository.

On Windows, you can load the solution on the root of it's directory and load it on Visual Studio. It should work with VS 2015, 2017 and 2019. You will need v140 tools (VS should promptly warn you to install if you don't have it).
The dll provided by Release Win32 builds is the one you should build to use with an AGS Game at the time of this writing.

On Linux and MacOS, navigate to agsbox2d/ inside the directory and type make.
[close]

License and Author

AgsBox2D is made by eri0o provided with Z-Lib LICENSE.

Box2D itself is made by Erin Catto and is provided with a Z-Lib LICENSE too.

Dualnames

Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Mehrdad

My official site: http://www.pershaland.com/

eri0o

#3
Hey, I haven't yet tested (been away from my main computer for some days), but I sketched an idea of a mini editor that could be helpful in generating the code to position walls in a room. You can find it in the link below:

ericoporto.github.io/agsbox2d/

It's ugly, but if someone could try it, the idea is you can put an image to use as base on the background and position vectorial rectangles on top with rotation, then it will generate an AGS function that creates these bodies if you pass the world object.

Probably the best approach would be having a format to describe the world and edit it in a proper editor, but I had this idea above and decided to give it a try.

--+

So, I participated in the one hour jam that happened on Discord on the November's 9th, and the terrible result is below:

Download: eri0o.itch.io/fall-ball

GitHub repo: github.com/ericoporto/my1hour

Video of development, where I use the above hackish level Editor.
Spoiler
[close]

eri0o

Hey! I would like to report a new version has been released with the addition of Joints! I am adding for now Distance Joint, Motor Joint, Mouse Joint and Pulley Joint.



Bug reports are always welcome, if you try the plugin, please leave a message here! The different "levels" of the Demo project can be accessed with CTRL+X and you can download the source on GitHub (both the plugin and the AGS project for the Demo).

Any wishes for more features and stuff on the plugin are cool too.  8-)

Here is me throwing boxes -> https://streamable.com/nlfuj


Mehrdad

My official site: http://www.pershaland.com/

eri0o

Thanks @Mehrdad and @Matti!

If you guys have any questions, fire them and I can try to answer, improve documentation and generally help out with it. :)

Mehrdad

Quote from: eri0o on Mon 25/11/2019 16:43:08

If you guys have any questions, fire them and I can try to answer, improve documentation and generally help out with it. :)

Maybe it's out of this topic but I always wish that I can make a cinematic platformer with AGS. like Another World, Flashback and Prince of Persia game. Concurrence (Made with AGS) is exactly same that want
https://www.adventuregamestudio.co.uk/site/games/game/1428/

It have grab ledge, width jump, turning,...
@eri0o  I have some useful things about this game. If it's interesting for you please let me know to send a surprise as PM. You can send me pm in discord too.
My official site: http://www.pershaland.com/

eri0o

Got Bounding Box queries working! :D  I want to add Raycast queries and some sort of way to listen to contacts yet before a new version, but I thought this was neat. Now I can grab anything! :]



(If anyone need this badly, I provide snapshot builds of the plugin too on GitHub)

@Mehrdad, a cinematic platformer is a different thing, from this plugin, they usually require precision, which I think is hard to do with this level of physics.  Because the physics of Box2D are realistic I think you would spend a ton of time calibrating numbers everywhere, and using a more dedicated manual implementation of a platformer, you would know the distances for the types of jumps available - like, jump 32px, jump when was running 96px.

This plugin is more useful on a general platformer, like Limbo, or Shovel Knight. For ledge grabbing, I think the way to do would be with sensors - I haven't added them yet, but the general idea would be having sensors to detect both the wall and free space for the character to occupy, similar to this method here: https://gamedev.stackexchange.com/questions/40302/how-do-i-detect-ledges . For actually placing the character in the position, in Box2D is a little tricky because ideally you need to do with forces. You can position the character simply, you need to make sure it doesn't interact with anything on the path.

Other things like jump and turning are generally easier to do I think. You would need to build some animation engine to feed the acceleration, position, and other things into it, to be able to change animation states accordingly (is slow, speed is different than 0->go with walking animation; was using walking animation, pressed jump->do slow jump animation; abruptely reduced speed-> feet hitting floor animation ...).

If a module for 2D platformer game was to be made using this Plugin, I kinda don't know, now, what exactly it would need to have, how this module API would be like and such... I have used Box2D before in other engines, so for now, for me, it's easier to just implement it here and let whoever has interest in developing on top of it free to explore. The plugin is open source and I am constantly building for Mac, Windows and Linux, to check I haven't broken something in terrible ways - so I hope it can be used in any other AGS ports and architectures without much fuss.

Mehrdad

@eri0o  Your right. Thanks a lot mate for information, anyway I'll send for you a pm in discord today.
My official site: http://www.pershaland.com/

eri0o

Cool, send me it :)

Tbh, I kinda found one dev making a mix between cinematic and traditional platfomer using Box2D and I entered in contact to see his thoughts, he still hasn't answered me yet, but if he does, I may have some ideas.

My rough plan is making the things needed for Box2D usage, being on par with the physics module from Love2D; and then later evolve to a liquid fun merge. Liquid fun is Box2D fork from Google (that's not very maintained at the moment, that's why I would switch to the merge that IS maintained, including the new features and bugfixes from Box2D I am using). Liquid fun adds particles, as a new entity that can do stuff. The problem is that particles kinda come along with their own way to render them, using OpenGL 2.0 context, so they would exist, but their usage if so desired would be dependent on OpenGL - still, I would rasterize the results so you could have a regular AGS Sprite to retrieve. But this is long in the future and I still have a lot of basic things to solve. My current biggest problem is probably convincing someone to try this so I can understand what are the priorities for ags developers, and what I may be doing wrong.

The advantage of being closer to the original Box2D API is that one could follow one of the many tutorials among the web and adapt for ags usage. :)


eri0o

Thanks @Jack! If you try it, let me know what you think :]

I added Raycast and Bouding Box queries to the World object, and added some math functions to PointF object too, even though they are small things, I released a new version so if anyone wants to play with these, they are available.

lorenzo

I've tried the demo, it's so cool! Probably way too advanced for me, but it was a lot of fun to mess with it. Great work!

eri0o

Hey @lorenzo, I am considering at some point, if no one do it, to create a module to ease using it, but the plug-in I want to keep similar to original API so the online tutorials already existing for Box2D can be useful.  :-D

Do you have suggestions of what can I do to make it more approachable? Like, some expectation you had of how to interact with a physics library in AGS.  :)

Cassiebsg

Wish I can find the time to play with this.
Good work and keep it up.  (nod)
There are those who believe that life here began out there...

lorenzo

Quote from: eri0o on Fri 29/11/2019 11:44:06
Do you have suggestions of what can I do to make it more approachable? Like, some expectation you had of how to interact with a physics library in AGS.  :)
I don't know, yet. I'll experiment a bit with it during the weekend if I can!

eri0o

@Cassiebsg Yey! Can't wait for you to play around with this.  (nod)

@lorenzo No worries, I may have some ideas. If you play around, leave some feedback here.

Also, this is a small update, but now you can test if a point in a fixture is indeed in it's shape. Additionally, a fixture can now be a sensor, which doesn't collide with anything and is kinda useless for now, because it will just fall forever, unless the body it's attached to has a non-sensor fixture or it's hold by a joint.

The other addition is groups, category bits and mask bits, these give you more fine tune control for what collides with what. Suppose you want to blow up an enemy and it drops coins that nicely collide with things on the ground (the scenery), but you don't want other enemies kicking these coins around, well, you can now set the enemies and to belong to a different category, and have the coins have their mask preventing the enemies to collide with them. Below is an example, notice the two balls at left (categories 2 and 4), they don't collide with each other, but they are masked to collide with everything else (mask 65535-4 and mask 65535-2). There are 16 possible categories, and they are represented by powers of 2 (1, 2, 4, 8 ... 32768). A fixture can belong to any number of categories (but at least one). There's a tutorial here that may or may not be useful.


Mehrdad

My official site: http://www.pershaland.com/

lorenzo

I tried the plugin during the weekend, by modifying the code in the demo, trying to make some new objects, etc. It's really impressive!

Your guide in the first post is pretty useful, but it would be nice to have a demo with comments explaining what the code does. I think it could be beneficial for people like me who aren't good with coding. The demo right now is pretty cool and helped me figure out how to use some of the plugin's functions, but a few more comments would've helped!

Anyway, great job! It's fantastic to see physics in AGS.

eri0o

Hey @Lorenzo, thanks for testing it! I recognize I still need to add a better tutorial somewhere. I also created a task for myself to comment all code that I want to tackle soon.

Cheer @Mehrdad, hopefully we get somewhere.

Added a new Room at the game project and added a new Module script in the demo called Boxify.



The idea of the Boxify Module script is, if the object or character requirements in physics is relatively dull, this module simplifies the linking of the objects with the physics world:

Code: ags

function room_Load()
{
  Boxify.Character(player, AgsBox2D.CreateRectangleShape(12.0, 34.0));
  Boxify.Object(obj_box1);
  Boxify.Object(obj_box2);
  Boxify.Object(obj_box3);
  Boxify.Object(obj_box4);
  Boxify.Object(obj_box5);
  Boxify.Object(obj_box6);
  Boxify.Object(obj_barrel1);
}


More details in the code of the Room script...

I still need to figure more things out and I may conclude that making this module is not practical since it's hard to predict usages for elements in the world, so basic idea it implements is it keep tracks which fixtures and bodies have corresponding room objects or characters, and moves them appropriately after we step a bit of time in the world simulation.

lorenzo

That looks so cool, can't wait to try the new demo!

eri0o

@lorenzo, below is the first example commented. I was going to add to the first post, but it exceeded the character limit for the forum. I actually wanted to add a release today, but I encountered a last minute little bug that I am not sure yet how to tackle.

Let's break down this code now. We will create a world, the world is where all the physics simulation happens.
Nothing in Box2D "exists", it just calculates a bunch of numbers that we can use.
To help visualize these numbers, it also offers a debug image, where the shapes and their movements are drawn, as if these numbers had a meaning. We are going to use this debug image to show these shapes moving on the screen.

Code: ags

World* world; // If the world doesn't exist, everything in it is gone as well, se we make this "global" to our room.
Overlay* ov; // The overlay is merely for showing the debug sprite on screen.

struct Physical {  // This struct will help hold important pointers for us.
  Body* body;      // The body is a point in the simulation world
  Shape* shape;    // A shape can have many forms, for now we will use only Rectangles and Circles.
  Fixture* fixture; // A fixtures attaches a shape to a body, enabling the body the ability to collide with other bodies that have shapes attached to them.
};

Physical ground; // We are going to create just a single platform as ground
Physical ball; // And a ball is going to be our "player" here


The above code is just to be able to receive pointers to the things we are going to simulate. Note that the "Physical" struct was arbitrary . We don't actually need to hold pointers to anything but the world, unless we need them later.

Code: ags

function room_Load()
{
   // this check will prevent recreating this world if we enter again in this room later on.
  if(world == null){
    // The line below says in our world, 32 pixels means 1 meter. So, a 1.8 meter character would have 58 pixels in height.
    AgsBox2D.SetMeter(32.0); // The physics simulation is tuned to work best from 0.1 to 10 meters, so in this world, 3 px to 320 px in size are good.
    // NOTE: 32.0 is the default, so if you don't set any value, 32 pixels will be equal to 1 meter.

    world = AgsBox2D.CreateWorld(0.0, 9.8*AgsBox2D.GetMeter()); //We create the world, and set the gravitational acceleration as we wish.
    // Positive y is down in AGS. Also, since we are talking in pixels, acceleration has to be set in pixels per squared seconds.

    ground.body = AgsBox2D.CreateBody(world, 160.0, 160.0, eBodyStatic); // This creates a static body, at the 160 pixels x and 160 pixels y in the world. It's just a point.
    ground.shape = AgsBox2D.CreateRectangleShape(320.0, 40.0); // This creates a rectangle of 320 pixel width and 40 pixels height, but this rectangle doesn't exist anywhere.
    ground.fixture = AgsBox2D.CreateFixture(ground.body, ground.shape); // This copies the rectangle we created, and attaches this copy center point on to the body. Because the body is static, it's mass is infinite.

    ball.body = AgsBox2D.CreateBody(world, 160.0, 40.0, eBodyDynamic); // This creates a dynamic body at the position 160, 40 in the world. It's a point.
    ball.shape = AgsBox2D.CreateCircleShape(20.0); // This creates a circle of 20 pixels radius, but this circle doesn't exist anywhere.
    ball.fixture = AgsBox2D.CreateFixture(ball.body, ball.shape, 1.0); // This copies the circle we created, and attaches this copy center point on to the body, it's density is 1/(32*32) kg per squared pixels.
    ball.fixture.Restitution = 0.5; // This makes the ball receive half of the force it impacted another body, in the oposing direction. This will make the ball bounce.

    // The below will create a box 30 width and 20 height, that is dynamic, with center at 80, 60 position, with a density of 5/(32*32) kg/sq px.
    // We won't do anything with it outside of the physics, so we don't store pointers to it.
    AgsBox2D.CreateFixture(AgsBox2D.CreateBody(world, 80.0, 60.0, eBodyDynamic),
                           AgsBox2D.CreateRectangleShape(30.0, 20.0), 5.0);
  }
}


The above code sets up the world, this is pretty much the most part of what we have to do at first.
OK, let's make the physics happen.

Code: ags

// Remember, for this function to execute you need to link it in the room repeatedly execute event in the Editor
function room_RepExec()
{
  // In AGS coordinates, position increases as we go to the right, so positive values in the x coordinate pushes to the right.
  if(IsKeyPressed(eKeyLeftArrow)) ball.body.ApplyForce(-500.0, 0.0); // We are going to check if left key is pressed, and if it's, apply a force pushing the ball left.
  if(IsKeyPressed(eKeyRightArrow)) ball.body.ApplyForce(500.0, 0.0); // This does the same, but to the right direction.
  if(IsKeyPressed(eKeyUpArrow) && ball.body.IsTouching(ground.body)){ // We want the ball to jump if we press up, but only if it's touching the ground.
    ball.body.ApplyForce(0.0, -6000.0); // A negative vector in y points upwards, remember the positive gravity points downwards?
    ball.body.SetLinearVelocity(0.0, 0.0); // This resets the linear velocity, so that when you press up, the ball initially moves upwards and dismiss previous side momentum it had.
  }

  if(ov!=null && ov.Valid) ov.Remove();  // This is just to reset the overlay at every frame, so we are erasing it.
  ov = Overlay.CreateGraphical(0, 0, world.GetDebugSprite(), true); // The overlay here is used to show the debug sprite, to help us visualize what's happening.

  world.Step(1.0/IntToFloat(GetGameSpeed()), 8, 3); //This advances a time instant in the simulation. Nothing will happen unless the world advances a step. The calculation here yields 16 ms.
  // The value 8 and 3 passed along are used for simulation convergence, if you omit then, they are the default values. You should not change them unless you know what you are doing.
}


With this we have a very simplistic example working. You can explore the examples shipped along.

There is a very very simple wall creator here which you can use to start playing with making a level. I intend to figure out later on how to make an Editor to help using this plugin.

This code is for demonstration purposes, in a game, usually you want at least four walls outside of the screen enclosing your bodies, otherwise once they fall the platform, the simulation will need to calculate the body falling through eternity, which is an extra effort to the simulation for no reason.
If a body falls in a bigger cage outside the screen limit, the body will reach rest state eventually, which is much cheaper to calculate, or you can even detect it so you can destroy it.

lorenzo

That's fantastic, thanks a lot eri0o! The comments are really well made and understandable. It's a great way to learn how it works.

The wall creator is pretty cool as well. I was making boundaries with rectangles "manually" when trying out the plugin and it took me forever, this should make everything much easier!

eri0o

This "editor" is only temporary, I actually plan to add Tiled integration at some point (despite the name, it packs a vector Editor too, with polygons, circles, rectangles, ... ), but I am still unsure how to do it (a new plug-in for tiled only and do the wrap in script module is where I am leaning towards to, so people could use it for other stuff, like, drawing tiled maps). This can be done in a weekend once the design is figured.

Alternatively people suggested me some Box2D editors, but they all seem unmaintained so I am not so keen on using them.

The long term plan is writing an Editor plugin, so one could draw these directly in the AGS Editor, but this requires me learning C# and coding in Windows, so I am postponing this a bit.

eri0o

New small release! Should fix most random segmentation faults people could get. This release adds Contacts! Contacts are the last concept to add to AgsBox2D, they represent two fixtures with their AABBs touching!

You can see below the result of querying the World for the current contact list and each contact point being marked with a pink circle.



There are some more things I want to add to AgsBox2D, but these will suffice for this year. This release also adds the possibility of changing the world gravity after it's creation, which can enable some interesting puzzles.

SMF spam blocked by CleanTalk