-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Physics
The gameplay framework supports 3-D physics using the game service/controller gameplay::PhysicsController
. The gameplay::PhysicsController
class maintains a physics world that has gravity, and will simulate the objects you add to it.
The gameplay physics system supports 3-D rigid body dynamics, including collision shapes, constraints, and a physics character class.
To simulate objects within the physics world, you need to create a gameplay::PhysicsCollisionObject
object representing the geometry, or gameplay::Model
.
By attaching a collision object to a gameplay::Node
, the rigid body will be added to the physics world and the simulation will automatically update the node's transformation.
PhysicsCollisionObject
is the base class that provides an interface for receiving collision events.
You can add collision listeners to a PhysicsCollisionObject
or test if the collision object currently collides with another collision object.
There are 3 types of collision objects:
- PhysicsRigidBody
- PhysicsGhostObject
- PhysicsCharacter
- PhyiscsVehicle
A rigid body is an idealized, infinitely hard, non-deformable solid object. Rigid bodies have mass, shape and other properties that affect forces within the simulation.
A PhysicsRigidBody can be set to be a kinematic rigid body. A kinematic rigid body is an object that is not simulated by the physics system, and instead has its transform driven manually.
A note regarding the mass of a rigid body: In GamePlay, a mass value of zero causes the object to be immovable. This value is reserved by the underlying physics engine for making objects static, (albeit admittedly counter-intuitive).
To create a rigid body, first you need to know what kind of shape you want to simulate. The physics system supports boxes, spheres, meshes, capsules, and terrain height fields. For basic shapes, such as boxes and spheres, you can programmatically create the rigid bodies by calling Node::setCollisionObject()
and passing in the desired shape type.
PhysicsRigidBody::Parameters params;
params.mass = 10.0f;
node->setCollisionObject(PhysicsCollisionObject::RIGID_BODY,
PhysicsCollisionShape::box(), ¶ms);
All other types of rigid bodies must be created using the .scene and .physics property definition files. The .scene file allows you to bind various attachments or properties to nodes, including a rigid body.
For example, to create a mesh rigid body for the node within the scene with ID equal to tree_1:
game.scene:
scene
{
...
node tree_1
{
...
collisionObject = game.physics#tree_mesh
}
...
}
game.physics:
collisionObject tree_mesh
{
type = RIGID_BODY
shape = MESH
mass = 15.0
...
}
All properties have default values if not defined. See PhysicsRigidBody::Parameters
for more information.
collisionObject <string>
{
type = <RIGID_BODY | GHOST_OBJECT | CHARACTER>
shape = <BOX | SPHERE | MESH | HEIGHTFIELD | CAPSULE>
image = <string> // only for HEIGHTFIELD
radius = <float>
height = <float>
extents = <float, float, float>
center = <float, float, float>
centerAbsolute = <float, float, float>
mass = <float>
friction = <float>
restitution = <float>
linearDamping = <float>
angularDamping = <float>
kinematic = <bool>
anisotropicFriction = <float, float, float>
gravity = <float, float, float>
}
Shapes | Properties |
---|---|
BOX | extents, center, center-absolute |
SPHERE | radius, center, center-absolute |
MESH | |
HEIGHTFIELD | image |
CAPSULE | radius, height, center, center-absolute |
A ghost object is like a rigid body except that it does not have an effect the simulation. It will not cause forces or react to the other rigid bodies. Ghost objects have a shape but they do not have mass, or any of the properties that affect forces.
Ghost objects are useful for querying the simulation, or detecting collisions without having rigid bodies react to the ghost object. A ghost object could be used to detect if an object entered a volume, such as a soccer ball going into a goal. This use of a ghost object is often called a volumetric trigger. Ghost objects can also detect if they collide with other ghost objects.
Collision objects do not require a model so you could use a ghost object to check if a camera collides with a wall.
Programmatically:
// Create a ghost object with radius 5
node->setCollisionObject(PhysicsCollisionObject::GHOST_OBJECT,
PhysicsCollisionShape::sphere(5.0f));
In a .physics file:
collisionObject powerup
{
type = GHOST_OBJECT
shape = SPHERE
radius = 5.0
}
Ghost objects only have a shape and support the same shapes as rigid bodies.
collisionObject <string>
{
type = GHOST_OBJECT
shape = <BOX | SPHERE | MESH | HEIGHTFIELD | CAPSULE>
radius = <float>
height = <float>
extents = <float, float, float>
center = <float, float, float>
centerAbsolute = <float, float, float>
image = <string> // HEIGHTFIELD shapes only.
}
The PhysicsCharacter
class can be used to control the movements and collisions of a character in a game.
It interacts with the physics system to apply gravity and handle collisions, however dynamics are not applied to the character directly by the physics system.
Instead, the character's movement is controlled directly by the PhysicsCharacter
class.
This results in a more responsive and typical game character than would be possible if trying to move a character by applying physical simulation with forces.
To programmatically create a PhysicsCharacter
with mass 20 and capsule shape:
PhysicsRigidBody::Parameters params(20.0f);
node->setCollisionObject(PhysicsCollisionObject::CHARACTER,
PhysicsCollisionShape::capsule(1.2f, 5.0f, Vector3(0, 2.5, 0), true), ¶ms);
PhysicsCharacter* character = static_cast<PhysicsCharacter*>(
node->getCollisionObject());
Physics characters have a mass and shape. A capsule is a typical shape for a typical biped character.
collisionObject <string>
{
type = CHARACTER
shape = <BOX | SPHERE | MESH | CAPSULE>
radius = <float>
height = <float>
extents = <float, float, float>
center = <float, float, float>
centerAbsolute = <float, float, float>
mass = <float>
}
The gameplay framework supports various types of constraints between two rigid bodies (or one rigid body and the physics world), including hinge, fixed, generic (six-degree-of-freedom), socket, and spring.
Constraints can be created programmatically using one of the create functions on gameplay::PhysicsController
, or they can be specified within the physics section of the .scene file.
For example, to create a hinge constraint from within a .scene file between the rigid body attached to the node with id door and the physics world:
game.scene:
scene
{
...
physics
{
...
constraint
{
type = HINGE
rigidBodyA = door
rotationOffsetA = 0.0, 1.0, 0.0, 90.0
translationOffsetA = 0.0, 0.0, 2.0
limits = 0.0, 90.0, 0.5
}
}
}
PhysicsConstraint schema
constraint <string>
{
type = <FIXED | GENERIC | HINGE | SOCKET | SPRING>
rigidBodyA = <string>
rigidBodyB = <string>
translationOffsetA = <float, float, float>
translationOffsetB = <float, float, float>
rotationOffsetA = <float>
rotationOffsetB = <float>
angularLowerLimit = <float, float, float>
angularUpperLimit = <float, float, float>
linearLowerLimit = <float, float, float>
linearUpperLimit = <float, float, float>
limits = <float, float, float>
angularDampingX = <float>
angularDampingY = <float>
angularDampingZ = <float>
angularStrengthX = <float>
angularStrengthY = <float>
angularStrengthZ = <float>
linearDampingX = <float>
linearDampingY = <float>
linearDampingZ = <float>
linearStrengthX = <float>
linearStrengthY = <float>
linearStrengthZ = <float>
breakingImpulse = <float>
}
Constraint Types | Properties |
---|---|
FIXED | |
GENERIC | translationOffsetA, translationOffsetB, rotationOffsetA, rotationOffsetB, angularLowerLimit, angularUpperLimit, linearLowerLimit, linearUpperLimit |
HINGE | translationOffsetA, translationOffsetB, rotationOffsetA, rotationOffsetB, limits |
SOCKET | translationOffsetA, translationOffsetB |
SPRING | translationOffsetA, translationOffsetB, rotationOffsetA, rotationOffsetB, angularLowerLimit, angularUpperLimit, linearLowerLimit, linearUpperLimit, angularDampingX, angularDampingY, angularDampingZ, angularStrengthX, angularStrengthY, angularStrengthZ, linearDampingX, linearDampingY, linearDampingZ, linearStrengthX, linearStrengthY, linearStrengthZ |
The gameplay framework allows you to register to be notified whenever a collision occurs between two rigid bodies (and also when two rigid bodies stop colliding).
In order to do this, you must define a class that derives from gameplay::PhysicsRigidBody::Listener
and implements the function collisionEvent()
.
Then, you must add an instance of the class as a listener on a given rigid body using the PhysicsRigidBody::addCollisionListener
function.
For example, to print all information for all collisions with the door and for collisions between the character and the wall:
MyGame.h
class MyGame: public gameplay::PhysicsRigidBody::Listener
{
public:
/// ...
/**
* Collision event handler.
*/
void collisionEvent(PhysicsRigidBody::Listener::EventType type,
const PhysicsRigidBody::CollisionPair& pair,
const Vector3& pointA, const Vector3& pointB);
// ...
};
MyGame.cpp:
MyGame* mygame;
Node* door;
Node* character;
Node* wall;
// ...
door->getRigidBody()->addCollisionListener(mygame);
character->getRigidBody()->addCollisionListener(mygame, wall);
// ...
void MyGame::collisionEvent(PhysicsRigidBody::Listener::EventType type,
const PhysicsRigidBody::CollisionPair& pair,
const Vector3& pointA, const Vector3& pointB)
{
GP_WARN("Collision between rigid bodies %s (at point (%f, %f, %f)) "
"and %s (at point (%f, %f, %f)).",
pair._rbA-getNode()-getId(), pointA.x, pointA.y, pointA.z,
pair._rbB-getNode()-getId(), pointB.x, pointB.y, pointB.z);
}
The PhysicsVehicle and PhysicsVehicleWheel classes give you access to vehicle physics for racing games. You designate a VEHICLE type collision object for the node in your scene representing the vehicle body or chassis, and VEHICLE_WHEEL for the nodes representing the wheels. When gameplay loads the scene, it attempts to automatically bind the wheels to the associated vehicle chassis. In the hierarchy of your scene be sure to locate the wheel nodes and the vehicle chassis under a common group node. This is how gameplay determines which vehicle body the wheels belong to – by searching for a common ancestor in the hierarchy. The nodes do not need to be direct descendents of the group node; they just need to appear somewhere below it in the hierarchy. The presence of other mesh nodes under the common node has no effect. All that matters is that the node of collision type VEHICLE shares a common ancestor with the nodes of collision type VEHICLE_WHEEL (details below):
Then in your .scene file, designate collision objects for the car and its wheels, like this:
scene main
{
path = res/common/game.gpb
activeCamera = camera1
node carbody
{
url = car_top
material = res/common/game.material#car
collisionObject = res/common/game.physics#car
}
node wheelFrontLeft
{
url = Left_top
material = res/common/game.material#car
collisionObject = res/common/game.physics#carWheelFrontLeft
}
node wheelFrontRight
{
url = Right_top
material = res/common/game.material#car
collisionObject = res/common/game.physics#carWheelFrontRight
}
node wheelBackLeft
{
url = Left_bottom
material = res/common/game.material#car
collisionObject = res/common/game.physics#carWheelBackLeft
}
node wheelBackRight
{
url = Right_bottom
material = res/common/game.material#car
collisionObject = res/common/game.physics#carWheelBackRight
}
//...
physics
{
gravity = 0.0, -9.8, 0.0
}
}
In the .physics file be sure to specify type VEHICLE
for the chassis and type VEHICLE_WHEEL
for the wheels:
collisionObject car
{
type = VEHICLE
shape = BOX
mass = 800.0
friction = 0.5
restitution = 0.01
linearDamping = 0.025
angularDamping = 0.6
steeringGain = 0.4
brakingForce = 350.0
drivingForce = 2000.0
steerdownSpeed = 87
steerdownGain = 0.22
brakedownStart = 100
brakedownFull = 170
drivedownStart = 105
drivedownFull = 180
boostSpeed = 74
boostGain = 2.6
downforce = 4.5
}
collisionObject carWheel
{
type = VEHICLE_WHEEL
shape = MESH
mass = 1.0
friction = 0.5
restitution = 0.01
linearDamping = 0.025
angularDamping = 0.16
wheelDirection = 0, -1, 0
wheelAxle = -1, 0, 0
strutRestLength = 0.6
strutStiffness = 25.0
strutDampingCompression = 5.1
strutDampingRelaxation = 2.3
frictionBreakout = 1000.0
wheelRadius = 0.5
rollInfluence = 0.1
strutConnectionOffset = 0.0, 0.0, 1.4
}
collisionObject carWheelFrontLeft : carWheel
{
steerable = true
}
collisionObject carWheelFrontRight : carWheel
{
steerable = true
}
collisionObject carWheelBackLeft : carWheel
{
steerable = false
}
collisionObject carWheelBackRight : carWheel
{
steerable = false
}
Practically speaking the only collision shape that makes sense right now for the vehicle chassis is BOX because currently there is a known issue with MESH collision shapes.
In the initialize() method of your Game class, you can set a member variable for accessing the vehicle via PhysicsVehicle:
Node* carNode = _scene->findNode("carbody");
if (carNode && carNode->getCollisionObject()->getType() == PhysicsCollisionObject::VEHICLE)
{
_carVehicle = static_cast<PhysicsVehicle*>(carNode->getCollisionObject());
}
Then, in the update()
method of your Game class you need to call PhysicsVehcicle::update()
with the various control inputs described below:
_carVehicle->update(elapsedTime, steering, braking, driving);
The steering
parameter controls vehicle steering and has an expected range of -1 to +1.
The braking
parameter applies wheel brakes and has an expected range of 0 to 1.
The driving
parameter lumps together engine output and overall drivetrain, with an expected range of 0 to 1.
Optional properties in the .physics definition for your vehicle give you greater control of the handling characteristics, and are described below in turn.
The following properties specify the vehicle's overall response to control inputs:
// Vehicle steering, braking, and powertrain
steeringGain = <float> // steering at full deflection
brakingForce = <float> // braking force at full braking
drivingForce = <float> // driving force at full throttle
This is an over-simplification of vehicle handling, and therefore gameplay provides further refinement as follows.
Turning the steering wheel of a real car by 1 degree has a much different effect at 100 km/h than it does at 10 km/h. (Please do not attempt this). In a real vehicle, the "feel" of a steering wheel tends to stiffen as speed increases. In a racing game, we can approximate this effect by reducing the amount of authority at higher speeds. gameplay provides the following properties to control this effect:
// Steering gain reduction with speed (optional)
steerdownSpeed = <float> // steering gain fades to this point
steerdownGain = <float> // gain value at that point (less than 1)
The gain at zero speed is always 1. The properties steerdownSpeed and steerdownGain specify a point of reduced gain, above which the gain remains constant. A steerdownGain of 1 effectively disables this feature.
Due to imperfections in the simulated physics, full braking at high speeds can cause unexpected behavior. gameplay provides the following properties to reduce braking above a certain threshold speed:
// Brake force reduction at high speeds (optional)
brakedownStart = <float> // braking fades above this speed
brakedownFull = <float> // braking is fully faded at this speed
Braking remains unaffected up to the speed specified by brakedownStart. Above that speed, braking fades and reaches zero at the speed specified by brakedownFull. An unreachably-large speed value for brakedownStart will effectively disable this feature.
Driving force is currently simplified down to a single value that lumps together the engine and drivetrain. In the absence of a proper gearbox simulation gameplay provides the following properties for affecting all-out speed and bottom-end acceleration:
// Driving force reduction at high speeds (optional)
drivedownStart = <float> // driving force fades above this speed
drivedownFull = <float> // driving force is fully faded at this speed
// Driving force boost at low speeds (optional)
boostSpeed = <float> // Boost fades to 1 at this point
boostGain = <float> // Boost at zero speed (greater than 1)
The first 2 properties allow you to reduce driving force at high speeds which limits the top speed of the vehicle. Above the speed specified by drivedownStart driving force begins to fade, and eventually reaches zero at the speed specified by drivedownFull, more or less. An unreachably-large value for drivedownStart will effectively disable this feature.
The last 2 properties allow you to increase acceleration at low speeds. boostGain specifies the gain at zero speed, so a value greater than 1 will increase vehicle acceleration from a standing start. This supplemental gain then fades to 1 at the speed specified by boostSpeed. A boostGain of 1 effectively disables this feature.
Racing cars typically make use of airfoils to produce a downward force at high speeds. This improves handling and performance. gameplay provides the following property to simulate this effect:
// Aerodynamic downforce effect (optional)
downforce = <float> // proportional control of downforce
The value of downforce controls the amount of downward force at a given speed. In particular, the value of this property represents the product of a reference area and an aerodynamic coefficient. However what's important is that this property acts as a constant of proportionality in computing the downward force as a function of speed. A value of 0 effectively disables this feature.
Tire and suspension characteristics can be specified at each individual wheel as follows:
collisionObject <wheelID>
{
type = VEHICLE_WHEEL
steerable = <bool> // indicates whether wheel is steerable
wheelDirection = <float, float, float> // direction of strut extension, in chassis space
wheelAxle = <float, float, float> // direction of axle (spin axis), in chassis space
strutConnectionOffset = <float, float, float> // offset from default strut connection point
strutRestLength = <float> // strut rest length
strutTravelMax = <float> // maximum strut travel
strutStiffness = <float> // strut stiffness, normalized to chassis mass
strutDampingCompression = <float> // strut damping under compression, normalized to chassis mass
strutDampingRelaxation = <float> // strut damping under relaxation, normalized to chassis mass
strutForceMax = <float> // maximum strut force
frictionBreakout = <float> // breakout friction
wheelRadius = <float> // wheel radius
rollInfluence = <float> // how side friction affects chassis roll, normalized
}
gameplay automatically determines a default location on the chassis for the strut connection point based on the position of the wheel nodes relative to the car body. The strutConnectionOffset property allows you to specify an offset from the default. This is useful, for example, if the origin of the car body is not located at the center of the mesh:
strutConnectionOffset = 0.0, 0.0, 1.4