Best Practices
This page lists some of the best practices for working with physics in Easel.
Use body
The body parameter is a very common parameter taken by many functions that work with bodies,
and so it is common to write use body=... at the top of any block that is going to be working with a body.
Now all functions can find the body from context, and you don't have to pass it in as a parameter every time.
Subspawn ship {
use body=ship
Body(pos=20*RandomVector)
// finds `body` from context
PolygonCollider(category=Category:Ship, shape=Equilateral(radius=1, numPoints=3))
once Tick(5s) {
Velocity = @(20,20) // finds `body` from context
}
}
Sizes
You should design your game so your main character has approximately the radius of 1,
and then design the rest of your game around that.
There are two reasons for this.
First, various defaults in Easel are built around this scale,
including the tolerance parameters of PolygonCollider
and PolygonSprite,
Second, area is the square of distance.
If your main character has a size of 1, their area is 1, and you can move them with a force of 100,
which is a number that is easy to comprehend.
If your main character has a size of 100, then their area is 10000,
which means the forces you need to move them are in the scale of 1000000,
which is a number that is hard to comprehend.
It might be easier to think of the distance units as meters, yards or tiles, depending on the type of game you are making. That helps you think about them in the right scale, and to design your game accordingly. Whatever you choose, they do not use pixels as your distance units as the scale will be wrong.
Use Categories Wisely
You only have 32 categories to work with, so you should use them wisely.
For example, instead of having a different category for every type of projectile
(e.g. Category:Fireball and Category:Lightning),
just have a single Category:Projectile. You can have an unlimited number of fields
or properties on your entities, so if needed,
add a field like pub field projectile.IsFireball to distinguish between different types of projectiles.
Simpler Shapes for Physics than for Graphics
Let's say you have a game where the hero is a spaceship with a cool shape - a fuselage, wings, fins, turrets, etc.
When you are creating your PolygonCollider, consider simply using either a Circle or a Capsule shape that roughly fits around the spaceship,
instead of trying to match the exact shape of the spaceship.
This is substantially faster to calculate and will still give you good gameplay.
The most efficient shapes for physics calculations are Circle, Capsule and Rectangle,
so if you can get away with using those, it will be much better for performance!
Create Shapes Once
If you are using Line or Polygon shapes, it is more efficient to create these once and then reuse them.
When you create a Line, it needs to calculate all the normals for every vertex.
When you create a Polygon, it needs to calculate the convex decomposition of the polygon.
If you create the shape once and reuse it, you only need to do these calculations once.
Let's say you have a game where the hero can buy an upgrade to increase their size. First, we define the shape as a constant at the top of the file so that it only gets calculated once:
const HeroShape = Polygon([@(-2,-1), @(1,-1), @(2,1), @(-1,1)])
Next, let's define a property called SizeBonus which begins at 0 but can be increased through upgrades:
pub prop hero.SizeBonus = 0
Now we wrap our PolygonCollider in a with block that causes us to replace the PolygonCollider with a bigger one
each time the SizeBonus increases.
with SizeBonus {
PolygonCollider(shape=HeroShape, scale=1+SizeBonus, category=Category:Hero)
}
Depending on the game, the SizeBonus property may change frequently,
but because we are reusing the same HeroShape, we only need to calculate the convex decomposition once.
Performance tips
-
Avoid unnecessary collision force calculations: Use
senseorisSensoronPolygonColliderwhere possible so that the physics engine does not need to calculate collision forces. Particularly if you have projectiles that will simply expire as soon as they hit something - they can just be sensors. -
Continuous collision detection is expensive - avoid using
bulletonBodyexcept where necessary. -
When using
QueryNearest, provide amaxDistanceif possible, because otherwise if there is no match, it will search every single collider in the whole world before returningnull.