Last active
September 26, 2023 09:14
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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