Last active
February 4, 2024 14:24
-
-
Save Jelleebeen/fb274f5c9082626184917a8b82b7dea8 to your computer and use it in GitHub Desktop.
Resolving collisions using Matter physics with Phaser 3 in constant O(1) time - Port to Typescript
This file contains hidden or 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/ | |
| // TS example - Jelleebeen - https://github.com/jelleebeen/ | |
| */ | |
| /* | |
| 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 | |
| */ | |
| enum CollisionCategories { | |
| RobotBody = 1, | |
| RobotTurret = 2, | |
| Arena = 4, | |
| RobotProjectile = 8, | |
| RobotProjectileSensor = 9 | |
| }; | |
| // A type for the collision handler functions created later, to store in the collision handler hash map. | |
| type CollisionHandler = (bodyA: MatterJS.BodyType, bodyB: MatterJS.BodyType) => void | |
| // Collision handler hash map, holds the keys that map to the different collision handlers that can happen | |
| const collisionHandlers: Map<number, CollisionHandler> = new Map<number, CollisionHandler>() | |
| // 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.set( | |
| createLookupKey(CollisionCategories.RobotBody, CollisionCategories.RobotBody), | |
| handleCollision_RobotToRobot | |
| ) // A robot can collide with another robot | |
| collisionHandlers.set( | |
| createLookupKey(CollisionCategories.RobotProjectileSensor, CollisionCategories.RobotProjectile), | |
| handleCollision_RobotProjectileSensorToProjectile | |
| ) // A projectile can collide with a projectile sensor | |
| collisionHandlers.set( | |
| createLookupKey(CollisionCategories.RobotBody, CollisionCategories.Arena), handleCollision_RobotToArena | |
| ) // A robot can collide with the arena | |
| collisionHandlers.set( | |
| createLookupKey(CollisionCategories.RobotProjectile, CollisionCategories.RobotProjectile), | |
| handleCollision_ProjectileToProjectile | |
| ) // A projectile can collide with another projectile | |
| collisionHandlers.set( | |
| createLookupKey(CollisionCategories.RobotProjectile, CollisionCategories.Arena), | |
| handleCollision_ProjectileToArena | |
| ) // A projectile can collide with the arena | |
| } | |
| // The 'collisionstart' callback | |
| const handleEvent_CollisionStart = function(event: Phaser.Physics.Matter.Events.CollisionStartEvent) { | |
| 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.get(collisionLookupKey) | |
| if (collisionHandler == undefined) { | |
| console.error('Unable to find collision handler for', bodyA_CollisionCategory, 'and', bodyB_CollisionCategory, '. Key:', collisionLookupKey) | |
| return | |
| } | |
| // Execute the collision handler | |
| collisionHandler(bodyA, bodyB) | |
| } | |
| }; | |
| // Hook to the collisionstart event, placed in your scene and then uncommented. | |
| //this.matter.world.on('collisionstart', handleEvent_CollisionStart); | |
| // COLLISION HANDLERS: | |
| // Robot to Robot collision handler | |
| const handleCollision_RobotToRobot: CollisionHandler = function(robotBodyA, robotBodyB) { | |
| // Both matter bodies belong to robots | |
| // ... | |
| } | |
| // Robot Projectile Sensor to Projectile collision handler | |
| const handleCollision_RobotProjectileSensorToProjectile: CollisionHandler = 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: CollisionHandler = 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: CollisionHandler = function(projectileMatterBodyA, projectileMatterBodyB) { | |
| // Both matter bodies belong to projectiles | |
| // ... | |
| } | |
| // Projectile to Arena collision handler | |
| const handleCollision_ProjectileToArena: CollisionHandler = 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: number, number2: number) { | |
| var key = Math.min(number1, number2) * 1000 + Math.max(number1, number2) | |
| if (Number.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