Примеры Phaser.js

Примеры игрового движка Phaser.js, включая 2D-игры, анимацию, физику, спрайты и интерактивные игровые механики

💻 Phaser.js Hello World javascript

🟢 simple

Базовая настройка Phaser.js и примеры Hello World со спрайтами, текстом и простыми взаимодействиями

⏱️ 20 min 🏷️ phaser, game, 2d, sprite, physics
Prerequisites: JavaScript basics, HTML5 Canvas basics
// Phaser.js Hello World Examples

// 1. Basic Phaser Game Setup
import Phaser from 'phaser';

class HelloWorldScene extends Phaser.Scene {
    constructor() {
        super('HelloWorld');
    }

    preload() {
        // Load assets
        this.load.image('sky', 'assets/sky.png');
        this.load.image('ground', 'assets/platform.png');
        this.load.image('star', 'assets/star.png');
        this.load.spritesheet('dude', 'assets/dude.png', {
            frameWidth: 32,
            frameHeight: 48
        });
    }

    create() {
        // Add background
        this.add.image(400, 300, 'sky');

        // Add platforms
        const platforms = this.physics.add.staticGroup();
        platforms.create(400, 568, 'ground').setScale(2).refreshBody();
        platforms.create(600, 400, 'ground');
        platforms.create(50, 250, 'ground');
        platforms.create(750, 220, 'ground');

        // Add player
        const player = this.physics.add.sprite(100, 450, 'dude');
        player.setBounce(0.2);
        player.setCollideWorldBounds(true);
        this.physics.add.collider(player, platforms);

        // Add animations
        this.anims.create({
            key: 'left',
            frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
            frameRate: 10,
            repeat: -1
        });

        this.anims.create({
            key: 'turn',
            frames: [{ key: 'dude', frame: 4 }],
            frameRate: 20
        });

        this.anims.create({
            key: 'right',
            frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
            frameRate: 10,
            repeat: -1
        });

        // Add stars
        const stars = this.physics.add.group({
            key: 'star',
            repeat: 11,
            setXY: { x: 12, y: 0, stepX: 70 }
        });

        stars.children.entries.forEach(child => {
            child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
        });
        this.physics.add.collider(stars, platforms);
        this.physics.add.overlap(player, stars, this.collectStar, null, this);

        // Score text
        const scoreText = this.add.text(16, 16, 'Score: 0', {
            fontSize: '32px',
            color: '#000'
        });

        // Store references
        this.player = player;
        this.stars = stars;
        this.scoreText = scoreText;
        this.score = 0;

        // Add Hello World text
        this.add.text(400, 100, 'Hello Phaser World!', {
            fontSize: '48px',
            color: '#fff',
            backgroundColor: '#000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5);

        // Controls
        this.cursors = this.input.keyboard.createCursorKeys();
    }

    update() {
        const { cursors, player } = this;

        if (cursors.left.isDown) {
            player.setVelocityX(-160);
            player.anims.play('left', true);
        } else if (cursors.right.isDown) {
            player.setVelocityX(160);
            player.anims.play('right', true);
        } else {
            player.setVelocityX(0);
            player.anims.play('turn');
        }

        if (cursors.up.isDown && player.body.touching.down) {
            player.setVelocityY(-330);
        }
    }

    collectStar(player, star) {
        star.disableBody(true, true);
        this.score += 10;
        this.scoreText.setText('Score: ' + this.score);
    }
}

// 2. Game Configuration
const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 300 },
            debug: false
        }
    },
    scene: HelloWorldScene
};

// 3. Initialize Game
const game = new Phaser.Game(config);

// 4. Simple Text Only Game
class TextGame extends Phaser.Scene {
    constructor() {
        super('TextGame');
    }

    create() {
        // Add text elements
        this.add.text(400, 200, 'Hello Phaser!', {
            fontSize: '64px',
            color: '#ff0000',
            fontFamily: 'Arial'
        }).setOrigin(0.5);

        this.add.text(400, 300, 'Welcome to Game Development', {
            fontSize: '32px',
            color: '#00ff00',
            fontFamily: 'Arial'
        }).setOrigin(0.5);

        // Interactive text
        const clickText = this.add.text(400, 400, 'Click Me!', {
            fontSize: '24px',
            color: '#0000ff',
            backgroundColor: '#ffff00',
            padding: { x: 10, y: 5 }
        }).setOrigin(0.5).setInteractive();

        clickText.on('pointerdown', () => {
            clickText.setColor('#ff00ff');
            clickText.setText('Clicked!');
        });
    }
}

// 5. Simple Animation Example
class AnimationExample extends Phaser.Scene {
    constructor() {
        super('AnimationExample');
    }

    preload() {
        // Create colored rectangles as placeholders for sprites
        this.add.graphics()
            .fillStyle(0xff0000)
            .fillRect(0, 0, 64, 64)
            .generateTexture('redBox', 64, 64);

        this.add.graphics()
            .fillStyle(0x00ff00)
            .fillRect(0, 0, 64, 64)
            .generateTexture('greenBox', 64, 64);

        this.add.graphics()
            .fillStyle(0x0000ff)
            .fillRect(0, 0, 64, 64)
            .generateTexture('blueBox', 64, 64);
    }

    create() {
        // Create sprite with tweens
        const sprite = this.add.sprite(400, 300, 'redBox');

        // Scale animation
        this.tweens.add({
            targets: sprite,
            scaleX: 2,
            scaleY: 2,
            duration: 2000,
            ease: 'Power2',
            yoyo: true,
            repeat: -1
        });

        // Rotation animation
        const rotatingSprite = this.add.sprite(200, 200, 'greenBox');
        this.tweens.add({
            targets: rotatingSprite,
            angle: 360,
            duration: 3000,
            repeat: -1
        });

        // Position animation
        const movingSprite = this.add.sprite(600, 200, 'blueBox');
        this.tweens.add({
            targets: movingSprite,
            x: 200,
            y: 400,
            duration: 2500,
            ease: 'Sine.inOut',
            yoyo: true,
            repeat: -1
        });

        this.add.text(400, 500, 'Animation Examples', {
            fontSize: '24px',
            color: '#ffffff'
        }).setOrigin(0.5);
    }
}

// 6. Mouse Interaction Example
class MouseInteraction extends Phaser.Scene {
    constructor() {
        super('MouseInteraction');
    }

    create() {
        // Create interactive sprites
        for (let i = 0; i < 5; i++) {
            const x = Phaser.Math.Between(50, 750);
            const y = Phaser.Math.Between(50, 550);
            const color = Phaser.Math.Between(0, 0xffffff);

            const sprite = this.add.circle(x, y, 30, color).setInteractive();

            sprite.on('pointerover', () => {
                sprite.setScale(1.5);
                sprite.setTint(0xff0000);
            });

            sprite.on('pointerout', () => {
                sprite.setScale(1);
                sprite.clearTint();
            });

            sprite.on('pointerdown', () => {
                sprite.setFillStyle(Phaser.Math.Between(0, 0xffffff));
            });
        }

        this.add.text(400, 30, 'Hover and Click the Circles!', {
            fontSize: '20px',
            color: '#ffffff'
        }).setOrigin(0.5);
    }
}

// 7. Particle System Example
class ParticleExample extends Phaser.Scene {
    constructor() {
        super('ParticleExample');
    }

    create() {
        // Create particle system
        const particles = this.add.particles(0, 0, 'redBox', {
            x: 400,
            y: 300,
            speed: { min: -100, max: 100 },
            angle: { min: 0, max: 360 },
            scale: { start: 1, end: 0 },
            lifespan: 2000,
            emitting: false
        });

        const text = this.add.text(400, 500, 'Click to Emit Particles', {
            fontSize: '24px',
            color: '#ffffff'
        }).setOrigin(0.5).setInteractive();

        text.on('pointerdown', () => {
            particles.explode(50);
        });
    }
}

// 8. Game State Management Example
class GameStateExample extends Phaser.Scene {
    constructor() {
        super('GameStateExample');
        this.gameState = {
            score: 0,
            lives: 3,
            level: 1,
            gameOver: false
        };
    }

    create() {
        // Create game state display
        this.scoreText = this.add.text(16, 16, `Score: ${this.gameState.score}`, {
            fontSize: '20px',
            color: '#ffffff'
        });

        this.livesText = this.add.text(16, 50, `Lives: ${this.gameState.lives}`, {
            fontSize: '20px',
            color: '#ffffff'
        });

        this.levelText = this.add.text(16, 84, `Level: ${this.gameState.level}`, {
            fontSize: '20px',
            color: '#ffffff'
        });

        // Create buttons to modify game state
        const scoreButton = this.add.text(400, 200, 'Add Score', {
            fontSize: '24px',
            backgroundColor: '#00ff00',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setInteractive();

        scoreButton.on('pointerdown', () => {
            this.gameState.score += 10;
            this.updateDisplay();
        });

        const loseLifeButton = this.add.text(400, 300, 'Lose Life', {
            fontSize: '24px',
            backgroundColor: '#ff0000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setInteractive();

        loseLifeButton.on('pointerdown', () => {
            this.gameState.lives--;
            this.updateDisplay();

            if (this.gameState.lives <= 0) {
                this.gameOver();
            }
        });

        const levelButton = this.add.text(400, 400, 'Next Level', {
            fontSize: '24px',
            backgroundColor: '#0000ff',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setInteractive();

        levelButton.on('pointerdown', () => {
            this.gameState.level++;
            this.updateDisplay();
        });
    }

    updateDisplay() {
        this.scoreText.setText(`Score: ${this.gameState.score}`);
        this.livesText.setText(`Lives: ${this.gameState.lives}`);
        this.levelText.setText(`Level: ${this.gameState.level}`);
    }

    gameOver() {
        this.gameState.gameOver = true;
        this.add.text(400, 300, 'GAME OVER', {
            fontSize: '64px',
            color: '#ff0000',
            backgroundColor: '#000000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5);
    }
}

export {
    HelloWorldScene,
    TextGame,
    AnimationExample,
    MouseInteraction,
    ParticleExample,
    GameStateExample
};

💻 Анимация спрайтов и твенинг javascript

🟡 intermediate ⭐⭐⭐

Продвинутая анимация спрайтов, спрайтовые листы, твенинг и движение персонажей в Phaser.js

⏱️ 30 min 🏷️ phaser, animation, sprite, effects
Prerequisites: Phaser.js basics, JavaScript ES6+, Animation concepts
// Phaser Sprite Animation and Tweening Examples

import Phaser from 'phaser';

// 1. Character Animation System
class CharacterAnimation extends Phaser.Scene {
    constructor() {
        super('CharacterAnimation');
        this.character = null;
        this.cursors = null;
        this.wasMoving = false;
    }

    preload() {
        // Create placeholder sprite sheet
        this.createPlayerSpritesheet();
        this.load.spritesheet('player', 'player_spritesheet', {
            frameWidth: 32,
            frameHeight: 48
        });
    }

    createPlayerSpritesheet() {
        // Create simple colored rectangles for each animation frame
        const graphics = this.add.graphics();

        // Idle frames (frames 0-1)
        for (let i = 0; i < 2; i++) {
            graphics.clear();
            graphics.fillStyle(0x00ff00);
            graphics.fillRect(i * 32, 0, 32, 48);
            graphics.fillStyle(0x000000);
            graphics.fillRect(i * 32 + 8, 8, 6, 6); // left eye
            graphics.fillRect(i * 32 + 18, 8, 6, 6); // right eye
        }

        // Walk frames (frames 2-7)
        for (let i = 2; i < 8; i++) {
            graphics.clear();
            graphics.fillStyle(0x0000ff);
            graphics.fillRect(i * 32, 0, 32, 48);

            // Animated legs
            const legOffset = (i % 2) * 4;
            graphics.fillRect(i * 32 + 10, 32 + legOffset, 4, 16);
            graphics.fillRect(i * 32 + 18, 32 - legOffset, 4, 16);
        }

        // Jump frame (frame 8)
        graphics.clear();
        graphics.fillStyle(0xff0000);
        graphics.fillRect(8 * 32, 0, 32, 48);

        graphics.generateTexture('player_spritesheet', 9 * 32, 48);
        graphics.destroy();
    }

    create() {
        // Create character
        this.character = this.physics.add.sprite(400, 300, 'player');
        this.character.setCollideWorldBounds(true);

        // Create animations
        this.createAnimations();

        // Setup controls
        this.cursors = this.input.keyboard.createCursorKeys();
        this.wasd = this.input.keyboard.addKeys('W,S,A,D');

        // Display instructions
        this.add.text(400, 50, 'Use Arrow Keys or WASD to Move', {
            fontSize: '20px',
            color: '#ffffff'
        }).setOrigin(0.5);

        // Character info display
        this.stateText = this.add.text(16, 16, 'State: Idle', {
            fontSize: '16px',
            color: '#ffffff'
        });
    }

    createAnimations() {
        // Idle animation
        this.anims.create({
            key: 'idle',
            frames: [{ key: 'player', frame: 0 }],
            frameRate: 1,
            repeat: -1
        });

        // Walk animation
        this.anims.create({
            key: 'walk',
            frames: this.anims.generateFrameNumbers('player', { start: 2, end: 7 }),
            frameRate: 10,
            repeat: -1
        });

        // Jump animation
        this.anims.create({
            key: 'jump',
            frames: [{ key: 'player', frame: 8 }],
            frameRate: 1
        });
    }

    update() {
        const { cursors, wasd, character } = this;
        const left = cursors.left.isDown || wasd.A.isDown;
        const right = cursors.right.isDown || wasd.D.isDown;
        const up = cursors.up.isDown || wasd.W.isDown;

        let isMoving = false;
        let currentState = 'Idle';

        // Horizontal movement
        if (left) {
            character.setVelocityX(-200);
            character.setFlipX(true);
            isMoving = true;
            currentState = 'Walking';
        } else if (right) {
            character.setVelocityX(200);
            character.setFlipX(false);
            isMoving = true;
            currentState = 'Walking';
        } else {
            character.setVelocityX(0);
        }

        // Jump
        if (up && character.body.touching.down) {
            character.setVelocityY(-400);
            character.anims.play('jump');
            currentState = 'Jumping';
        }

        // Play appropriate animation
        if (!character.body.touching.down) {
            // Keep jump animation when in air
            if (!character.anims.isPlaying || character.anims.currentAnim.key !== 'jump') {
                character.anims.play('jump');
            }
        } else if (isMoving) {
            character.anims.play('walk', true);
        } else {
            character.anims.play('idle');
        }

        // Update state display
        this.stateText.setText(`State: ${currentState}`);
        this.wasMoving = isMoving;
    }
}

// 2. Advanced Tweening System
class TweeningSystem extends Phaser.Scene {
    constructor() {
        super('TweeningSystem');
        this.sprites = [];
    }

    create() {
        this.createTweeningExamples();
        this.createUI();
    }

    createTweeningExamples() {
        // Linear movement
        const sprite1 = this.add.rectangle(100, 100, 50, 50, 0xff0000);
        this.tweens.add({
            targets: sprite1,
            x: 700,
            duration: 2000,
            ease: 'Linear',
            repeat: -1,
            yoyo: true
        });

        // Easing examples
        const easeTypes = ['Power0', 'Power1', 'Power2', 'Power3', 'Sine', 'Back', 'Elastic', 'Bounce'];
        easeTypes.forEach((ease, index) => {
            const x = 100 + (index % 4) * 180;
            const y = 200 + Math.floor(index / 4) * 100;
            const color = Phaser.Math.Between(0x100000, 0xffffff);

            const sprite = this.add.circle(x, y, 20, color);

            this.tweens.add({
                targets: sprite,
                x: x + 140,
                duration: 2000,
                ease: ease,
                repeat: -1,
                yoyo: true,
                delay: index * 100
            });

            // Label
            this.add.text(x + 70, y - 30, ease, {
                fontSize: '12px',
                color: '#ffffff'
            }).setOrigin(0.5);
        });

        // Complex path animation
        const pathSprite = this.add.rectangle(400, 400, 40, 40, 0x00ff00);
        this.tweens.timeline({
            targets: pathSprite,
            loop: -1,
            tweens: [
                {
                    x: 200,
                    y: 300,
                    duration: 500,
                    ease: 'Power2'
                },
                {
                    x: 600,
                    y: 300,
                    duration: 1000,
                    ease: 'Sine.inOut'
                },
                {
                    x: 600,
                    y: 500,
                    duration: 500,
                    ease: 'Power2'
                },
                {
                    x: 200,
                    y: 500,
                    duration: 1000,
                    ease: 'Sine.inOut'
                },
                {
                    x: 200,
                    y: 300,
                    duration: 500,
                    ease: 'Power2'
                },
                {
                    x: 400,
                    y: 400,
                    duration: 500,
                    ease: 'Power2'
                }
            ]
        });
    }

    createUI() {
        this.add.text(400, 30, 'Tweening Examples - Different Easing Functions', {
            fontSize: '24px',
            color: '#ffffff'
        }).setOrigin(0.5);
    }
}

// 3. Particle Effects System
class ParticleEffects extends Phaser.Scene {
    constructor() {
        super('ParticleEffects');
        this.particleManager = null;
    }

    preload() {
        // Create particle textures
        this.createParticleTextures();
    }

    createParticleTextures() {
        // Create star particle
        const starGraphics = this.add.graphics();
        starGraphics.fillStyle(0xffff00);
        starGraphics.beginPath();
        for (let i = 0; i < 5; i++) {
            const angle = (i * 72 - 90) * Math.PI / 180;
            const x = Math.cos(angle) * 15;
            const y = Math.sin(angle) * 15;
            if (i === 0) starGraphics.moveTo(x, y);
            else starGraphics.lineTo(x, y);

            const innerAngle = ((i * 72 + 36) - 90) * Math.PI / 180;
            const innerX = Math.cos(innerAngle) * 7;
            const innerY = Math.sin(innerAngle) * 7;
            starGraphics.lineTo(innerX, innerY);
        }
        starGraphics.closePath();
        starGraphics.fillPath();
        starGraphics.generateTexture('star_particle', 30, 30);
        starGraphics.destroy();
    }

    create() {
        this.add.text(400, 30, 'Click to Create Different Particle Effects', {
            fontSize: '20px',
            color: '#ffffff'
        }).setOrigin(0.5);

        // Create different particle effects
        this.createExplosionEffect();
        this.createTrailEffect();
        this.createFountainEffect();
        this.createRainEffect();
    }

    createExplosionEffect() {
        const explosionZone = this.add.zone(200, 200, 100, 100).setInteractive();
        this.add.graphics().lineStyle(2, 0xff0000).strokeRect(150, 150, 100, 100);
        this.add.text(200, 120, 'Explosion', { fontSize: '16px', color: '#ffffff' }).setOrigin(0.5);

        explosionZone.on('pointerdown', (pointer) => {
            const particles = this.add.particles(pointer.x, pointer.y, 'star_particle', {
                speed: { min: -300, max: 300 },
                angle: { min: 0, max: 360 },
                scale: { start: 0.5, end: 0 },
                lifespan: 1500,
                quantity: 30,
                blendMode: 'ADD'
            });
            particles.explode();
        });
    }

    createTrailEffect() {
        const trailSprite = this.add.circle(400, 300, 20, 0x00ff00);

        const trailParticles = this.add.particles(trailSprite.x, trailSprite.y, 'star_particle', {
            scale: { start: 0.3, end: 0 },
            speed: 50,
            lifespan: 500,
            quantity: 2
        });

        this.tweens.add({
            targets: trailSprite,
            x: 600,
            y: 300,
            duration: 2000,
            ease: 'Sine.inOut',
            yoyo: true,
            repeat: -1,
            onUpdate: () => {
                trailParticles.setPosition(trailSprite.x, trailSprite.y);
            }
        });

        this.add.text(400, 250, 'Trail Effect', { fontSize: '16px', color: '#ffffff' }).setOrigin(0.5);
    }

    createFountainEffect() {
        const fountainParticles = this.add.particles(600, 450, 'star_particle', {
            x: 600,
            y: 450,
            speedY: { min: -400, max: -200 },
            speedX: { min: -50, max: 50 },
            scale: { start: 0.3, end: 0.1 },
            lifespan: 3000,
            gravityY: 300,
            quantity: 3
        });

        this.add.text(600, 500, 'Fountain', { fontSize: '16px', color: '#ffffff' }).setOrigin(0.5);
    }

    createRainEffect() {
        const rainParticles = this.add.particles(0, 0, 'star_particle', {
            x: { min: 0, max: 800 },
            y: -10,
            speedY: 200,
            scale: { start: 0.2, end: 0.2 },
            lifespan: 4000,
            quantity: 2,
            frequency: 50
        });

        this.add.text(100, 500, 'Rain Effect', { fontSize: '16px', color: '#ffffff' }).setOrigin(0.5);
    }
}

// 4. Sprite Manipulation and Effects
class SpriteEffects extends Phaser.Scene {
    constructor() {
        super('SpriteEffects');
        this.sprites = [];
    }

    create() {
        this.createInteractiveSprites();
        this.addInstructions();
    }

    createInteractiveSprites() {
        // Scale effect
        const scaleSprite = this.add.circle(150, 200, 30, 0xff0000).setInteractive();
        scaleSprite.on('pointerover', () => {
            this.tweens.add({
                targets: scaleSprite,
                scaleX: 1.5,
                scaleY: 1.5,
                duration: 200,
                ease: 'Back.out'
            });
        });
        scaleSprite.on('pointerout', () => {
            this.tweens.add({
                targets: scaleSprite,
                scaleX: 1,
                scaleY: 1,
                duration: 200,
                ease: 'Back.out'
            });
        });

        // Color effect
        const colorSprite = this.add.circle(400, 200, 30, 0x00ff00).setInteractive();
        colorSprite.on('pointerdown', () => {
            const newColor = Phaser.Math.Between(0, 0xffffff);
            this.tweens.add({
                targets: colorSprite,
                duration: 500,
                props: {
                    red: { value: (newColor >> 16) & 255, ease: 'Power1' },
                    green: { value: (newColor >> 8) & 255, ease: 'Power1' },
                    blue: { value: newColor & 255, ease: 'Power1' }
                }
            });
        });

        // Rotation effect
        const rotationSprite = this.add.rectangle(650, 200, 60, 30, 0x0000ff).setInteractive();
        rotationSprite.on('pointerdown', () => {
            this.tweens.add({
                targets: rotationSprite,
                angle: 360,
                duration: 1000,
                ease: 'Power2',
                repeat: -1
            });
        });

        // Fade effect
        const fadeSprite = this.add.circle(150, 350, 30, 0xffff00).setInteractive();
        fadeSprite.on('pointerdown', () => {
            this.tweens.add({
                targets: fadeSprite,
                alpha: 0,
                duration: 1000,
                yoyo: true,
                repeat: -1
            });
        });

        // Shake effect
        const shakeSprite = this.add.circle(400, 350, 30, 0xff00ff).setInteractive();
        shakeSprite.on('pointerdown', () => {
            this.cameras.main.shake(500, 0.01);
        });

        // Blink effect
        const blinkSprite = this.add.circle(650, 350, 30, 0x00ffff).setInteractive();
        blinkSprite.on('pointerdown', () => {
            this.tweens.add({
                targets: blinkSprite,
                alpha: { from: 1, to: 0 },
                duration: 200,
                ease: 'Power2',
                yoyo: true,
                repeat: 5
            });
        });
    }

    addInstructions() {
        this.add.text(400, 50, 'Interactive Sprite Effects', {
            fontSize: '24px',
            color: '#ffffff'
        }).setOrigin(0.5);

        const instructions = [
            'Red: Hover to Scale',
            'Green: Click to Change Color',
            'Blue: Click to Rotate',
            'Yellow: Click to Fade',
            'Purple: Click to Shake Camera',
            'Cyan: Click to Blink'
        ];

        instructions.forEach((text, index) => {
            const x = 150 + (index % 3) * 250;
            const y = 450 + Math.floor(index / 3) * 30;
            this.add.text(x, y, text, {
                fontSize: '14px',
                color: '#ffffff'
            }).setOrigin(0.5);
        });
    }
}

// 5. Animation Blending and States
class AnimationBlending extends Phaser.Scene {
    constructor() {
        super('AnimationBlending');
        this.character = null;
        this.animationState = 'idle';
        this.blendValue = 0;
    }

    preload() {
        this.createAnimationSpritesheet();
    }

    createAnimationSpritesheet() {
        const graphics = this.add.graphics();

        // Create blend animation frames
        for (let i = 0; i < 8; i++) {
            graphics.clear();

            // Blend between idle and run based on frame
            const blend = i / 7;
            const width = 32;
            const height = 48;

            // Body
            graphics.fillStyle(
                Phaser.Math.Interpolation.Colors.Interpolation([0x00ff00, 0xff0000], blend)
            );
            graphics.fillRect(i * width, height/2, width, height/2);

            // Head
            graphics.fillRect(i * width + 8, 4, 16, 16);

            // Moving legs
            if (blend > 0.3) {
                const legMove = (i % 2) * 8 * blend;
                graphics.fillRect(i * width + 8, height - 12 + legMove, 4, 12);
                graphics.fillRect(i * width + 20, height - 12 - legMove, 4, 12);
            }
        }

        graphics.generateTexture('character_anim', 256, 48);
        graphics.destroy();
    }

    create() {
        this.character = this.add.sprite(400, 300, 'character_anim');

        // Create blend animation
        this.anims.create({
            key: 'blend',
            frames: this.anims.generateFrameNumbers('character_anim', { start: 0, end: 7 }),
            frameRate: 12,
            repeat: -1
        });

        this.character.anims.play('blend');

        // Create UI
        this.createUI();
    }

    createUI() {
        this.add.text(400, 50, 'Animation Blending Control', {
            fontSize: '24px',
            color: '#ffffff'
        }).setOrigin(0.5);

        this.blendText = this.add.text(400, 500, 'Blend: Idle', {
            fontSize: '18px',
            color: '#ffffff'
        }).setOrigin(0.5);

        // Create clickable zones for different blend states
        const states = [
            { x: 200, y: 200, label: 'Idle', value: 0 },
            { x: 400, y: 200, label: 'Walk', value: 0.5 },
            { x: 600, y: 200, label: 'Run', value: 1 }
        ];

        states.forEach(state => {
            const zone = this.add.zone(state.x, state.y, 100, 60).setInteractive();
            this.add.graphics()
                .lineStyle(2, 0xffffff)
                .strokeRect(state.x - 50, state.y - 30, 100, 60);

            this.add.text(state.x, state.y, state.label, {
                fontSize: '16px',
                color: '#ffffff'
            }).setOrigin(0.5);

            zone.on('pointerdown', () => {
                this.blendValue = state.value;
                this.updateAnimation();
            });
        });
    }

    updateAnimation() {
        let stateLabel = 'Idle';
        if (this.blendValue === 0.5) stateLabel = 'Walk';
        else if (this.blendValue === 1) stateLabel = 'Run';

        this.blendText.setText(`Blend: ${stateLabel}`);

        // Update animation speed based on blend
        const frameRate = 12 + (this.blendValue * 8);
        this.character.anims.currentAnim.msPerFrame = 1000 / frameRate;
    }
}

export {
    CharacterAnimation,
    TweeningSystem,
    ParticleEffects,
    SpriteEffects,
    AnimationBlending
};

💻 Игровые механики и физика javascript

🔴 complex ⭐⭐⭐⭐

Полные игровые механики, включая физику, обнаружение столкновений, состояния игры и управление игроком

⏱️ 45 min 🏷️ phaser, physics, collision, game mechanics
Prerequisites: Phaser.js basics, Physics concepts, Game design fundamentals
// Phaser Game Mechanics and Physics Examples

import Phaser from 'phaser';

// 1. Platformer Physics System
class PlatformerGame extends Phaser.Scene {
    constructor() {
        super('PlatformerGame');
        this.player = null;
        this.platforms = null;
        this.collectibles = null;
        this.enemies = null;
        this.gameState = {
            score: 0,
            lives: 3,
            level: 1,
            gameOver: false
        };
    }

    preload() {
        // Create game assets
        this.createGameAssets();
    }

    createGameAssets() {
        // Create tile graphics
        const graphics = this.add.graphics();

        // Ground tile
        graphics.fillStyle(0x8B4513).fillRect(0, 0, 64, 64);
        graphics.fillStyle(0x228B22).fillRect(0, 0, 64, 20);
        graphics.generateTexture('ground', 64, 64);

        // Platform tile
        graphics.clear();
        graphics.fillStyle(0x808080).fillRect(0, 0, 64, 16);
        graphics.generateTexture('platform', 64, 16);

        // Collectible (star)
        graphics.clear();
        graphics.fillStyle(0xFFD700);
        graphics.beginPath();
        for (let i = 0; i < 8; i++) {
            const angle = (i * 45 - 90) * Math.PI / 180;
            const x = Math.cos(angle) * 20;
            const y = Math.sin(angle) * 20;
            if (i === 0) graphics.moveTo(x + 25, y + 25);
            else graphics.lineTo(x + 25, y + 25);

            const innerAngle = ((i * 45 + 22.5) - 90) * Math.PI / 180;
            const innerX = Math.cos(innerAngle) * 10;
            const innerY = Math.sin(innerAngle) * 10;
            graphics.lineTo(innerX + 25, innerY + 25);
        }
        graphics.closePath();
        graphics.fillPath();
        graphics.generateTexture('star', 50, 50);

        // Enemy
        graphics.clear();
        graphics.fillStyle(0xFF0000).fillRect(0, 0, 40, 40);
        graphics.fillStyle(0x000000).fillRect(8, 8, 6, 6);
        graphics.fillStyle(0x000000).fillRect(26, 8, 6, 6);
        graphics.generateTexture('enemy', 40, 40);

        // Player
        graphics.clear();
        graphics.fillStyle(0x00FF00).fillRect(0, 0, 32, 48);
        graphics.fillStyle(0x000000).fillRect(6, 8, 5, 5);
        graphics.fillStyle(0x000000).fillRect(21, 8, 5, 5);
        graphics.generateTexture('player', 32, 48);

        graphics.destroy();
    }

    create() {
        // Setup physics world
        this.physics.world.setBounds(0, 0, 1600, 600);
        this.cameras.main.setBounds(0, 0, 1600, 600);

        // Create platforms
        this.platforms = this.physics.add.staticGroup();

        // Ground
        for (let i = 0; i < 25; i++) {
            this.platforms.create(i * 64, 600 - 32, 'ground').refreshBody();
        }

        // Floating platforms
        this.platforms.create(300, 450, 'platform');
        this.platforms.create(500, 350, 'platform');
        this.platforms.create(800, 400, 'platform');
        this.platforms.create(1100, 300, 'platform');
        this.platforms.create(1300, 450, 'platform');

        // Create player
        this.player = this.physics.add.sprite(100, 400, 'player');
        this.player.setBounce(0.2);
        this.player.setCollideWorldBounds(true);
        this.player.body.setGravityY(300);
        this.physics.add.collider(this.player, this.platforms);

        // Create collectibles
        this.collectibles = this.physics.add.group({
            key: 'star',
            repeat: 15,
            setXY: { x: 200, y: 100, stepX: 80 }
        });

        this.collectibles.children.entries.forEach(child => {
            child.setBounce(Phaser.Math.FloatBetween(0.4, 0.8));
            child.setGravityY(100);
        });

        this.physics.add.collider(this.collectibles, this.platforms);
        this.physics.add.overlap(this.player, this.collectibles, this.collectStar, null, this);

        // Create enemies
        this.enemies = this.physics.add.group();
        this.createEnemies();

        this.physics.add.collider(this.enemies, this.platforms);
        this.physics.add.collider(this.player, this.enemies, this.hitEnemy, null, this);

        // Setup controls
        this.cursors = this.input.keyboard.createCursorKeys();
        this.wasd = this.input.keyboard.addKeys('W,S,A,D');

        // Camera follow
        this.cameras.main.startFollow(this.player, true, 0.05, 0.05);

        // Create UI
        this.createUI();

        // Create player animations
        this.createPlayerAnimations();
    }

    createPlayerAnimations() {
        // Create simple animation
        this.anims.create({
            key: 'idle',
            frames: [{ key: 'player', frame: 0 }],
            frameRate: 1
        });
    }

    createEnemies() {
        const enemyPositions = [
            { x: 400, y: 400 },
            { x: 700, y: 250 },
            { x: 1000, y: 300 },
            { x: 1400, y: 400 }
        ];

        enemyPositions.forEach(pos => {
            const enemy = this.enemies.create(pos.x, pos.y, 'enemy');
            enemy.setBounce(1);
            enemy.setCollideWorldBounds(true);
            enemy.setVelocity(Phaser.Math.Between(-50, 50), 0);
            enemy.body.setGravityY(300);
        });
    }

    createUI() {
        this.scoreText = this.add.text(16, 16, `Score: ${this.gameState.score}`, {
            fontSize: '20px',
            color: '#ffffff',
            backgroundColor: '#000000',
            padding: { x: 10, y: 5 }
        }).setScrollFactor(0);

        this.livesText = this.add.text(16, 50, `Lives: ${this.gameState.lives}`, {
            fontSize: '20px',
            color: '#ffffff',
            backgroundColor: '#000000',
            padding: { x: 10, y: 5 }
        }).setScrollFactor(0);
    }

    update() {
        if (this.gameState.gameOver) return;

        // Player movement
        const { cursors, wasd, player } = this;
        const left = cursors.left.isDown || wasd.A.isDown;
        const right = cursors.right.isDown || wasd.D.isDown;
        const up = cursors.up.isDown || wasd.W.isDown;

        if (left) {
            player.setVelocityX(-200);
            player.setFlipX(true);
        } else if (right) {
            player.setVelocityX(200);
            player.setFlipX(false);
        } else {
            player.setVelocityX(0);
        }

        if (up && player.body.touching.down) {
            player.setVelocityY(-500);
        }

        // Check win condition
        if (this.collectibles.countActive() === 0) {
            this.levelComplete();
        }
    }

    collectStar(player, star) {
        star.disableBody(true, true);
        this.gameState.score += 10;
        this.scoreText.setText(`Score: ${this.gameState.score}`);
    }

    hitEnemy(player, enemy) {
        if (player.body.touching.down && enemy.body.touching.up) {
            // Player stomped enemy
            enemy.disableBody(true, true);
            this.gameState.score += 50;
            this.scoreText.setText(`Score: ${this.gameState.score}`);
            player.setVelocityY(-300);
        } else {
            // Player hit by enemy
            this.playerHit();
        }
    }

    playerHit() {
        this.gameState.lives--;
        this.livesText.setText(`Lives: ${this.gameState.lives}`);

        if (this.gameState.lives <= 0) {
            this.gameOver();
        } else {
            // Reset player position
            this.player.setPosition(100, 400);
            this.player.setVelocity(0, 0);

            // Flash player
            this.tweens.add({
                targets: this.player,
                alpha: 0.5,
                duration: 100,
                repeat: 5,
                yoyo: true
            });
        }
    }

    levelComplete() {
        this.gameState.level++;
        this.gameState.score += 100;

        this.add.text(this.cameras.main.midX, this.cameras.main.midY, 'Level Complete!', {
            fontSize: '64px',
            color: '#00ff00',
            backgroundColor: '#000000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setScrollFactor(0);

        this.time.delayedCall(2000, () => {
            this.nextLevel();
        });
    }

    nextLevel() {
        // Reset collectibles
        this.collectibles.clear(true, true);
        this.collectibles = this.physics.add.group({
            key: 'star',
            repeat: 20,
            setXY: { x: 200, y: 100, stepX: 70 }
        });

        this.collectibles.children.entries.forEach(child => {
            child.setBounce(Phaser.Math.FloatBetween(0.4, 0.8));
            child.setGravityY(100);
        });

        this.physics.add.collider(this.collectibles, this.platforms);
        this.physics.add.overlap(this.player, this.collectibles, this.collectStar, null, this);
    }

    gameOver() {
        this.gameState.gameOver = true;
        this.physics.pause();

        this.add.text(this.cameras.main.midX, this.cameras.main.midY, 'GAME OVER', {
            fontSize: '64px',
            color: '#ff0000',
            backgroundColor: '#000000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setScrollFactor(0);
    }
}

// 2. Top-Down Shooter Mechanics
class TopDownShooter extends Phaser.Scene {
    constructor() {
        super('TopDownShooter');
        this.player = null;
        this.bullets = null;
        this.enemies = null;
        this.walls = null;
        this.gameState = {
            score: 0,
            wave: 1,
            gameOver: false
        };
    }

    preload() {
        this.createShooterAssets();
    }

    createShooterAssets() {
        const graphics = this.add.graphics();

        // Player (triangle)
        graphics.fillStyle(0x0000FF);
        graphics.beginPath();
        graphics.moveTo(15, 0);
        graphics.lineTo(0, 25);
        graphics.lineTo(30, 25);
        graphics.closePath();
        graphics.fillPath();
        graphics.generateTexture('player', 30, 30);

        // Bullet
        graphics.clear();
        graphics.fillStyle(0xFFFF00).fillCircle(5, 5, 4);
        graphics.generateTexture('bullet', 10, 10);

        // Enemy
        graphics.clear();
        graphics.fillStyle(0xFF0000).fillCircle(15, 15, 15);
        graphics.generateTexture('enemy', 30, 30);

        // Wall
        graphics.clear();
        graphics.fillStyle(0x808080).fillRect(0, 0, 50, 50);
        graphics.generateTexture('wall', 50, 50);

        graphics.destroy();
    }

    create() {
        // Create world boundaries
        this.physics.world.setBounds(0, 0, 800, 600);

        // Create player
        this.player = this.physics.add.sprite(400, 300, 'player');
        this.player.setCollideWorldBounds(true);

        // Create walls
        this.walls = this.physics.add.staticGroup();
        this.createWalls();

        // Create bullets
        this.bullets = this.physics.add.group({
            defaultKey: 'bullet',
            maxSize: 50
        });

        // Create enemies
        this.enemies = this.physics.add.group();
        this.spawnEnemyWave();

        // Setup physics
        this.physics.add.collider(this.player, this.walls);
        this.physics.add.collider(this.enemies, this.walls);
        this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this);
        this.physics.add.overlap(this.bullets, this.walls, this.bulletHitWall, null, this);
        this.physics.add.overlap(this.player, this.enemies, this.playerHitEnemy, null, this);

        // Setup controls
        this.cursors = this.input.keyboard.createCursorKeys();
        this.wasd = this.input.keyboard.addKeys('W,S,A,D');
        this.space = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);

        // Follow player with camera
        this.cameras.main.startFollow(this.player);

        // Create UI
        this.createShooterUI();

        // Setup continuous enemy spawning
        this.enemySpawnTimer = this.time.addEvent({
            delay: 2000,
            callback: this.spawnEnemy,
            callbackScope: this,
            loop: true
        });
    }

    createWalls() {
        // Create maze-like walls
        const wallPositions = [
            { x: 200, y: 150 },
            { x: 600, y: 150 },
            { x: 200, y: 450 },
            { x: 600, y: 450 },
            { x: 100, y: 300 },
            { x: 700, y: 300 },
            { x: 400, y: 100 },
            { x: 400, y: 500 }
        ];

        wallPositions.forEach(pos => {
            this.walls.create(pos.x, pos.y, 'wall');
        });
    }

    createShooterUI() {
        this.scoreText = this.add.text(16, 16, `Score: ${this.gameState.score}`, {
            fontSize: '18px',
            color: '#ffffff',
            backgroundColor: '#000000',
            padding: { x: 10, y: 5 }
        }).setScrollFactor(0);

        this.waveText = this.add.text(16, 45, `Wave: ${this.gameState.wave}`, {
            fontSize: '18px',
            color: '#ffffff',
            backgroundColor: '#000000',
            padding: { x: 10, y: 5 }
        }).setScrollFactor(0);

        this.healthText = this.add.text(16, 74, 'Health: 100', {
            fontSize: '18px',
            color: '#ffffff',
            backgroundColor: '#000000',
            padding: { x: 10, y: 5 }
        }).setScrollFactor(0);
    }

    spawnEnemyWave() {
        const enemyCount = 3 + this.gameState.wave;
        for (let i = 0; i < enemyCount; i++) {
            this.spawnEnemy();
        }
    }

    spawnEnemy() {
        const side = Math.floor(Math.random() * 4);
        let x, y;

        switch(side) {
            case 0: // Top
                x = Math.random() * 800;
                y = 0;
                break;
            case 1: // Right
                x = 800;
                y = Math.random() * 600;
                break;
            case 2: // Bottom
                x = Math.random() * 800;
                y = 600;
                break;
            case 3: // Left
                x = 0;
                y = Math.random() * 600;
                break;
        }

        const enemy = this.enemies.create(x, y, 'enemy');
        enemy.health = 2;
    }

    update() {
        if (this.gameState.gameOver) return;

        // Player movement
        const speed = 200;
        let velocityX = 0;
        let velocityY = 0;

        if (this.cursors.left.isDown || this.wasd.A.isDown) velocityX = -speed;
        if (this.cursors.right.isDown || this.wasd.D.isDown) velocityX = speed;
        if (this.cursors.up.isDown || this.wasd.W.isDown) velocityY = -speed;
        if (this.cursors.down.isDown || this.wasd.S.isDown) velocityY = speed;

        this.player.setVelocity(velocityX, velocityY);

        // Point player towards mouse
        const pointer = this.input.activePointer;
        const angle = Phaser.Math.Angle.BetweenPoints(this.player, pointer);
        this.player.rotation = angle + Math.PI / 2;

        // Shooting
        if (this.space.isDown && !this.shootCooldown) {
            this.shoot(angle);
            this.shootCooldown = true;
            this.time.delayedCall(200, () => {
                this.shootCooldown = false;
            });
        }

        // Move enemies towards player
        this.enemies.children.entries.forEach(enemy => {
            if (enemy.active) {
                const enemyAngle = Phaser.Math.Angle.BetweenPoints(enemy, this.player);
                const enemySpeed = 50 + (this.gameState.wave * 10);
                enemy.setVelocity(
                    Math.cos(enemyAngle) * enemySpeed,
                    Math.sin(enemyAngle) * enemySpeed
                );
                enemy.rotation = enemyAngle + Math.PI / 2;
            }
        });

        // Check wave completion
        if (this.enemies.countActive() === 0) {
            this.nextWave();
        }
    }

    shoot(angle) {
        const bullet = this.bullets.get(this.player.x, this.player.y);
        if (bullet) {
            bullet.setActive(true);
            bullet.setVisible(true);
            bullet.body.setVelocity(
                Math.cos(angle) * 600,
                Math.sin(angle) * 600
            );

            // Remove bullet after 1 second
            this.time.delayedCall(1000, () => {
                bullet.setActive(false);
                bullet.setVisible(false);
            });
        }
    }

    hitEnemy(bullet, enemy) {
        bullet.setActive(false);
        bullet.setVisible(false);

        enemy.health--;
        if (enemy.health <= 0) {
            enemy.setActive(false);
            enemy.setVisible(false);
            this.gameState.score += 10;
            this.scoreText.setText(`Score: ${this.gameState.score}`);
        } else {
            // Flash enemy
            this.tweens.add({
                targets: enemy,
                tint: 0xFFFFFF,
                duration: 100,
                yoyo: true
            });
        }
    }

    bulletHitWall(bullet, wall) {
        bullet.setActive(false);
        bullet.setVisible(false);
    }

    playerHitEnemy(player, enemy) {
        this.gameState.health = (this.gameState.health || 100) - 10;
        this.healthText.setText(`Health: ${this.gameState.health}`);

        // Push enemy back
        const pushAngle = Phaser.Math.Angle.BetweenPoints(enemy, player);
        enemy.setVelocity(
            Math.cos(pushAngle) * 200,
            Math.sin(pushAngle) * 200
        );

        // Flash player
        this.tweens.add({
            targets: player,
            tint: 0xFF0000,
            duration: 100,
            yoyo: true
        });

        if (this.gameState.health <= 0) {
            this.gameOverShooter();
        }
    }

    nextWave() {
        this.gameState.wave++;
        this.waveText.setText(`Wave: ${this.gameState.wave}`);
        this.gameState.health = Math.min(100, (this.gameState.health || 50) + 20);
        this.healthText.setText(`Health: ${this.gameState.health}`);

        this.add.text(400, 300, `Wave ${this.gameState.wave}!`, {
            fontSize: '48px',
            color: '#00ff00',
            backgroundColor: '#000000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setScrollFactor(0);

        this.time.delayedCall(2000, () => {
            this.spawnEnemyWave();
        });
    }

    gameOverShooter() {
        this.gameState.gameOver = true;
        this.physics.pause();
        this.enemySpawnTimer.remove();

        this.add.text(400, 300, 'GAME OVER', {
            fontSize: '64px',
            color: '#ff0000',
            backgroundColor: '#000000',
            padding: { x: 20, y: 10 }
        }).setOrigin(0.5).setScrollFactor(0);
    }
}

// 3. Pong Game Physics
class PongGame extends Phaser.Scene {
    constructor() {
        super('PongGame');
        this.leftPaddle = null;
        this.rightPaddle = null;
        this.ball = null;
        this.score = { left: 0, right: 0 };
        this.paddleSpeed = 400;
    }

    preload() {
        this.createPongAssets();
    }

    createPongAssets() {
        const graphics = this.add.graphics();

        // Paddle
        graphics.fillStyle(0xFFFFFF).fillRect(0, 0, 15, 80);
        graphics.generateTexture('paddle', 15, 80);

        // Ball
        graphics.clear();
        graphics.fillStyle(0xFFFFFF).fillCircle(10, 10, 8);
        graphics.generateTexture('ball', 20, 20);

        graphics.destroy();
    }

    create() {
        // Create paddles
        this.leftPaddle = this.physics.add.sprite(50, 300, 'paddle')
            .setImmovable(true)
            .setCollideWorldBounds(true);

        this.rightPaddle = this.physics.add.sprite(750, 300, 'paddle')
            .setImmovable(true)
            .setCollideWorldBounds(true);

        // Create ball
        this.ball = this.physics.add.sprite(400, 300, 'ball')
            .setBounce(1)
            .setCollideWorldBounds(true);

        // Setup collisions
        this.physics.add.collider(this.ball, this.leftPaddle, this.hitPaddle, null, this);
        this.physics.add.collider(this.ball, this.rightPaddle, this.hitPaddle, null, this);

        // Setup world bounds
        this.physics.world.setBoundsCollision(true, true, false, false);

        // Setup controls
        this.cursors = this.input.keyboard.createCursorKeys();
        this.wasd = this.input.keyboard.addKeys('W,S,A,D');

        // Create UI
        this.createPongUI();

        // Start ball
        this.resetBall();
    }

    createPongUI() {
        this.scoreText = this.add.text(400, 50, `${this.score.left} - ${this.score.right}`, {
            fontSize: '48px',
            color: '#ffffff'
        }).setOrigin(0.5);

        this.add.text(400, 100, 'W/S: Left Paddle | Arrow Keys: Right Paddle', {
            fontSize: '16px',
            color: '#ffffff'
        }).setOrigin(0.5);
    }

    update() {
        // Left paddle controls (W/S)
        if (this.wasd.W.isDown) {
            this.leftPaddle.setVelocityY(-this.paddleSpeed);
        } else if (this.wasd.S.isDown) {
            this.leftPaddle.setVelocityY(this.paddleSpeed);
        } else {
            this.leftPaddle.setVelocityY(0);
        }

        // Right paddle controls (Arrow Keys)
        if (this.cursors.up.isDown) {
            this.rightPaddle.setVelocityY(-this.paddleSpeed);
        } else if (this.cursors.down.isDown) {
            this.rightPaddle.setVelocityY(this.paddleSpeed);
        } else {
            this.rightPaddle.setVelocityY(0);
        }

        // Keep paddles on screen
        this.leftPaddle.y = Phaser.Math.Clamp(this.leftPaddle.y, 40, 560);
        this.rightPaddle.y = Phaser.Math.Clamp(this.rightPaddle.y, 40, 560);

        // Check scoring
        if (this.ball.x < 0) {
            this.score.right++;
            this.scoreText.setText(`${this.score.left} - ${this.score.right}`);
            this.resetBall();
        } else if (this.ball.x > 800) {
            this.score.left++;
            this.scoreText.setText(`${this.score.left} - ${this.score.right}`);
            this.resetBall();
        }
    }

    hitPaddle(ball, paddle) {
        // Increase ball speed slightly
        ball.setVelocityX(ball.body.velocity.x * 1.05);

        // Add some spin based on where the ball hits the paddle
        const paddleCenter = paddle.y;
        const ballCenter = ball.y;
        const diff = ballCenter - paddleCenter;
        ball.setVelocityY(diff * 4);
    }

    resetBall() {
        this.ball.setPosition(400, 300);
        this.ball.setVelocity(
            Phaser.Math.Between(-200, 200),
            Phaser.Math.Between(-200, 200)
        );
    }
}

export {
    PlatformerGame,
    TopDownShooter,
    PongGame
};