Phaser案例

wen java案例 2

本文目录导读:

Phaser案例

  1. 案例一:经典「躲避星星」小游戏(核心:物理引擎与碰撞检测)
  2. 案例二:塔防游戏 AI(核心:路径规划与状态切换)
  3. 案例三:RPG 对话系统(核心:数据驱动 UI)
  4. 性能优化与调试建议
  5. 如何运行这些案例?

经典「躲避星星」小游戏(核心:物理引擎与碰撞检测)

这是 Phaser 官方教程的升级版,适合快速上手。

游戏玩法:控制角色移动,收集星星得分,碰到炸弹则游戏结束。

// 场景配置
const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 0 }, // 无重力,用于俯视游戏
            debug: false
        }
    },
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};
let player;
let stars;
let bombs;
let cursors;
let score = 0;
let scoreText;
// 资源加载
function preload() {
    this.load.image('sky', 'assets/sky.png');
    this.load.image('star', 'assets/star.png');
    this.load.image('bomb', 'assets/bomb.png');
    this.load.spritesheet('dude', 'assets/dude.png', { frameWidth: 32, frameHeight: 48 });
}
// 游戏初始化
function create() {
    // 设置背景
    this.add.image(400, 300, 'sky');
    // 创建玩家(带动画)
    player = this.physics.add.sprite(100, 450, 'dude');
    player.setBounce(0.2);
    player.setCollideWorldBounds(true);
    // 动画
    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
    });
    // 创建星星组(12个)
    stars = this.physics.add.group({
        key: 'star',
        repeat: 11,
        setXY: { x: 12, y: 0, stepX: 70 }
    });
    stars.children.iterate((child) => {
        child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
    });
    // 创建炸弹组(后面动态生成)
    bombs = this.physics.add.group();
    // 碰撞检测
    this.physics.add.collider(player, stars);
    this.physics.add.collider(player, bombs);
    this.physics.add.collider(stars, bombs);
    // 重叠检测(收集星星)
    this.physics.add.overlap(player, stars, collectStar, null, this);
    // 输入控制
    cursors = this.input.keyboard.createCursorKeys();
    // 分数显示
    scoreText = this.add.text(16, 16, '分数: 0', { fontSize: '32px', fill: '#fff' });
}
// 收集星星
function collectStar(player, star) {
    star.disableBody(true, true);
    score += 10;
    scoreText.setText('分数: ' + score);
    // 生成炸弹
    if (stars.countActive(true) === 0) {
        stars.children.iterate((child) => {
            child.enableBody(true, child.x, 0, true, true);
        });
        let x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
        let bomb = bombs.create(x, 16, 'bomb');
        bomb.setBounce(1);
        bomb.setCollideWorldBounds(true);
        bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
    }
}
// 游戏主循环
function update() {
    // 玩家移动控制
    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);
    }
    // 游戏结束检测
    this.physics.add.overlap(player, bombs, () => {
        this.physics.pause();
        player.setTint(0xff0000);
        player.anims.play('turn');
        this.add.text(300, 300, '游戏结束!', { fontSize: '64px', fill: '#ff0000' });
        this.add.text(280, 380, '点击重新开始', { fontSize: '32px', fill: '#ffffff' });
        this.input.once('pointerdown', () => {
            this.scene.restart();
        });
    });
}
new Phaser.Game(config);

塔防游戏 AI(核心:路径规划与状态切换)

展示敌人如何按照路径点移动,并具有不同的攻击状态。

class Enemy extends Phaser.Physics.Arcade.Sprite {
    constructor(scene, path) {
        super(scene, path[0].x, path[0].y, 'enemy');
        this.path = path;
        this.pathIndex = 0;
        this.speed = 100;
        this.hp = 100;
        this.state = 'moving';
        scene.add.existing(this);
        scene.physics.add.existing(this);
    }
    update() {
        if (this.state === 'dead') return;
        // 状态机逻辑
        switch (this.state) {
            case 'moving':
                this.moveAlongPath();
                this.checkRangeForAttack();
                break;
            case 'attacking':
                this.attackTarget();
                break;
            case 'retreating':
                this.moveBackToPath();
                break;
        }
    }
    // 路径移动
    moveAlongPath() {
        if (this.pathIndex >= this.path.length) {
            this.state = 'attacking'; // 到达终点
            return;
        }
        const targetPoint = this.path[this.pathIndex];
        const distanceToPoint = Phaser.Math.Distance.Between(this.x, this.y, targetPoint.x, targetPoint.y);
        if (distanceToPoint < 5) {
            this.pathIndex++;
        } else {
            this.scene.physics.moveTo(this, targetPoint.x, targetPoint.y, this.speed);
        }
    }
    // 检测攻击范围 (假设有炮台)
    checkRangeForAttack() {
        const tower = this.scene.tower;
        if (tower && Phaser.Math.Distance.Between(this.x, this.y, tower.x, tower.y) < 150) {
            this.state = 'retreating';
            this.setTexture('enemy_angry'); // 切换表情
        }
    }
    // 撤退逻辑
    moveBackToPath() {
        if (this.pathIndex === 0) {
            this.state = 'moving';
            this.setTexture('enemy');
            return;
        }
        const backPoint = this.path[this.pathIndex - 1];
        this.scene.physics.moveTo(this, backPoint.x, backPoint.y, this.speed * 1.2);
        // 回到上一个点就继续前进
        if (Phaser.Math.Distance.Between(this.x, this.y, backPoint.x, backPoint.y) < 10) {
            this.state = 'moving';
        }
    }
}
// 在场景中使用
class GameScene extends Phaser.Scene {
    constructor() {
        super('GameScene');
        this.path = [
            { x: 50, y: 100 },
            { x: 200, y: 100 },
            { x: 200, y: 300 },
            { x: 600, y: 300 }
        ];
    }
    create() {
        // 绘制路径 (用于调试)
        this.path.forEach((point, index) => {
            if (index > 0) {
                this.add.line(0, 0, this.path[index-1].x, this.path[index-1].y, point.x, point.y, 0xffffff).setOrigin(0);
            }
        });
        // 创建测试塔
        this.tower = this.add.sprite(400, 300, 'tower');
        // 生成敌人
        this.time.addEvent({
            delay: 2000,
            repeat: 3,
            callback: () => {
                new Enemy(this, this.path);
            }
        });
    }
    update() {
        this.children.list.forEach(child => {
            if (child instanceof Enemy) child.update();
        });
    }
}

RPG 对话系统(核心:数据驱动 UI)

当你需要一个可复用的对话系统并更新 UI 面板时。

class DialogueScene extends Phaser.Scene {
    constructor() {
        super('DialogueScene');
        this.dialogues = [
            { speaker: '老人', text: '年轻人,你来了。', face: 'oldman' },
            { speaker: '玩家', text: '请问你知道宝藏在哪吗?', face: 'player' },
            { speaker: '老人', text: '穿过黑暗森林,你会找到答案。', face: 'oldman' },
            { speaker: '玩家', text: '谢谢!', face: 'player' }
        ];
        this.currentIndex = 0;
    }
    create() {
        // 半透明背景
        this.add.rectangle(400, 300, 800, 600, 0x000000, 0.5);
        // 对话框UI
        this.dialogueBox = this.add.container(0, 0);
        const boxBg = this.add.graphics();
        boxBg.fillStyle(0xffffff, 0.9);
        boxBg.fillRoundedRect(50, 400, 700, 150, 20);
        this.speakerText = this.add.text(80, 420, '', { fontSize: '28px', fill: '#3d3d3d', fontStyle: 'bold' });
        this.text1 = this.add.text(80, 460, '', { fontSize: '24px', fill: '#000000', wordWrap: { width: 640 } });
        this.dialogueBox.add([boxBg, this.speakerText, this.text1]);
        // 下一步按钮
        this.nextButton = this.add.text(650, 520, '▼', { fontSize: '32px', fill: '#007acc' })
            .setInteractive()
            .on('pointerdown', () => this.nextDialogue());
        // 首次对话
        this.showDialogue(0);
    }
    showDialogue(index) {
        if (index >= this.dialogues.length) {
            this.closeDialogue();
            return;
        }
        const data = this.dialogues[index];
        this.speakerText.setText(data.speaker + ':');
        this.text1.setText(data.text);
    }
    nextDialogue() {
        this.currentIndex++;
        this.showDialogue(this.currentIndex);
    }
    closeDialogue() {
        console.log('对话结束');
        this.scene.stop();
        this.scene.resume('GameScene');
    }
}
// 在游戏场景中触发
// this.scene.launch('DialogueScene'); // 启动对话

性能优化与调试建议

对于以上案例,如果遇到性能问题,可以这样优化:

对象池重用(用于案例一)

// 创建对象池
this.bombs = this.physics.add.group({
    maxSize: 10,
    // 当对象不存在时自动创建
    defaultKey: 'bomb',
    defaultFrame: undefined
});
// 从池中获取
let bomb = this.bombs.get(player.x, player.y, 'bomb');
if (bomb) {
    bomb.setActive(true).setVisible(true);
    bomb.setVelocity(100, -100);
}

调试工具(用于案例三)

// 在 create 中启用
this.physics.world.createDebugGraphic();
this.physics.world.debugGraphic.setVisible(true);
// 路径可视化
this.add.graphics().lineStyle(2, 0xff0000, 1).strokePoints(this.path.map(p => new Phaser.Geom.Point(p.x, p.y)));

帧率显示

this.time.addEvent({
    delay: 1000,
    loop: true,
    callback: () => {
        const fps = this.game.loop.actualFps.toFixed(0);
        this.fpsText.setText('FPS: ' + fps);
    }
});

如何运行这些案例?

  1. 本地开发:使用 phaser3-project-template 或手动引入 CDN:

    <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
  2. 在线调试:将案例代码复制到 Phaser 官方示例编辑器 中。

  3. 资源替换:案例中的图片资源(sky.pngstar.png)可以在 Kenney 资源库 免费获取。

需要我针对某个案例进行深度扩展(如增加多人联机、WebGL 着色器特效等),请告诉我。

抱歉,评论功能暂时关闭!