Utilities and type-safe builders for the default libGDX 2D physics engine: Box2D.
Box2D API, being a direct port from C++, can be difficult to work with - especially for beginners. Bodies and fixtures construction code readability could certainly be improved. Kotlin type-safe building DSL can help with that.
ktx-box2d
provides some extensions and utilities that aim improve Box2D API:
createWorld
factory method eases the construction ofWorld
instances.World.body
andBody
extension methods provideFixture
type-safe building DSL that supports customizing fixtures with the following shapes:box
:PolygonShape
as a box.circle
:CircleShape
.polygon
:PolygonShape
.chain
:ChainShape
.loop
: loopedChainShape
.edge
: loopedEdgeShape
.fixture
: a customShape
passed as parameter. Note that in contrary to other fixture factory methods, the passed customShape
will not be disposed of unless thedisposeOfShape
parameter is set totrue
.
FixtureDef.filter
extension methods aim to simplifyFilter
API usage.Body
was extended with the following builder methods that ease creation ofJoint
instances:gearJointWith
:GearJoint
.ropeJointWith
:RopeJoint
.weldJointWith
:WeldJoint
.motorJointWith
:MotorJoint
.mouseJointWith
:MouseJoint
.wheelJointWith
:WheelJoint
.pulleyJointWith
:PulleyJoint
.distanceJointWith
:DistanceJoint
.frictionJointWith
:FrictionJoint
.revoluteJointWith
:RevoluteJoint
.prismaticJointWith
:PrismaticJoint
.jointWith
: anyJoint
type supported by the customJointDef
passed as the method argument.
earthGravity
is a constant that roughly matches Earth's gravity.World.rayCast
extension methods allow creating ray-cast callbacks with Kotlin lambda syntax.World.query
extension method allow creating AABB querying callbacks with Kotlin lambda syntax.
Creating a new Box2D World
without gravity:
import ktx.box2d.createWorld
val world = createWorld()
Creating a new Box2D World
with a custom gravity:
import ktx.box2d.createWorld
import ktx.box2d.earthGravity
import com.badlogic.gdx.math.Vector2
val world = createWorld(gravity = Vector2(0f, -10f))
val earth = createWorld(gravity = earthGravity)
Creating a Body
with a PolygonShape
box Fixture
:
import ktx.box2d.body
// Building body from scratch:
val body = world.body {
box(width = 2f, height = 1f) {
density = 40f
}
}
// Adding box polygon fixture to an existing body:
val fixture = body.box(width = 2f, height = 1f) {
density = 40f
}
Creating a Body
with a customized PolygonShape
Fixture
:
import ktx.box2d.body
import com.badlogic.gdx.math.Vector2
// Building body from scratch:
val body = world.body {
polygon(Vector2(-1f, -1f), Vector2(0f, 1f), Vector2(1f, -1f)) {
density = 40f
}
}
// Adding polygon fixture to an existing body:
val fixture = body.polygon(Vector2(-1f, -1f), Vector2(0f, 1f), Vector2(1f, -1f)) {
density = 40f
}
Creating a Body
with a CircleShape
Fixture
:
import ktx.box2d.body
// Building body from scratch:
val body = world.body {
circle(radius = 1f) {
restitution = 0.5f
}
}
// Adding circle fixture to an existing body:
val fixture = body.circle(radius = 1f) {
restitution = 0.5f
}
Creating a Body
with a ChainShape
Fixture
:
import ktx.box2d.body
import com.badlogic.gdx.math.Vector2
// Building body from scratch:
val body = world.body {
chain(Vector2(-1f, -1f), Vector2(-1f, 1f), Vector2(1f, -1f), Vector2(1f, 1f)) {
friction = 0.5f
}
}
// Adding chain fixture to an existing body:
val fixture = body.chain(Vector2(-1f, -1f), Vector2(-1f, 1f), Vector2(1f, -1f), Vector2(1f, 1f)) {
friction = 0.5f
}
Creating a Body
with a looped ChainShape
Fixture
:
import ktx.box2d.body
import com.badlogic.gdx.math.Vector2
// Building body from scratch:
val body = world.body {
loop(Vector2(-1f, -1f), Vector2(-1f, 1f), Vector2(1f, -1f), Vector2(1f, 1f)) {
friction = 0.5f
}
}
// Adding looped chain fixture to an existing body:
val fixture = body.loop(Vector2(-1f, -1f), Vector2(-1f, 1f), Vector2(1f, -1f), Vector2(1f, 1f)) {
friction = 0.5f
}
Creating a Body
with an EdgeShape
Fixture
:
import ktx.box2d.body
import com.badlogic.gdx.math.Vector2
// Building body from scratch:
val body = world.body {
edge(from = Vector2(-1f, -1f), to = Vector2(1f, 1f)) {
restitution = 1f
}
}
// Adding edge fixture to an existing body:
val fixture = body.edge(from = Vector2(-1f, -1f), to = Vector2(1f, 1f)) {
restitution = 1f
}
Creating a dynamic Body
with multiple Fixture
instances:
import ktx.box2d.*
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType.DynamicBody
val body = world.body {
type = DynamicBody
circle(radius = 1f) {
restitution = 0.5f
filter {
categoryBits = 0x01
maskBits = 0x02
}
}
box(width = 2f, height = 2f) {
density = 40f
}
edge(from = Vector2(-1f, 1f), to = Vector2(1f, 0.5f)) {
friction = 0.7f
}
}
Customizing Shape
of a Fixture
:
import ktx.box2d.*
val body = world.body {
// Shapes are available as the default `it` parameter of fixture
// building blocks. Of course, they can be renamed - in this
// example we renamed the CircleShape parameter to `shape`:
circle { shape ->
shape.radius = 0.5f
shape.position = Vector2(0.5f, 0.5f)
}
}
Creating two Body
instances joined with a DistanceJoint
:
import ktx.box2d.*
val bodyA = world.body {
position.set(-1f, 0f)
box(width = 0.5f, height = 0.5f) {}
}
val bodyB = world.body {
position.set(1f, 0f)
box(width = 0.5f, height = 0.5f) {}
}
// Extension methods for all other joint types are also available.
val joint = bodyA.distanceJointWith(bodyB) {
length = 2f
}
Adding callbacks invoked after creation of Body
and Fixture
instances:
import ktx.box2d.*
val body = world.body {
onCreate { body ->
// Will be called when the body and all of its fixtures are built.
}
circle {
onCreate { fixture ->
// Will be called when this particular fixture is created. Note
// that at invocation time the Box2D body of this fixture might
// not be fully constructed yet.
}
}
}
Creating ray-casts:
import ktx.box2d.*
fun createRayCast() {
world.rayCast(startX = 0f, startY = 0f, endX = 1f, endY = 1f) { fixture, point, normal, fraction ->
// Will be called when this ray hits a fixture.
RayCast.CONTINUE
}
}
Querying the world for fixtures overlapping an AABB:
import ktx.box2d.*
fun createQuery() {
world.query(lowerX = 0f, lowerY = 0f, upperX = 1f, upperY = 1f) { fixture ->
// Will be called when this query overlaps a fixture.
Query.CONTINUE
}
}
Pair this library with ktx-math
for Vector2
factory methods, operator overloads and other math-related
utilities.
- gdx-box2d-kotlin is the first published Kotlin Box2D utilities library
that originally inspired
ktx-box2d
. - libgdx-utils-box2d is a set of Box2D utilities written in Java.