Skip to content

Instantly share code, notes, and snippets.

@dreasgrech
Last active September 26, 2023 09:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dreasgrech/3c09c632d24ab331eb00f5637c534aa1 to your computer and use it in GitHub Desktop.
Save dreasgrech/3c09c632d24ab331eb00f5637c534aa1 to your computer and use it in GitHub Desktop.
Resolving collisions using Matter physics with Phaser 3 in constant O(1) time
/*
This code shows how to resolve collisions using Matter physics with Phaser 3 in constant O(1) time.
Going through all the collision pairs is still linear O(n) time depending on how many collisions happened this frame
but the code that handles the resolution of the collision is constant and will not change with the total number of CollisionCategories / collision-lookups.
The way the code works is by generating a number based on the each of the collision combinations, use that number as a key for storing a pointer to the respective collision handler function,
and then when a collision happens, calculate the number again of both bodies' collision categories and use that number to fetch the collision handler function. Simple.
// dreasgrech - https://github.com/dreasgrech/
*/
/*
When a Matter GameObject is created, its body is assigned one of the below CollisionCategories to the collision category
and its collision mask set to a combination of a number of Collision Categories.
Ex:
const collisionFilter = robotBody.collisionFilter;
collisionFilter.category = CollisionCategories.Robot;
collisionFilter.mask = CollisionCategories.RobotBody | CollisionCategories.Arena; // A Robot can collide with another robot and the arena
*/
const CollisionCategories = {
RobotBody: 1,
RobotTurret: 2,
Arena: 4,
RobotProjectile: 8,
};
// Holds the keys that map to the different collision handlers that can happen
const collisionHandlers = {};
// This create() is called from a Phaser create() function
const create = function() {
// Set up all the collision handlers lookups. This is the matrix which allows for the collision handler resolution
collisionHandlers[createLookupKey(CollisionCategories.RobotBody, CollisionCategories.RobotBody)] = handleCollision_RobotToRobot; // A robot can collide with another robot
collisionHandlers[createLookupKey(CollisionCategories.RobotProjectileSensor, CollisionCategories.RobotProjectile)] = handleCollision_RobotProjectileSensorToProjectile; // A projectile can collide with a projectile sensor
collisionHandlers[createLookupKey(CollisionCategories.RobotBody, CollisionCategories.Arena)] = handleCollision_RobotToArena; // A robot can collide with the arena
collisionHandlers[createLookupKey(CollisionCategories.RobotProjectile, CollisionCategories.RobotProjectile)] = handleCollision_ProjectileToProjectile; // A projectile can collide with another projectile
collisionHandlers[createLookupKey(CollisionCategories.RobotProjectile, CollisionCategories.Arena)] = handleCollision_ProjectileToArena; // A projectile can collide with the arena
};
// The 'collisionstart' callback
const handleEvent_CollisionStart = function(event) {
const eventPairs = event.pairs;
const eventPairsLength = eventPairs.length;
for (let i = 0; i < eventPairsLength; ++i) {
const matterCollisionData = eventPairs[i];
// Fetch bodyA's collision category
const bodyA = matterCollisionData.bodyA;
const bodyA_parent = bodyA.parent;
const bodyA_CollisionCategory = bodyA_parent.collisionFilter.category;
// Fetch bodyB's collision category
const bodyB = matterCollisionData.bodyB;
const bodyB_parent = bodyB.parent;
const bodyB_CollisionCategory = bodyB_parent.collisionFilter.category;
// Resolve the lookup key based on the physics category
const collisionLookupKey = createLookupKey(bodyA_CollisionCategory, bodyB_CollisionCategory);
const collisionHandler = collisionHandlers[collisionLookupKey];
// Execute the collision handler
// TODO: Remove this branch when you're sure you have all the collision handlers because then collisionHandler will never be null
if (collisionHandler != null) {
collisionHandler(bodyA, bodyB);
} else {
console.error('Unable to find collision handler for', bodyA_CollisionCategory, 'and', bodyB_CollisionCategory, '. Key:', collisionLookupKey);
}
}
};
// Hook to the collisionstart event
gameContext.matter.world.on('collisionstart', handleEvent_CollisionStart);
// COLLISION HANDLERS:
// Robot to Robot collision handler
const handleCollision_RobotToRobot = function(robotBodyA, robotBodyB) {
// Both matter bodies belong to robots
// ...
};
// Robot Projectile Sensor to Projectile collision handler
const handleCollision_RobotProjectileSensorToProjectile = function(matterBodyA, matterBodyB) {
// Determine which body is which
const isBodyA_RobotProjectileSensor = matterBodyA.collisionFilter.category & CollisionCategories.RobotProjectileSensor;
const robotProjectileSensorMatterBody = isBodyA_RobotProjectileSensor ? matterBodyA : matterBodyB;
const projectileMatterBody = isBodyA_RobotProjectileSensor ? matterBodyB : matterBodyA;
const projectileMatterGameObject = projectileMatterBody.parent.gameObject;
// ...
};
// Robot to Arena collision handler
const handleCollision_RobotToArena = function(matterBodyA, matterBodyB) {
// Determine which body is which
const isBodyA_Arena = matterBodyA.collisionFilter.category & CollisionCategories.Arena;
const robotMatterBody = isBodyA_Arena ? matterBodyB : matterBodyA;
const arenaMatterBody = isBodyA_Arena ? matterBodyA : matterBodyB;
// ...
};
// Projectile to Projectile collision handler
const handleCollision_ProjectileToProjectile = function(projectileMatterBodyA, projectileMatterBodyB) {
// Both matter bodies belong to projectiles
// ...
};
// Projectile to Arena collision handler
const handleCollision_ProjectileToArena = function(matterBodyA, matterBodyB) {
// Determine which body is which
const isBodyA_Arena = matterBodyA.collisionFilter.category & CollisionCategories.Arena;
const projectileMatterBody = isBodyA_Arena ? matterBodyB : matterBodyA;
const arenaBody = isBodyA_Arena ? matterBodyA : matterBodyB;
// ...
};
// HELPER FUNCTIONS:
// Creates a lookup key based on two numbers.
// Inversing the numbers returns the same results
const createLookupKey = function(number1, number2) {
var key = Math.min(number1, number2) * 1000 + Math.max(number1, number2);
if (isNaN(key)) {
throw `${number1} and ${number2} have created a NaN key!`;
}
return key;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment