$npx -y skills add Yakoub-ai/phaser4-gamedev --skill phaser-physicsThis skill should be used when the user asks to "add physics", "set up collisions", "implement gravity", "create a platformer", "add physics to sprite", "detect overlaps", "create collision groups", "make objects bounce", "top-down movement", "set velocity", "apply forces", "enab
| 1 | # Phaser 4 Arcade Physics |
| 2 | |
| 3 | Phaser 4 uses Arcade Physics — fast AABB (axis-aligned bounding box) simulation. No real curves or rotation-based collisions, but excellent for most 2D games. |
| 4 | |
| 5 | ## Enable Physics |
| 6 | |
| 7 | In `main.ts` GameConfig: |
| 8 | |
| 9 | ```typescript |
| 10 | const config: Phaser.Types.Core.GameConfig = { |
| 11 | // ... |
| 12 | physics: { |
| 13 | default: 'arcade', |
| 14 | arcade: { |
| 15 | gravity: { x: 0, y: 300 }, // world gravity (adjust per genre) |
| 16 | debug: true, // set false for production |
| 17 | }, |
| 18 | }, |
| 19 | }; |
| 20 | ``` |
| 21 | |
| 22 | **Gravity values by genre:** |
| 23 | - Platformer: `{ x: 0, y: 500 }` to `{ x: 0, y: 800 }` |
| 24 | - Top-down: `{ x: 0, y: 0 }` (no gravity) |
| 25 | - Space shooter: `{ x: 0, y: 0 }` (no gravity) |
| 26 | - Pinball: `{ x: 0, y: 600 }` with high bounce |
| 27 | |
| 28 | ## Creating Physics Bodies |
| 29 | |
| 30 | ```typescript |
| 31 | // Dynamic body — affected by gravity, velocity, collisions |
| 32 | const player = this.physics.add.sprite(x, y, 'player'); |
| 33 | const enemy = this.physics.add.sprite(x, y, 'enemy'); |
| 34 | const coin = this.physics.add.sprite(x, y, 'coin'); |
| 35 | |
| 36 | // Static body — never moves, others bounce off it |
| 37 | const ground = this.physics.add.staticSprite(x, y, 'platform'); |
| 38 | const wall = this.physics.add.staticImage(x, y, 'wall'); |
| 39 | |
| 40 | // Static group — collection of immovable objects |
| 41 | const platforms = this.physics.add.staticGroup(); |
| 42 | platforms.create(400, 568, 'ground'); |
| 43 | platforms.create(100, 400, 'platform'); |
| 44 | platforms.create(600, 300, 'platform'); |
| 45 | // NOTE: After moving a static body, call staticGroup.refresh() to update physics |
| 46 | |
| 47 | // Dynamic group with pooling |
| 48 | const enemies = this.physics.add.group({ |
| 49 | classType: Enemy, |
| 50 | runChildUpdate: true, |
| 51 | }); |
| 52 | ``` |
| 53 | |
| 54 | ## Body Configuration |
| 55 | |
| 56 | ```typescript |
| 57 | const body = player.body as Phaser.Physics.Arcade.Body; |
| 58 | |
| 59 | // Velocity — pixels per second |
| 60 | player.setVelocity(vx, vy); |
| 61 | player.setVelocityX(200); |
| 62 | player.setVelocityY(-400); // negative = up |
| 63 | |
| 64 | // Acceleration — adds to velocity each second |
| 65 | body.setAccelerationX(100); |
| 66 | body.setAccelerationY(0); |
| 67 | |
| 68 | // Maximum velocity cap |
| 69 | body.setMaxVelocity(300, 600); // max x and y |
| 70 | body.setMaxVelocityX(300); |
| 71 | |
| 72 | // Deceleration (friction when no input) |
| 73 | body.setDrag(100, 0); // x drag, y drag |
| 74 | body.setDragX(200); // horizontal friction only |
| 75 | |
| 76 | // Bounce (0 = no bounce, 1 = perfect bounce) |
| 77 | player.setBounce(0.2); |
| 78 | player.setBounceX(0); |
| 79 | player.setBounceY(0.4); |
| 80 | |
| 81 | // Hitbox size and offset (often smaller than visual sprite) |
| 82 | body.setSize(24, 40); // hitbox width, height |
| 83 | body.setOffset(4, 8); // offset from top-left of sprite |
| 84 | |
| 85 | // World boundary collision |
| 86 | player.setCollideWorldBounds(true); |
| 87 | // Note: world bounds default to canvas size; expand for big levels: |
| 88 | this.physics.world.setBounds(0, 0, worldWidth, worldHeight); |
| 89 | |
| 90 | // Gravity override for specific body |
| 91 | body.setGravityY(-300); // counteract world gravity |
| 92 | body.setAllowGravity(false); // disable gravity for this body (float) |
| 93 | |
| 94 | // Immovable (static-like but in a dynamic group) |
| 95 | body.setImmovable(true); // won't move when hit, but still detects |
| 96 | ``` |
| 97 | |
| 98 | ## Colliders and Overlaps |
| 99 | |
| 100 | ```typescript |
| 101 | // Collider — objects physically collide (physics response) |
| 102 | this.physics.add.collider(player, platforms); |
| 103 | this.physics.add.collider(player, enemies, this.hitEnemy, undefined, this); |
| 104 | this.physics.add.collider(enemies, platforms); |
| 105 | |
| 106 | // Overlap — trigger callback without physics bounce |
| 107 | this.physics.add.overlap(player, coins, this.collectCoin, undefined, this); |
| 108 | this.physics.add.overlap(player, powerups, this.collectPowerup, undefined, this); |
| 109 | |
| 110 | // Collider/Overlap callback signature |
| 111 | private hitEnemy( |
| 112 | playerObj: Phaser.Types.Physics.Arcade.GameObjectWithBody, |
| 113 | enemyObj: Phaser.Types.Physics.Arcade.GameObjectWithBody |
| 114 | ): void { |
| 115 | const player = playerObj as Phaser.Physics.Arcade.Sprite; |
| 116 | const enemy = enemyObj as Phaser.Physics.Arcade.Sprite; |
| 117 | this.playerHit(player, enemy); |
| 118 | } |
| 119 | |
| 120 | // Conditional collider (process callback — return true to proceed) |
| 121 | this.physics.add.collider( |
| 122 | player, |
| 123 | onewayPlatforms, |
| 124 | undefined, // no callback |
| 125 | (playerObj, platformObj) => { |
| 126 | // Only collide if player is falling down through platform |
| 127 | const body = (playerObj as Phaser.Physics.Arcade.Sprite) |
| 128 | .body as Phaser.Physics.Arcade.Body; |
| 129 | return body.velocity.y > 0; |
| 130 | }, |
| 131 | this |
| 132 | ); |
| 133 | ``` |
| 134 | |
| 135 | ## Genre Physics Recipes |
| 136 | |
| 137 | ### Platformer |
| 138 | |
| 139 | ```typescript |
| 140 | // GameConfig |
| 141 | physics: { default: 'arcade', arcade: { gravity: { y: 500 }, debug: true } } |
| 142 | |
| 143 | // Player setup |
| 144 | this.player = this.physics.add.sprite(100, 400, 'player'); |
| 145 | this.player.setBounce(0.1); |
| 146 | this.player.setCollideWorldBounds(true); |
| 147 | (this.player.body as Phaser.Physics.Arcade.Body) |
| 148 | .setSize(24, 40).setOffs |