-
Notifications
You must be signed in to change notification settings - Fork 109
Physics guide
Box2D is a software-only physics engine that provides specialized 2D rigid-body physics. More information on this library can be found here. Most of the physics features exposed by Torque 2D have an analogy in Box2D therefore its manual is a good source of information. Finally, this document won't describe what concepts like "density", "restitution" or "collision shapes" mean as you can find those described in the Box2D manual.
Bodies, Fixtures, Joints and the SceneObject
World-space and Local-space conversions
Like other game engines, Torque 2D use logical, floating-point world units. In Torque 2D, just like Box2D, these represent metres i.e. a single world unit is a one metre. This is important to understand as dynamic physics objects need to be within a restricted size to respond correctly as a rigid-body. Here's a quote from the Box2D manual:
"Box2D works with floating point numbers and tolerances have to be used to make Box2D perform well. These tolerances have been tuned to work well with meters-kilogram-second (MKS) units. In particular, Box2D has been tuned to work well with moving shapes between 0.1 and 10 meters. So this means objects between soup cans and buses in size should work well. Static shapes may be up to 50 meters long without trouble."
The physics size on the screen is dependent upon the view size of the camera. If you are having trouble understanding that and create your objects so that each pixel on the screen displays a single world unit then don't be surprised if dynamic rigid-body objects don't respond correctly. This completely down to Box2D and not Torque 2D but Box2D has good reasons for this. If your object is 500x500 world-units in size then the object is 500 metre x 500 metre in size which is huge! Whilst the effects of such large objects is minimal for static objects, dynamic objects can suffer from both accuracy problems as well as the need to apply huge forces to make them move.
In your come from the legacy Torque 2D product then you might be wondering where the "mounting" feature for scene objects has gone. Mounting has been replaced by the use of physics joints however it should be clear that while a fully rigid mount does exist in the form of a weld-joint, this may still provide less than satisfactory results in all cases.
For absolutely rigid mounting you should consider using the CompositeSprite as it is essentially a single object that is composed of multiple sprites.
It's worth briefly explaining how the simulation is updated within the engine so you get a sense of how a physics update is separated from a frame being rendered.
The physics is updated every 1/60th of a second whereas the frames render as fast as they can. This 1/60th of a second is known as a "tick", in this case a physics tick. This means that in many cases the frame is rendered many times "in between" the physics being updated. This would seem to be useless i.e. why rendering the same objects at the same positions if the positions only change very 1/60th of a second!
The reason for this is that whilst the objects' positions (etc) do indeed only update every 1/60th second, the system takes the opportunity to interpolate state such as an objects position/angle from its last position/angle to the new position/angle. In other words, the actual game world is updating in steps every 1/60th second and in between these steps the engine is rendering things like position/angle using interpolation. The main benefit of this is that it drastically improves performance as interpolation is far cheaper than performing the physics simulation each frame. A secondary benefit is that this render interpolation provides extremely smooth motion. Indeed, the higher the frame-rate, the smoother the motion. A tertiary benefit is that physics simulations typically need a consistent and regular time update and at 1/60th second, it provides a fine enough update interval for a good simulation.
This difference between things like position/angle is exposed in a few places in the API. For instance, you can retrieve an objects position/angle as expected but you can also retrieve its "render" position/angle. The "render" version is the position/angle at that frame and not its real position/angle which it is interpolating to.
Whilst this information is hardly critical in using the API, it's good to understand how it works.
This document assumes knowledge of both the Scene and Scene Objects. Also note that where possible both a method(s) and a field is shown however, if a field is available then an equivalent set/get method will be available with the same name prefixed with "set" or "get" appropriately.
The physics system internally has its own "world" in which it processes physics objects. This world is analogous to the Scene, indeed the Scene encapsulates a physics "world" and ensures that the world is kept up-to-date. For this document, a reference to the world refers to the Box2D physics world whereas the Scene refers to the object that contains both the world and Scene Objects. Notionally, you can treat them as one and the same thing.
The world contains several configurable options but by far the most important is gravity. You can change gravity like so:
%scene = new Scene();
// Update gravity using the method.
%scene.setGravity( 0, -9.8 );
// Update gravity using the field.
%scene.Gravity = "0 -9.8";
When you set the gravity you set the independent x/y axis. Each axis is specified in metres/sec therefore setting -9.8m/s (Y Axis) gives an approximate earth gravity. Gravity affects all "dynamic" bodies in the world. Dynamic bodies are discussed later in this document.
The are more advanced options that you are free to chaneg but you should try to leave them unchanged unless you have validated that they improve the performance or simulation quality for your game on your specific device or devices. They are:
- setVelocityIterations() / getVelocityIterations() / "VelocityIterations" field.
- setPositionIterations() / getPositionIterations() / "PositionIterations" field.
These control how many iterations Box2D should take during the simulation tick. It can do this independently for position and velocity. Reducing the number of iterations improves performance but reduces the quality of the simulation. Obviously increasing the number of iterations reduces performance but improves the quality of the simulation. The defaults chosen are the defaults recommended by Box2D however these defaults are not perfect for situations where a far simpler simulation is required i.e. only simple stacking, low-speed object etc.
Every feature of Box2D including bodies, fixtures (collision shapes) and joints etc are supported by Torque 2D. However, these features are in a slightly different way than Box2D but the analogies with Box2D features are direct allowing the use of the Box2D manual to be used.
Here's a breakdown of how those Box2D features are used in Torque 2D.
The SceneObject is the basis for all objects added to a Scene. All of the Box2D functionality (apart from "joints") is encapsulated within each SceneObject and exposed by its API.
Each SceneObject has a 2D position and orientation in world space. This is achieved by giving each SceneObject a Box2D body. A Box2D body is a position and orientation in space to which collision-shapes can be attached. For instance, when you set or get the position of a SceneObject, you are in-fact communicating with the underlying Box2D body itself.
When a SceneObject is created but not within a Scene, you can still set all of its properties including position, angle, collision-shapes etc. This is because Torque2D stands inbetween you and Box2D and caches these settings. In other words, consider a SceneObject that is not in a Scene as being "offline". This is in-fact why configuring a SceneObject physics-related state is faster if you do so before the SceneObject is added to a Scene. The reason being that if a SceneObject is within a Scene when you make physics changes, Box2D will perform the appropriate updates required to keep the physics simulation correct, flagging contact updates etc.
The body within each SceneObject can be one of three body types:
Static bodies cannot collide with other static or kinematic bodies (more precisely, collision-shapes on these bodies do not collide with each other). Also, static bodies are immovable. By immovable, it does not mean you cannot set the position of the static body but simply that if you apply forces or directly set either the linear or angular velocity on the object, it will not move due to that change. You are always free to position a static body however it is recommended that you do not do so for performance reasons. In this, static bodies are typically used for static world geometry e.g. the platforms in a platformer game or the table in a card-game.
Because static bodies are not affected by forces they are obviously not affected by the scene-wide gravity shown previously. Static bodies are not processed during the physics simulation therefore they provide the best performance so should be used whenever possible.
Dynamic bodies are the default body type of any t2dSceneObject you create and are fully processed by the physics simulation. Unlike static bodies, dynamic bodies do collide with all body types and are movable via forces applied to them. These bodies are typically used for the things moving around the scene that need to collide with other objects and move using their linear or angular velocity. Dynamic bodies need the most processing so whilst they are the default body type, you should not use them for objects that never move as you are simply wasting performance.
Because dynamic bodies are affected by forces they are affected by the scene-wide gravity. Indeed, this can be a cause of confusion because when adding objects into the scene they will immediately be affected by the scene-wide gravity. This can be confusing when you wonder why the object may no longer be in the current view; it may have moved a long way away from it under the force of gravity.
You can use the SceneObject "setGravityScale()" to scale how the scene-wide gravity affects the object however you should use this with caution as indicated in the Box2D manual as it can, under some circumstances, cause simulation instability in stacking scenarios. That method is explained later.
Kinematic bodies are the least used of the body types but are useful in that kinematic bodies do not collide with other kinematic bodies but do collide with static or other dynamic bodies. Kinematic bodies move according to their linear or angular velocities but do not respond to forces applied to them. Kinematic bodies are typically moved by the user by them modifying the velocities directly.
If you wish to change the body type, you should try to set it immediately before configuring the object further as this provides the best performance. Modifying the body type can result in many calculations if the body contains collision-shapes and has active contacts in the scene. As has already been said, configuring an object before it is added to the scene provides the best performance.
If you set the position of a SceneObjects that can collide with other SceneObjects in the scene then the system must process any existing contacts. This is true when adding a new SceneObject into the scene. If you add all your SceneObjects to position (0,0) then they will all collide and produce contacts even if you immediately reposition them. For best performance, set the position of the SceneObject before it is added to the Scene. In this case, it will only have contacts that it should have. Ignoring this can result in a lot of wasted processing simply adding objects to the Scene.
Here's how you configure the body type.
%obj = new SceneObject();
%obj.BodyType = static;
%obj.BodyType = dynamic;
%obj.BodyType = kinematic;
%obj.SetBodyType( static );
%obj.SetBodyType( dynamic );
%obj.SetBodyType( kinematic );
A SceneObject defaults to a "dynamic" body type i.e. it is affected by gravity. Types that inherit from SceneObject are also dynamic unless they override that. Certain objects default to "static" as it makes sense for their purpose. Refer to each types documentation for this information.
You can set a SceneObject body to be active or inactive. This is a quick way to exclude it from the physics simulation. When it is inactive, irrelevant of its body type, it will not interact with any other objects nor will it move according to its velocity or forces applied to it. It does not relate to whether the object renders anything, simply the physics part of its functionality. This is exposed directly from Box2D but is also slightly duplicated with the "setEnabled( true/false )" and "getEnabled()" methods. If you disable an object, it will also be made inactive. Disabling an object additionally results in it not being rendered as well. For visibility control only, you can use the "setVisible( true/false )" and "getVisible()". These three pairs of methods therefore control physics and rendering either independently or together.
%obj = new SceneObject();
%obj.Active = false;
You can set a SceneObject body to be awake or asleep or check to see if it is awake or asleep. The physics system uses this feature when the SceneObject has not moved or hasn't had any new contacts for a certain simulation period. If this is the case then the simulation will put the SceneObject body to sleep. Doing this massively reduces the overhead of the object. If the object is collided with or is modified directly by the user i.e. its position is changed, it is automatically woken up. You can therefore take control of this and set its "awake" state at any time. You should typically leave the system to control this but you can check if an object is awake without affecting it at any time.
All scene objects are awake by default. For better performance you can set an object to be initially asleep rather than waiting for the simulation to put it to sleep. This greatly improves initial performance however care should be taken as if an object is initially asleep and has contacts, it will not resolve them. For a many objects however, being initially asleep would not be a problem.
%obj = new SceneObject();
%obj.Awake = false;
If you do not wish to allow an object to sleep you can also control this using:
%obj = new SceneObject();
%obj.SleepingAllowed = false;
Sleeping is allowed by default.
The term "bullet" refers specifically to an object that moves fast. The default collision algorithms that Box2D uses balances performance and accuracy for low to medium speed objects. High speed objects can actually pass directly through each other. Most of the time you will not see this, however if you are encountering this (typically seen for high-speed projectiles in games) then you can flag the object as a bullet. In this case, Box2D will use the much more expensive CCD (continuous collision detection) algorithm which will completely eliminate this issue. By default, the bullet flag is off as using CCD for all objects would soon become prohibitively expensive. Use this feature with care and only when your game absolutely requires it, specifically on objects that require it and certainly not all objects. Do not turn it on and wait until your performance suffers, do the opposite, wait for the problem and turn it on for that specific object.
%obj = new SceneObject();
%obj.Bullet = true;
An object can have a position, angle, linear velocity (translation) and angular velocity (spin), all of which can be modified like so:
%obj = new SceneObject();
// Set position.
%obj.Position = "10 20"
// Set angle (in degrees).
%obj.Angle = 30;
// Set linear velocity (translation) in metres/sec.
%obj.LinearVelocity = "5 8";
// Set angular velocity (spin) in degrees/sec.
%obj.AngularVelocity = 10;
With the integration of Box2D, an important change from the legacy Torque2D was the change to the coordinate system used. As you have seen, world-units actual refer to a physical unit i.e. the metre. Additionally, legacy Torque 2D had the +Y Axis to mean "down" and the -Y to mean "up". This confused a lot of users. This change introduces an inverted Y meaning that +Y Axis means "up" and -Y means "down". If you think about it, in a game this makes much more sense. If your object needs to jump "up", you give it a positive impulse/force/velocity rather than a negative one.
As mentioned previously, it's worth showing some extra methods provided for render position and angle:
%obj.getRenderPosition();
%obj.getRenderAngle();
These provide the current position and angle being rendered in the current frame (interpolated). If the SceneObject is not moving, these will be identical to the "getPosition()" and "getAngle()" respectively however if the SceneObject is moving, they will differ.
For the most part, you will not use the render versions of position and angle however if you wish for some script code to relate directly to what is on screen at that point in time then you can use the render variants of these methods.
Modifying either the position, angle or size of a SceneObject causes the render position and angle to be made identical to the current position and angle respectively. This is why, when setting these values continuously, the smooth motion that the render interpolation system provides is undermined and smooth motion ceases for that object. Occasionally setting an objects position, angle or size would be fine. You should consider that setting an objects position, angle or size immediately "warps" (think teleportation) the SceneObject to a different position in space without it smoothly moving/resizing there.
Related to angle, you can configure a SceneObject so that its angle cannot be changed by forces applied directly to it or from collisions. This is known as a fixed angle. This does not affect you using "setAngle()" to change the angle however but it can be controlled using:
%obj.FixedAngle = true;
Note that this doesn't actually set the angle, just the fact that the current angle is fixed.
Scene-wide gravity is extremely useful in many scenarios however it affects all dynamic bodies equally by default. Sometimes it is neccessary to either scale how the gravity affects a SceneObject or to simply turn off gravity for that SceneObject. The best way to stop gravity from affecting a SceneObject is to make its body type "static" however if that is not wanted then setting the gravity scale is the next best thing.
You can use the following to change the gravity scale:
// This object is affected by half the Scene gravity.
%obj.GravityScale = 0.5;
// This object is now NOT affected by the Scene gravity.
%obj.GravityScale = 0;
As you can see, gravity can be scaled per-SceneObject or completely turned-off by scaling with zero. As indicated previously, caution should be used when scaling gravity as it can cause instabilities in the simulation for that SceneObject under some circumstances. This warning is reproduced in the Box2D manual.
There are many methods available to manipulate both velocities and forces on a SceneObject. Here is a brief list of the most important ones:
- setLinearVelocityX(velocityX)
- getLinearVelocityX()
- setLinearVelocityY(velocityY)
- getLinearVelocityY()
- getLinearVelocityFromWorldPoint()
- getLinearVelocityFromLocalPoint()
- setLinearVelocityPolar(angle, speed)
- getLinearVelocityPolar()
- setLinearDamping(damping)
- getLinearDamping()
- setAngularDamping(damping)
- getAngularDamping()
- applyForce(force, point)
- applyTorque(torque)
- applyLinearImpulse(impulse, point)
- applyAngularImpulse(impulse)
World space is what you get used to when organizing SceneObjects in a Scene however each SceneObject essentially "lives" in its own local space. Local space is simply where the origin is centered on the SceneObjects position. Additionally, the X/Y axis are not always aligned to the worlds X/Y axis (basis) but are aligned according to the SceneObjects current angle. In other words, if the SceneObject is at zero-degrees angle then the local X axis is "-left/+right" (the same as the world X axis) and the local Y axis is "+up/-down" (the same as the world Y axis). If the SceneObject is at +90 degrees angle (90 degrees anti-clockwise) then the local X axis is "-down/+up" and the local Y axis is "-right/+left".
One of the most common operations related to world and local space conversions is converting either a point or vector between these spaces. These operations are provided by the following methods:
- getLocalPoint( worldPoint )
- getWorldPoint( localPoint )
- getLocalVector( worldVector )
- getWorldVector( localVector )
As you can see, these methods take a local/world point/vector appropriately. These methods exist on a SceneObject therefore they relate to the position and angle of that object only.
When you add collision shapes to a SceneObject, they add mass to it assuming they have non-zero density. Because collision shapes are not necessarily symmetric and/or they don't have to be centered at the SceneObjects position and/or they have different masses, the center of mass is not always equal to the SceneObject body position. With this in mind, the center of mass can be queried using the following methods:
- getLocalCenter()
- getWorldCenter()
These methods return the center of mass in either local space or world space. The local center is relative to the SceneObject body position i.e. it is in local-space whereas the world center is relative to the world origin i.e. it's in world-space.
It can be useful to know the center of mass if you are applying forces. Applying linear forces to the center of mass does not cause rotation i.e. it applies no torque whereas applying a linear force elsewhere does. In summary; if you apply a linear force to the bodies' position and it is not the center of mass then the SceneObject will rotate.
Torque 2D supports all collision shapes that Box2D provides. In legacy Torque 2D it only had a box and circle collision shape.
The supported shapes therefore are:
- Circle
- Polygon (convex only)
- Edge
- Chain
A SceneObject can have as many of these collision shapes as it requires. This allows you to construct complex collision shape regions. All collision shapes are specified in metres (world-units). In legacy Torque 2D, collision shapes had their own special normalized local-space. This allow the collision shapes to change as an object was resized. This is completely unsupported in Box2D and requires collision shapes to be recreated which is prohibitively expensive therefore Torque 2D does not support this either.
A circle shape is obvious and actually provides the best performing collision shape. It has a radius >0 and can be positioned at any local-space point.
The polygon shape must be a convex shape and is hard-limited to have no more than 8 vertices. This can be changed but requires a recompilation as it is completely contained within a single define within the Box2D code. I ncreasing this is not recommended as it can impact performance greatly for very little return.
The edge shape provides a single edge comprised of two local-space points. This can be handy in many circumstances.
The chain is similar to the edge shape except that it allows multiple continuous edges to be specified. This allows, for instance, the surface (floor) of a tile-layer to be specified or just arbitrary chains of edges for any reason. Both edges and chains provide the opportunity for one-side collisions which can be handy in platformer games.
A lot of time was spent trying to design how to expose a flexible enough collision shape API without having to make each collision shape a SimObject to expose it as a discrete object to the script system as this would have been disastrous for memory consumption. To this end, the design is such that special factory functions for each collision shape type exist coupled with a common API that provides common enumeration, configuration, deletion etc.
There are several methods dedicated to creating either a Circle, Polygon, Edge or Chain polygon shape. There are a few overloads as well to provide common functionality. All these methods have their own arguments and is not described here.
The factory methods are:
- createCircleCollisionShape( radius, [position] )
- createPolygonCollisionShape( points )
- createPolygonBoxCollisionShape(width, height, [position, angle] )
- createChainCollisionShape( points, [adjacentStartPoint], [adjacentEndPoint] )
- createEdgeCollisionShape( startPoint, endPoint, [adjacentStartPoint], [adjacentEndPoint] )
%obj = new SceneObject();
%obj.createCircleCollisionShape( 3 );
%obj.createPolygonCollisionShape( %points );
%obj.createPolygonBoxCollisionShape( 4, 3 );
%obj.createChainCollisionShape( %points );
%obj.createEdgeCollisionShape( %startPoint, %endPoint );
Note that the Polygon version has an overload that lets you create a polygon box (actually a square i.e. 4 vertices). When you create a collision shape, these methods return you the collision shape index which is a simple index from 0 onwards. If you do not delete a collision shape then the index is always associated with the collision shape as it is simply an index into an array of collision shapes for the SceneObject. If you delete a collision shape however then any collision shapes with a higher index have their index changed i.e their index is reduced by 1. Typically you do not remove collision shapes due to the performance cost therefore this is unlikely to affect you however you must be aware that the collision shape index is NOT an id but a simple index.
If creating a collision shape fails, an index of -1 is returned in all cases.
If you do want to delete a collision-shape you can delete it, passing its index using:
- deleteCollisionShape()
%obj.deleteCollisionShape( %shapeIndex );
Note that you do not need to delete collision shapes yourself as they are deleted automatically when the SceneObject is destroyed which typically happens when the Scene they are within is destroyed or the SceneObject is explicitly deleted.
Creating each collision shape requires several arguments to be specified however all collision shapes have a common set of properties. To reduce the number of arguments that must be specified when creating collision shapes you do not specify them when creating the collision shape but can do so after the shape has been created.
These methods are:
- setCollisionShapeDensity(index, density)
- getCollisionShapeDensity(index)
- setCollisionShapeFriction(index, friction)
- getCollisionShapeFriction(index)
- setCollisionShapeRestitution(index, restitution)
- getCollisionShapeRestitution(index)
- setCollisionShapeIsSensor(index, isSensor?)
- getCollisionShapeIsSensor(index)
All these methods require you to specify the collision shape index. As you can see you can change the Density, Friction, Restitution and whether the shape is a sensor (does not react to contacts but still reports them).
The most common ones are Density, Friction and Restitution and typically you want all collision shapes on a SceneObject to have the same values. To aid this and remove the need to set each collision shape you can set the defaults for Density, Friction and Restitution prior to creating the collision shapes.
These methods are:
- setDefaultDensity(density)
- getDefaultDensity()
- setDefaultFriction(friction)
- getDefaultFriction()
- setDefaultRestitution(restitution)
- getDefaultRestitution()
You call these on the SceneObject, not on a collision shape. With these set, any subsequent collision shapes created will use the default values you specify.
If you do not modify these they are:
- Density = 1.0
- Friction = 0.2
- Restitution = 0.0
After you create a collision shapes, many of their properties are immutable but you can retrieve them using the following methods:
- Circle
- getCircleCollisionShapeRadius(index)
- getCircleCollisionShapeLocalPosition(index)
- Polygon
- getPolygonCollisionShapePointCount(index)
- getPolygonCollisionShapeLocalPoint(index)
- Chain
- getChainCollisionShapePointCount(index)
- getChainCollisionShapeLocalPoint(index)
- getChainCollisionShapeHasAdjacentStart(index)
- getChainCollisionShapeHasAdjacentEnd(index)
- getChainCollisionShapeAdjacentStart(index)
- getChainCollisionShapeAdjacentEnd(index)
- Edge
- getEdgeCollisionShapePointCount(index)
- getEdgeCollisionShapeLocalPoint(index)
- getEdgeCollisionShapeHasAdjacentStart(index)
- getEdgeCollisionShapeHasAdjacentEnd(index)
- getEdgeCollisionShapeAdjacentStart(index)
- getEdgeCollisionShapeAdjacentEnd(index)
You can enumerate the collision shapes on a SceneObject easily by simply using their index values. T o do this however you need to get a count of collision shapes on a SceneObject.
You can do this using:
- getCollisionShapeCount()
Using this you can iterate indexes from 0 to "getCollisionShapeCount()-1". A quick way of determining what type of collision shape is at any specific index is to use:
- getCollisionShapeType(index)
This will return you one of the four supported shape types below:
- circle
- polygon
- edge
- chain
Knowing the shape type at each location you can then perform the appropriate "get" actions shown previously to retrieve information specific to that shape type at that index.
Note that if you perform the incorrect shape type call i.e. try to retrieve the radius of a circle shape but the shape at the specified index is (say) a polygon then you'll simply get a warning in the console.
It's possible to copy either a single collision shape or all collision shapes from one SceneObject to another. You can do this using:
- copyAllCollisionShapes( %targetSceneObject, [clearTargetShapes?] )
- copyCollisionShape( %sourceShapeIndex, %targetSceneObject )
For two SceneObjects to come into contact with each other, it's important to not only understand how to configure that but to actually understand what occurs when they do.
As we have seen, a SceneObject encapsulates a Box2D body. The body gives the SceneObject a position and orientation in the Scene (world). Bodies can have both linear and angular velocities i.e. they can translate and rotate.
None of this provides the ability for things to collide. In other words, bodies don't collide with each other directly. This happens by adding collision shapes to a body. The collision shapes themselves can be configured to collide with other collision shapes on other bodies.
When two shapes collide, they produce what is known as a "contact". A contact is created when collision shapes touch/overlap and is destroyed when they separate therefore the physics simulation is constantly creating and destroying contacts as collisions start and end.
Configuring what collides with what can therefore be thought of as "filtering potential contacts".
To configure what a SceneObject can collide with you simply configure which scene groups and scene layers it should collide with. Each SceneObject can be placed into a single scene group and a single scene layer. Scene layers range from 0 to 31 as do scene groups. The Scene group of a SceneObject is a general purpose way of grouping objects. This can be used when picking objects from the Scene or as we'll see here for selecting collisions. The Scene layer is the primary rendering order of the SceneObject with 0 being the front-most and 31 being the back-most. Again, this can be used for selecting collisions i.e. only collide with stuff on certain rendering layer. Whilst it may at first seem strange that collisions be controlled via a rendering layer, it's actually a useful way of filtering contacts however, the typical method is to restrict stuff via scene groups.
By default, a newly created SceneObject exists in SceneLayer zero and SceneGroup zero. A SceneObject can only exist in a single layer and single group i.e. it can't be on multiple render layers or in multiple scene groups. You set these using the following methods:
- setSceneLayer( layer ) or "%obj.SceneLayer = %layer"
- getSceneLayer()
- setSceneGroup( group ) or "%obj.SceneGroup = %group"
- getSceneGroup()
To configure which Scene group(s) and Scene layer(s) a SceneObject can collide with you use the following methods:
- setCollisionLayers( %layers ) or "%obj.CollisionLayers = %layers"
- setCollisionGroups( %groups ) or "%obj.CollisionGroups = %groups"
%obj = new SceneObject();
// These all do the same thing.
%obj.setCollisionLayers( 5, 6, 7 );
%obj.setCollisionLayers( "5 6 7" );
%obj.CollisionLayers = "5 6 7";
// These all do the same thing.
%obj.setCollisionGroups( 20, 30 );
%obj.setCollisionGroups( "20 30" );
%obj.CollisionGroups = "20 30";
By default, a SceneObject will collide with all Scene layers and all Scene groups i.e. SceneObjects collide with all other SceneObjects by default.
There are also some short-cuts here that mean all groups/layers or even no groups/layers like so:
%obj = new SceneObject();
// Each of these methods select all layers!
%obj.setCollisionLayers();
%obj.setCollisionLayers( all );
// Each of these methods select all groups!
%obj.setCollisionGroups();
%obj.setCollisionGroups( all );
// Each of these methods select no layers!
%obj.setCollisionLayers( none );
%obj.setCollisionLayers( off );
// Each of these methods select no groups!
%obj.setCollisionGroups( none );
%obj.setCollisionGroups( off );
You are free to configure both Scene layer(s) and Scene group(s) that an object collides with or you can simply use just the Scene layer(s) or Scene group(s). In other words, you may only wish to allow a SceneObject to collide with a specific Scene group, irrelevant of the Scene layer it is on. To do this you would just leave the collision layers at default i.e. to collide with all Scene layers) and just configured which particular Scene group(s) are to be collided with.
How you organize your Scene, which Scene layers and Scene groups your objects are organized in is totally up to you. It's typical however to use Scene groups to organize collisions rather than Scene layers. You can apply meaning to each Scene group i.e. "0 = Aliens", "1 = Projectiles", "2 = Pickups" etc. You can logically do the same for Scene layers however those obviously also affect the order of rendering whereas modifying the Scene group has no impact on the render order or anything else so you are free to apply any meaning to the 32 available groups you wish.
It's worth planning out your Scene groups (and possibly Scene layers if you intend to use them for collisions) before you start the game construction or at least have a place where you note what each group means. For simply games you can get away with a few groups so their meaning is easy to remember however for more complex games it's worth planning ahead.
Here's a quick example that uses Scene groups.
// Configure the projectile to collide with anything in collision group 10.
%projectile = new SceneObject();
%projectile.createCircleCollisionShape( 1 );
%projectile.setCollisionGroups( 10 );
// Configure the enemy to be in scene group 10.
%enemy = new SceneObject();
%enemy.createCircleCollisionShape( 1 );
%enemy.setSceneGroup( 10 );
In this example, the "projectile" collides with the "enemy" object in Scene group 10. It doesn't matter what Scene layers they are in as the default is to collide with all Scene layers. In this case we're restricting collision by group.
What is particularly interesting here is that we only had to allow the projectile to collide with the enemy for a collision to occur, we didn't have to configure the enemy to collide with the projectile.
It is important to note the bi-directional nature of collision filtering though. If you want to prevent collisions from occurring, careful planning of groups and/or layers is needed. Using the projectile/enemy example above, consider this change:
%projectile.setCollisionGroups(none);
%projectile.setCollisionLayers(none);
The projectile is now set to collide with nothing, regardless of scene group or layer. The enemy is still set at default, which is to collide with everything. In this case, you would filter out the collision of the projectile with the enemy – however, the collision of the enemy with the projectile still takes place! If you want to prevent a collision from happening in both directions, then you need to set the CollisionGroups field on the enemy to not include the scene group of the projectile or one of the objects needs to have their collisions suppressed. Continue reading a few paragraphs down for more information on collision suppression.
To determine if two objects should collide, Torque 2D uses this a specific contact algorithm however, you are free to change this as it is located with in a single method located here
Even with a plan of each group or layer meaning, it can be useful to not have to spread this knowledge throughout your scripts. To help with this, you can configure a SceneObject to collide with another SceneObject without knowing the details of each SceneObject using the folllowing method:
- setCollisionAgainst( %sceneObject, [clearMasks?] )
You would use it like this:
%objA.setCollisionAgainst( %objB );
In the above example, "%objA" would append to its collision Scene layers and collision Scene groups (the layers and groups it collides with) the Scene layer and Scene group from "%objB". This allows you to effectively "point" to a SceneObject and configure another object to collide with it (specifically any object in its Scene layer or Scene group).
The optional second argument allows you to clear the collision masks. By default this is false meaning the call will append the collision Scene layers and groups. When true, any existing collision Scene layers and groups are cleared first then the assignment is made therefore changing it into a replace operation rather than an append operation.
The meaning of "collision mask" comes from the fact that you can, in parallel, select any/all of both the Scene layers and Scene groups. This produces an unsigned 32-bit value for the Scene layers and another for the Scene groups. These are known as "collision masks" because they filter (mask) collisions.
You can set both the collision Scene group and collision Scene layers directly using makes with the following:
- setCollisionMasks( %groupMask, [%layerMask] )
- getCollisionMasks()
Each bit in each mask represents the respective group or layer i.e. Bit#0 in the group mask represents group 0, Bit#31 in the group mask represents group 31, Bit#5 in the layer mask represents layer 0 etc.
If you don't want a SceneObject participating in any collisions whatsoever, irrelevant of which Scene groups or layers it's in or what its collision groups or layers are currently set to, you can simply use the following:
- setCollisionSuppress( true/false )
- getCollisionSuppress()
The reason this is known as "supressing" collision comes from the fact that you can configure an object to collide with other objects but can temporarily supress the collisions using this method. You can turn off the supression at any time to resume collisions with affecting any of the existing collision configuration. It should be noted however that whether collisions are suppressed or not is persisted should you persist the SceneObject itself.
When a collision occurs between two objects, the physics system registers a "contact". A contact is a record of the collision and the contact will persist as long as the two objects are touching.
When a contact is created, it produces a "response" to the contact. Box2D is a rigid-body physics system and so produces a rigid-body response. The exception to this is if either of the collision shapes involved in the collision are sensors. A sensor does not produce a collision response but does allow you to receive a callback that it occurred. More details of this can be found in the Box2D manual.
For your custom game-play however, you'll probably be interested in knowing when specific objects collide with other objects. To get this information you can use the collision callback system. This system is configured on a per-SceneObject basis. The reason for this is that the physics system can produce a huge amount of contacts and producing a callback to the scripts for each and every one of them, irrelevant of whether they are required would be prohibitively expensive.
Turning on callbacks for an object is done like this:
// These do the same thing.
%obj.setCollisionCallback( true );
%obj.CollisionCallback = true;
So with collision callbacks on, it's important to know how to set up the callback in TorqueScript. Torque 2D is quite flexible here and allows you to get a callback from two different sources.
The first is a callback from the Scene itself. This may sound strange to some who are familiar with the legacy Torque 2D however it's quite a natural way of consuming contacts.
Here's the syntax for such a collision callback:
function Scene::onSceneCollision( %this, %sceneObjectA, %sceneObjectB, %collisionDetails )
{
}
As you can see, you simply get the two scene objects involved in the contact. The "%collisionDetails" returns extra details about the collision which will be covered shortly.
The main reason for getting collision information from the Scene is that it provides you with a reference to both objects that collided and you don't need know any information about the SceneObjects at all.
An advantage to this approach is that you'll only get a single callback for the collision between both objects which will give you better performance.
A disadvantage is that you can't write a callback for objects of a certain "class" or "name" as will be shown shortly.
It's also worth nothing that if you don't define the above callback method then Torque 2D will attempt to call this on any behaviors added to the Scene object itself.
So an alternate way is to get a callback from the Scene object like this:
function SceneObject::onCollision( %this, %sceneObject, %collisionDetails )
{
}
Notice here that the syntax is slightly different in that only a reference to the object being collided with is passed. In this case, the "%this" is the object that is colliding with the object "%sceneObject".
An advantage here is that this is more useful if you have an object with a "class" or "name" like so:
%sceneObject = new Sprite()
{
class = "Projectile";
};
If you have this then you can set up a callback like this:
function Projectile::onCollision( %this, %sceneObject, %collisionDetails )
{
}
This would be called whenever a "Projectile" sprite comes in contact with another object. Note that an object doesn't collide with another, the physics system doesn't make that distinction. All it really cares about is that two objects have come into contact. The callback isn't saying that one object "caused" the collision but simply that both objects came into contact!
A advantage of this approach is that you'll only get a call on the object if it has its collision callback active even if the object it is colliding with doesn't. In other words, you can know if an object is colliding with another object without having to also have the "other" objects collision callback active as well.
A disadvantage of this approach is that if both objects have their collision callback active and both are configured to collide with each other then you'll get two callbacks which can hurt performance if too many are happening. This disadvantage however is removed if you only configure objects to contact in one "direction" i.e. "projectiles" are configured to contact "enemy" but "enemty" are not configured to contact "projectiles". In this case, when a "projectile" and an "enemy" come into contact, only the "projectile" will get a callback because it's configured to collide with the "enemy"!
It's also worth nothing that if you don't define the above callback method at all then Torque 2D will attempt to call this on any behaviors added to the SceneObject itself.
In both cases you should notice the callbacks pass "%collisionDetails". This returns extra details about the collision. There are actually two formats for the collision details depending on whether one or two contact points occurred.
The format is as a space separated list containing the following items in the specified order:
- Collision Shape Index A
- Collision Shape Index B
- Collision Normal
- Contact World Point #1
- Collision Normal Impulse #1
- Collision Tangent Impulse #1
- Contact World Point #2 (Only if two contacts)
- Collision Normal Impulse #2 (Only if two contacts)
- Collision Tangent Impulse #2 (Only if two contacts)
As you see, the first two items are the collision shape indexes that collided within each SceneObject. These two items are the only two that are guaranteed to be returned. If this happens then it signifies that there are no contact points. Whilst this may sound strange, Box2D doesn't produce contact points for collisions involving sensors and therefore nothing can be returned. In this instance you'll only know the two SceneObject and their collision shapes indexes.
If you have at least a single contact point then you'll also get a Collision Normal (item#3).
For each contact point you'll get a Contact World Point, Normal Impulse and Tangent Impulse. In the case of a single contact point this means you'll get items 4-6. For two contact points you'll not only get items 4-6 but an additional contact of items 7-9.
An example might be:
function Scene::onCollision( %this, %sceneObjectA, %sceneObjectB, %collisionDetails )
{
echo( %collisionDetails );
}
... where, assuming this has a single contact point, might echo ...
// Collision Shape A Index = 0
// Collision Shape B Index = 2
// Collision Normal = (0.0, 1.0)
// Contact World Point #1 = (100.0, 50.0)
// Normal Impulse #1 = 30.0
// Tangent Impulse #1 = 0.0
// Contact World Point #2 = (100.0, 60.0)
// Normal Impulse #2 = 40.0
// Tangent Impulse #2 = 0.0
"0 2 0.0 1.0 100.0 50.0 30.0 0.0 100.0 50.0 40.0 0.0"
These are the details of each item are:
- Collision Shape Index A & B - Refers to the collision shapes involved in the collision. You can use this to retrieve specific details about those collision shapes such as whether they are sensors, their friction, restitution etc.
- Normal - This normal relates to the direction that an impulse will be applied to separate the two collision shapes. In the case where either collision shape is a sensor, no impulse is applied however the collision normal is still calculated. You could conceivably use this to apply your own impulse along the collision normal.
- Contact World Point - These are known as support points and relate to the calculation of the collision normal and therefore the separating impulse. Further information can be found in the Box2D manual.
- Normal Impulse - Is the impulse force used to separate the objects during the collision. It is indicative of the forces involved in the collision.
- Tangent Impulse - Is the impulse force used when calculating contact friction.
Both the contact normals and contact points come directly from Box2D itself and Torque 2D does not manipulate them in any way. If CCD (bullet) is not active then the contact points may not be on the perimeter of the collision shapes but in-fact are at the points where an impulse is applied to separate the objects. With CCD active, these are always on the perimeter of the collision shape. More information can be found in the Box2D documentation.
As with all TorqueScript callbacks, extreme care should be taken on what actions occur during the callback. If you wish to destroy an object involved in the collision you must always use the "safe delete" option and never immediately destroy an object as this can cause an exception. Performing a safe delete operation will ensure no further collision callbacks occur should the object be involved in multiple contacts during that game loop however.
Although you typically don't want all this information, here's an example of these being extracted in full:
function Scene::onCollision( %this, %sceneObjectA, %sceneObjectB, %collisionDetails )
{
// Fetch the collision shape indexes.
%shapeA = %collisionDetails._0;
%shapeB = %collisionDetails._1;
// Fetch how much detail we were returned.
%detailCount = %collisionDetails.count;
// The first contact point.
if ( %detailCount > 2 )
{
%collisionNormal = %collisionDetails._2 SPC %collisionDetails._3;
%worldPoint1 = %collisionDetails._4 SPC %collisionDetails._5;
%normalImpulse1 = %collisionDetails._6;
%tangentImpulse1 = %collisionDetails._7;
}
// Less common but you can have two contact points.
if ( %detailCount > 8 )
{
%worldPoint2 = %collisionDetails._8 SPC %collisionDetails._9;
%normalImpulse2 = %collisionDetails._10;
%tangentImpulse2 = %collisionDetails._11;
}
}
So far we've seen two different ways to set up a callback for when a collision starts but Torque 2D also provides you with a callback when a collision ends.
For the Scene you can use:
function Scene::onSceneEndCollision( %this, %sceneObjectA, %sceneObjectB, %collisionDetails )
{
}
For the SceneObject you can use:
function SceneObject::onEndCollision( %this, %sceneObject, %collisionDetails )
{
}
In both cases the "%collisionDetails" always contains only the collision shape indexes i.e. items #1 and #2 in the "onCollision()" callback collision details.
This therefore informs you when two scene objects stop contacting each other as well as the specific collision shape indexes.
In summary then, you can get a collision callback via the Scene when a collision both starts and ends if both objects have their collision callback active like so:
function Scene::onSceneCollision( %this, %sceneObjectA, %sceneObjectB, %collisionDetails )
{
}
function Scene::onSceneEndCollision( %this, %sceneObjectA, %sceneObjectB, %collisionDetails )
{
}
You can instead get a callback via either or both SceneObject when a collision both starts and ends if either object involved in the collision have their collision callback active like so:
function SceneObject::onCollision( %this, %sceneObject, %collisionDetails )
{
}
function SceneObject::onEndCollision( %this, %sceneObject, %collisionDetails )
{
}
In the above case, you'll get a callback for each object that has their collision callback active so you'll either get it never, once or twice with the "%sceneObject" representing the object that was collided with.
As a final and important note, it's vitally important to understand that in reality, the physics system doesn't have a concept of one object collide with another. In reality it knows about a contact between two objects. The Scene collision callback represents that. The SceneObject collision callbacks are a convenience only at the expense of potentially having two callbacks per collision. Obviously caution is indicated here as callbacks can become extremely expensive.
There are times when you want, for a specific object, to know all the contacts that the object has at any point in time without having to accumulate them yourself using the "onCollision()" and "onEndCollisionCallbacks()".
You can turn this on for any SceneObject using:
- setGatherContacts(true/false)
- getGatherContacts()
- "GatherContacts" field
Contact gathering is off by default as it requires extra memory and processing to store all the contact information. Contact gathering is still pretty efficient however having this feature on all the time for all objects would be extremely wasteful.
When contact gathering is enabled, you can retrieve contacts using two SceneObject methods:
- getContactCount()
- getContact(contactIndex)
If contact gathering is disabled, the contact count will always be zero.
Here's an example of retrieving all contacts on an object:
%count = %obj.getContactCount();
for( %i = 0; %i < %count; %i++ )
{
%contact = %obj.getContact( %i );
echo( %contact );
}
The contact string returned is almost identical to the "onCollision()" collision details string (items #1 to #9 with all the contact point rules described there) however this string is prefixed with the SceneObject that this object is colliding with.
For an in-depth explanation of specific joint types and their use, take a look at the Joints Guide
Torque 2D supports nearly all the joints provided by Box2D. I say nearly because only a single joint is not supported (the gear joint). The reason this joint is not supported is simply because it's fairly complex to configure making the arguments needed to configure it pretty overwhelming and it's rarely used so it was omitted. If it becomes important in the future to use it, it can be added relatively easily without affecting anything else. The same obviously goes for your own custom joints.
The complete joint list is therefore:
- distance
- rope
- revolute
- weld
- wheel
- friction
- prismatic
- pulley
- target
- motor
The "target" joint above is in actual fact Box2Ds "mouse" joint. It was renamed "target" as that better indicates what it is meant to do i.e. move a SceneObject towards a target point. The fact that it is typically used in the Box2D test-bed to allow the user to use the mouse to drag objects to target points is a poor reason to name it "mouse" joint.
This document does not go into detail for each joint as that would be duplicating what is already in the Box2D documentation. It will however list the methods involved in creating, configuring, enumerating and destroying joints.
Joints, the Scene and Legacy Mounting
The legacy Torque 2D "mounting" functionality has been removed. That functionality was implemented in the SceneObject API as it involved a simple mounting of one SceneObject to another. Joints completely replace that functionality.
Unlike the legacy Torque 2D "mounting" feature, the API for joints is against the Scene itself and not the SceneObject. Indeed, the SceneObject knows nothing about joints. This might seem odd to some but it makes sense in that joints are not owned by any single SceneObject. All joints bind two scene objects together so it makes complete sense that the joint cannot be owned by either but a third party; in this case the Scene.
It's worth pointing out something that's extremely important to know about how you refer to joints that have been created. If you recall, collision shapes live on a SceneObject to which they are attached. They are addressed by a simple index that can potentially change if other collision shapes on the same SceneObject are deleted: something that is rarely done due to performance reasons.
With joints, the situation is quite different. For starters, all joints live in the Scene itself. Also, it is fairly common to destroy joints during game-play, even expected. In this, it makes sense to use something more sophisticated than an index into an array of joints. For joints, when you create them, they each get a unique serial Id. Unlike collision shapes however it will never change during the lifetime of a joint. This means that once you create a joint, you can always refer to it by its Id. Additionally, if you delete the joint, that Id will never be used again during the game which makes it easy to spot bugs related to addressing incorrect joints.
Although it's kind of the wrong way around i.e. we've not yet covered how to create joints, let's start by showing some common joint operations:
- getJointCount()
- getJointType( jointId )
- deleteJoint( jointId )
The "getJointCount()" operation gives a count of all the joints in the Scene. This doesn't really provide massive utility as you cannot enumerate all joints in the scene simply because that isn't useful in any real gaming scenarios. Also, joints are not referred to by any index, only by a unique Id. It would be possible to return a string containing all the joints in the Scene and you could, using TorqueScript, iterate them but that would not only be expensive, it would beg the question why?
The "getJointType( jointId )" is similar to the "getCollisionShapeType( index )" method for collision shapes in that it returns you the type name of the "jointId" you specify.
This will return one of the following:
- distance
- rope
- revolute
- weld
- wheel
- friction
- prismatic
- pulley
- target
- motor
... which is the complete joint list shown previously.
The "deleteJoint( jointId )" will simply delete the joint referred to by the specified "jointId".
To create a joint you can use one of the following joint factory methods:
- createDistanceJoint()
- createRopeJoint()
- createRevoluteJoint()
- createWeldJoint()
- createWheelJoint()
- createFrictionJoint()
- createPrismaticJoint()
- createPulleyJoint()
- createTargetJoint()
- createMotorJoint();
All of these methods either return the new joints "jointId" (which is always a positive integer) or "-1" to indicate that there was a problem creating the joint (at which point you should check the log for warnings).
// Assume we have some scene objects already added to the scene "%scene" as "%obj1" and "%obj2".
%jointId = %scene.createDistanceJoint( %obj1, %obj2 );
if ( %jointId == -1 )
{
// Joint failed to be created !
}
The basic example above shows adding a "distance" joint being created between two scene objects. It also shows that a return value of "-1" indicates a problem creating the joint. In this case and assuming the joint creation worked, the two objects would stay separated by their current distance apart; the joint would ensure that until it is deleted. As with all the other joint factory methods, they have mandatory arguments (in the above example it's only the two scene objects that the joint works with) and optional arguments that allow you to immediately configure other options. In the case of the "distance" joint you can also, amongst other things, specific the actual distance you want the objects to achieve etc.
Unlike collision shapes, joints are able to change some of their parameters after they have been created. We have exposed all of these parameters for all joints. This produced a lot of additional methods however they are named consistently (against the joint type) and so it should become easy to memorize the joint API after several uses.
To supplement the joint creation methods above, here's the list of methods that allow you to modify each joint type after it has been created:
- createDistanceJoint()
- setDistanceJointLength()
- getDistanceJointLength()
- setDistanceJointFrequency()
- getDistanceJointFrequency()
- setDistanceJointDampingRatio()
- getDistanceJointDampingRatio()
- createRopeJoint()
- setRopeJointMaxLength()
- getRopeJointMaxLength()
- createRevoluteJoint()
- setRevoluteJointLimit()
- getRevoluteJointLimit()
- setRevoluteJointMotor()
- getRevoluteJointMotor()
- createWeldJoint()
- setWeldJointFrequency()
- getWeldJointFrequency()
- setWeldJointDampingRatio()
- getWeldJointDampingRatio()
- createWheelJoint()
- setWheelJointMotor()
- getWheelJointMotor()
- setWheelJointFrequency()
- getWheelJointFrequency()
- setWheelJointDampingRatio()
- getWheelJointDampingRatio()
- createFrictionJoint()
- setFrictionJointMaxForce()
- getFrictionJointMaxForce()
- setFrictionJointMaxTorque()
- getFrictionJointMaxTorque()
- createPrismaticJoint()
- setPrismaticJointLimit()
- getPrismaticJointLimit()
- setPrismaticJointMotor()
- getPrismaticJointMotor()
- createPulleyJoint()
- createTargetJoint()
- setTargetJointTarget()
- getTargetJointTarget()
- setTargetJointFrequency()
- getTargetJointFrequency()
- setTargetJointDampingRatio()
- getTargetJointDampingRatio()
- createMotorJoint()
- setMotorJointLinearOffset()
- getMotorJointLinearOffset()
- setMotorJointAngularOffset()
- getMotorJointAngularOffset()
- setMotorJointMaxForce()
- getMotorJointMaxForce()
- setMotorJointMaxTorque()
- getMotorJointMaxTorque()
These Scene methods are defined here.
Joints will obviously be destroyed when you call the "deleteJoint( jointId )" method however joints will also be destroyed if you destroy a SceneObject to which a joint is connected. In the case of all joints, a joint is always connected to two scene objects thus if either of those scene objects is destroyed the joint itself will be destroyed. This is done internally within Box2D and stops you from having degenerate joints.
These limitations relate directly to the limitations of Box2D itself and are documented in the Box2D manual but they are important enough to mention here:
- Stacking heavy bodies on top of much lighter bodies is not stable. Stability degrades as the mass ratio passes 10:1.
- Chains of bodies connected by joints may stretch if a lighter body is supporting a heavier body. For example, a wrecking ball connect to a chain of light weight bodies may not be stable. Stability degrades as the mass ratio passes 10:1.
- There is typically around 0.5cm of slop in shape versus shape collision.
- Continuous collision does not handle joints. So you may see joint stretching on fast moving objects.
- Box2D uses the symplectic Euler integration scheme. It does not reproduce parabolic motion of projectiles and has only first-order accuracy. However it is fast and has good stability.
- Box2D uses an iterative solver to provide real-time performance. You will not get precisely rigid collisions or pixel perfect accuracy. Increasing the iterations will improve accuracy.