Phaser(二):小恐龙跑酷游戏

简介: 创建地图放置障碍物添加玩家操作和动画玩家与障碍物碰撞游戏结束添加场景装饰云和石头完整代码

效果展示

image.png

资源

image.png

图片资源

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

地图资源

image.png
image.png

src/GameScenes/Scene02/Tilemaps/map.json

{
   
    "compressionlevel":-1,
 "height":6,
 "infinite":true,
 "layers":[
        {
   
   
         "chunks":[
                {
   
   
                 "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
                    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                 "height":16,
                 "width":16,
                 "x":0,
                 "y":0
                }],
         "height":16,
         "id":1,
         "name":"sky",
         "opacity":1,
         "startx":0,
         "starty":0,
         "type":"tilelayer",
         "visible":true,
         "width":16,
         "x":0,
         "y":0
        }, 
        {
   
   
         "chunks":[
                {
   
   
                 "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    4, 6, 5, 4, 6, 5, 4, 6, 5, 4, 6, 5, 0, 0, 0, 0,
                    7, 9, 8, 7, 9, 8, 7, 9, 8, 7, 9, 8, 0, 0, 0, 0,
                    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                 "height":16,
                 "width":16,
                 "x":0,
                 "y":0
                }],
         "height":16,
         "id":2,
         "name":"ground",
         "opacity":1,
         "startx":0,
         "starty":0,
         "type":"tilelayer",
         "visible":true,
         "width":16,
         "x":0,
         "y":0
        }],
 "nextlayerid":3,
 "nextobjectid":1,
 "orientation":"orthogonal",
 "renderorder":"left-up",
 "tiledversion":"1.9.0",
 "tileheight":16,
 "tilesets":[
        {
   
   
         "columns":1,
         "firstgid":1,
         "image":"sky.png",
         "imageheight":48,
         "imagewidth":16,
         "margin":0,
         "name":"sky",
         "spacing":0,
         "tilecount":3,
         "tileheight":16,
         "tilewidth":16
        }, 
        {
   
   
         "columns":3,
         "firstgid":4,
         "image":"ground.png",
         "imageheight":48,
         "imagewidth":48,
         "margin":0,
         "name":"ground",
         "spacing":0,
         "tilecount":9,
         "tileheight":16,
         "tilewidth":16
        }],
 "tilewidth":16,
 "type":"map",
 "version":"1.9",
 "width":12
}

创建地图

image.png

天空地面循环滚动

左键:减速

右键:加速

不按:普通速度

image.png

import Phaser from 'phaser';

export default class MainScene extends Phaser.Scene {
   
   

    // 地图
    private map: Phaser.Tilemaps.Tilemap | undefined;

    // 天空图层
    private skyLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 地面图层
    private groundLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 缩放系数
    private mapScale: number = 6.25;

    // 当前速度
    private currentVelocity: number = 1.5;

    // 速度
    private velocity: number = 1.5;

    // 加速度
    private acceleration: number = 1;

    // 距离
    private distance: number = 0;

    // 键盘
    private cursors: Phaser.Types.Input.Keyboard.CursorKeys | undefined;


    constructor() {
   
   
        super({
   
   
            key: 'MainScene',
            physics: {
   
   
                default: 'arcade',
                arcade: {
   
   
                    debug: true
                }
            },
        });
    }

    preload() {
   
   
        // 天空地面
        {
   
   
            const map = new URL(`../Tilemaps/map.json`, import.meta.url).href;
            const sky = new URL(`../Tilemaps/sky.png`, import.meta.url).href;
            const ground = new URL(`../Tilemaps/ground.png`, import.meta.url).href;
            this.load.tilemapTiledJSON('map', map);
            this.load.image('sky', sky);
            this.load.image('ground', ground);
        }
    }

    create() {
   
   
        // 添加键盘控制
        {
   
   
            this.cursors = this.input.keyboard.createCursorKeys();
        }
        // 创建天空地面
        {
   
   
            this.map = this.make.tilemap({
   
   key: 'map'});
            const sky = this.map.addTilesetImage('sky', 'sky');
            const ground = this.map.addTilesetImage('ground', 'ground');
            this.skyLayer = this.map.createLayer('sky', sky, 0, 0).setScale(this.mapScale);
            this.groundLayer = this.map.createLayer('ground', ground, 0, 0).setScale(this.mapScale);
            this.groundLayer.setCollision([4, 5, 6, 7, 8, 9, 10]);
        }
    }

    update(time: number, delta: number) {
   
   
        super.update(time, delta);
        // 天空地面运动
        {
   
   
            if (this.cursors?.left.isDown) {
   
    // 减速
                this.currentVelocity = this.velocity - this.acceleration;
            } else if (this.cursors?.right.isDown) {
   
    // 加速
                this.currentVelocity = this.velocity + this.acceleration;
            } else {
   
    // 默认速度
                this.currentVelocity = this.velocity;
            }
            this.distance += this.currentVelocity;
            let isTolerance = Phaser.Math.Within(this.map!.tileWidth * this.mapScale, this.distance, 1); // 容差在1以内
            if (isTolerance) {
   
   
                let prev, current;
                for (let y = 0; y < this.map!.height; y++) {
   
   
                    for (let x = 1; x < this.map!.width; x++) {
   
   
                        if (y < 3) {
   
   
                            prev = this.skyLayer?.getTileAt(x - 1, y);
                            current = this.skyLayer?.getTileAt(x, y);
                            prev!.index = current!.index;
                        } else if (y >= 3) {
   
   
                            prev = this.groundLayer?.getTileAt(x - 1, y);
                            current = this.groundLayer?.getTileAt(x, y);
                            prev!.index = current!.index;
                            if (y === 3) {
   
   
                                current!.index = Phaser.Math.RND.weightedPick([4, 5, 6]);
                            }
                            if (y === 4) {
   
   
                                if (this.groundLayer?.getTileAt(x, 3).index === 6) {
   
   
                                    current!.index = 9;
                                } else {
   
   
                                    current!.index = Phaser.Math.RND.weightedPick([7, 8]);
                                }
                            }
                            if (y === 5) {
   
   
                                current!.index = Phaser.Math.RND.weightedPick([10]);
                            }
                        }
                    }
                }
                this.distance = 0;
            }
            this.skyLayer?.setX(-this.distance * 0.5);
            this.groundLayer?.setX(-this.distance);
        }
    }
}

放置障碍物

image.png

import Phaser from 'phaser';

export default class MainScene extends Phaser.Scene {
   
   

    // 地图
    private map: Phaser.Tilemaps.Tilemap | undefined;

    // 天空图层
    private skyLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 地面图层
    private groundLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 缩放系数
    private mapScale: number = 6.25;

    // 当前速度
    private currentVelocity: number = 1.5;

    // 速度
    private velocity: number = 1.5;

    // 加速度
    private acceleration: number = 1;

    // 距离
    private distance: number = 0;

    // 键盘
    private cursors: Phaser.Types.Input.Keyboard.CursorKeys | undefined;

    // 时间监听
    private timer: Phaser.Time.TimerEvent | undefined;

    // 障碍物集合
    private obstacles: Phaser.Types.Physics.Arcade.SpriteWithDynamicBody[] = [];

    // 分数
    private score: number = 0;

    // 分数文本
    private scoreText: Phaser.GameObjects.Text | undefined;

    constructor() {
   
   
        super({
   
   
            key: 'MainScene',
            physics: {
   
   
                default: 'arcade',
                arcade: {
   
   
                    debug: true
                }
            },
        });
    }

    preload() {
   
   
        // 障碍物
        {
   
   
            const {
   
   href: cactusA} = new URL(`../Sprites/cactus A.png`, import.meta.url);
            const {
   
   href: cactusB} = new URL(`../Sprites/cactus B.png`, import.meta.url);
            const {
   
   href: cactusC} = new URL(`../Sprites/cactus C.png`, import.meta.url);
            this.load.spritesheet('obstacle A', cactusA, {
   
   frameWidth: 12, frameHeight: 20});
            this.load.spritesheet('obstacle B', cactusB, {
   
   frameWidth: 21, frameHeight: 15});
            this.load.spritesheet('obstacle C', cactusC, {
   
   frameWidth: 28, frameHeight: 20});
        }
        // 分数
        {
   
   
            this.scoreText = this.add.text(16, 16, `Score: ${
     
     this.score}`, {
   
   fontSize: '32px', color: '#48ff00'});
            this.scoreText.setDepth(2);
        }
    }

    create() {
   
   
        // 添加键盘控制
        {
   
   
            this.cursors = this.input.keyboard.createCursorKeys();
        }
        // 障碍物
        {
   
   
            this.setObstacles();
        }
    }

    update(time: number, delta: number) {
   
   
        super.update(time, delta);

        // 障碍物速度
        {
   
   
            this.obstacles.forEach(obstacle => {
   
   
                obstacle.x -= this.currentVelocity;
                if (obstacle.x < -100) {
   
   
                    obstacle.destroy();
                }
            })
        }
    }

    // 放置障碍物
    setObstacles() {
   
   
        this.score += 1;
        this.scoreText!.setText(`Score: ${
     
     this.score * 10}`);
        if (this.timer) this.timer.destroy();
        let delay = Math.cos(this.score / 600) * 3000;
        // 秒数小于2秒固定为2秒
        if (delay < 2000) {
   
   
            delay = 2000;
        }
        // 按左键固定秒数,避免障碍物过近
        if (this.cursors?.left.isDown) {
   
   
            delay = 6000;
        }
        this.timer = this.time.addEvent({
   
   
            delay,
            callback: this.setObstacles,
            callbackScope: this,
            loop: true
        });
        let item = Phaser.Math.RND.weightedPick([
            {
   
   key: 'obstacle A', w: 12, h: 20},
            {
   
   key: 'obstacle B', w: 21, h: 15},
            {
   
   key: 'obstacle C', w: 28, h: 20}
        ]);
        let x = this.map!.widthInPixels * this.mapScale + 100; // Math.cos(this.score / 600) * 1600;
        let y = this.map!.tileHeight * this.mapScale * 3 - item.h / 2 * this.mapScale;
        let obstacle = this.physics.add.sprite(x, y, item.key).setScale(this.mapScale).refreshBody();
        obstacle.setCircle(item.w / 2); // 碰撞区域
        this.obstacles.push(obstacle);
    }
}

添加玩家操作和动画

image.png

import Phaser from 'phaser';

export default class MainScene extends Phaser.Scene {
   
   

    // 地图
    private map: Phaser.Tilemaps.Tilemap | undefined;

    // 天空图层
    private skyLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 地面图层
    private groundLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 缩放系数
    private mapScale: number = 6.25;

    // 当前速度
    private currentVelocity: number = 1.5;

    // 速度
    private velocity: number = 1.5;

    // 加速度
    private acceleration: number = 1;

    // 距离
    private distance: number = 0;

    // 键盘
    private cursors: Phaser.Types.Input.Keyboard.CursorKeys | undefined;

    // 时间监听
    private timer: Phaser.Time.TimerEvent | undefined;

    // 障碍物集合
    private obstacles: Phaser.Types.Physics.Arcade.SpriteWithDynamicBody[] = [];

    // 分数
    private score: number = 0;

    // 分数文本
    private scoreText: Phaser.GameObjects.Text | undefined;

    // 玩家
    private player: (Phaser.Physics.Arcade.Sprite & {
   
    body: Phaser.Physics.Arcade.Body; }) | undefined;

    // 重力
    private gravityY: number = 600;

    // 跳跃速度
    private jumpVelocity: number = 500;

    constructor() {
   
   
        super({
   
   
            key: 'MainScene',
            physics: {
   
   
                default: 'arcade',
                arcade: {
   
   
                    debug: true
                }
            },
        });
    }

    preload() {
   
   
        // 玩家
        {
   
   
            const {
   
   href: player} = new URL(`../Sprites/player.png`, import.meta.url);
            this.load.spritesheet('player', player, {
   
   frameWidth: 16.8, frameHeight: 16});
        }
    }

    create() {
   
   
        // 添加键盘控制
        {
   
   
            this.cursors = this.input.keyboard.createCursorKeys();
        }
        // 玩家
        {
   
   
            this.player = this.physics.add.sprite(100, 100, 'player').setScale(this.mapScale).refreshBody();
            this.player.setDepth(1);
            // 左右
            this.anims.create({
   
   
                key: 'run',
                frames: this.anims.generateFrameNumbers('player', {
   
   start: 0, end: 3}),
                frameRate: 10,
                repeat: -1
            });
            // 跳跃
            this.anims.create({
   
   
                key: 'jump',
                frames: [{
   
   key: 'player', frame: 4}],
                frameRate: 20
            });
            // 死亡
            this.anims.create({
   
   
                key: 'died',
                frames: [{
   
   key: 'player', frame: 5}],
                frameRate: 20
            });

            this.player.setGravityY(this.gravityY);
        }

        // 玩家与地面碰撞
        {
   
   
            this.physics.add.collider(this.player, this.groundLayer);
        }
    }

    update(time: number, delta: number) {
   
   
        super.update(time, delta);

        // 玩家移动跳跃
        {
   
   
            // 在地面上
            if (this.player?.body.onFloor()) {
   
   
                // 获取当前动画是否为 run
                this.player?.anims.getName() !== 'run' && this.player?.anims.play('run');
                // 在地面时可跳跃
                if (this.cursors?.space.isDown || this.cursors?.up.isDown) {
   
   
                    this.player?.setVelocityY(-this.jumpVelocity);
                }
            }
            // 不在地面上 动画jump
            else {
   
   
                this.player?.anims.play('jump');
            }
        }
    }
}

玩家与障碍物碰撞游戏结束

image.png

import Phaser from 'phaser';

export default class MainScene extends Phaser.Scene {
   
   

    // 地图
    private map: Phaser.Tilemaps.Tilemap | undefined;

    // 天空图层
    private skyLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 地面图层
    private groundLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 缩放系数
    private mapScale: number = 6.25;

    // 当前速度
    private currentVelocity: number = 1.5;

    // 速度
    private velocity: number = 1.5;

    // 加速度
    private acceleration: number = 1;

    // 距离
    private distance: number = 0;

    // 键盘
    private cursors: Phaser.Types.Input.Keyboard.CursorKeys | undefined;

    // 时间监听
    private timer: Phaser.Time.TimerEvent | undefined;

    // 障碍物集合
    private obstacles: Phaser.Types.Physics.Arcade.SpriteWithDynamicBody[] = [];

    // 分数
    private score: number = 0;

    // 分数文本
    private scoreText: Phaser.GameObjects.Text | undefined;

    // 玩家
    private player: (Phaser.Physics.Arcade.Sprite & {
   
    body: Phaser.Physics.Arcade.Body; }) | undefined;

    // 重力
    private gravityY: number = 600;

    // 跳跃速度
    private jumpVelocity: number = 500;

    // 游戏结束
    private gameOver: boolean = false;

    constructor() {
   
   
        super({
   
   
            key: 'MainScene',
            physics: {
   
   
                default: 'arcade',
                arcade: {
   
   
                    debug: true
                }
            },
        });
    }

    preload() {
   
   

    }

    create() {
   
   
        // 玩家与障碍物碰撞游戏结束
        {
   
   
            this.physics.add.overlap(this.player, this.obstacles, this.hitObstacle, undefined, this);
        }
    }

    update(time: number, delta: number) {
   
   
        super.update(time, delta);
        // 游戏结束
        {
   
   
            if (this.gameOver) {
   
   
                this.timer && this.timer.destroy();
                this.scoreText!.setText(`游戏结束, Score: ${
     
     this.score * 10}`);
                return
            }
        }
    }

    // 放置障碍物
    setObstacles() {
   
   
        if (this.gameOver) return;
    }

    // 角色碰撞障碍物
    hitObstacle(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, _obstacle: Phaser.Types.Physics.Arcade.GameObjectWithBody) {
   
   
        // 暂停物理引擎
        this.physics.pause();
        // player.body.gameObject.setTint(0xff0000);
        player.body.gameObject.anims.play('died');
        this.timer && this.timer.destroy();
        this.gameOver = true;
    }
}

添加场景装饰云和石头

image.png

import Phaser from 'phaser';

export default class MainScene extends Phaser.Scene {
   
   

    // 地图
    private map: Phaser.Tilemaps.Tilemap | undefined;

    // 天空图层
    private skyLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 地面图层
    private groundLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 缩放系数
    private mapScale: number = 6.25;

    // 当前速度
    private currentVelocity: number = 1.5;

    // 速度
    private velocity: number = 1.5;

    // 加速度
    private acceleration: number = 1;

    // 距离
    private distance: number = 0;

    // 键盘
    private cursors: Phaser.Types.Input.Keyboard.CursorKeys | undefined;

    // 障碍物时间监听
    private obstacleTimer: Phaser.Time.TimerEvent | undefined;

    // 障碍物集合
    private obstacles: Phaser.Types.Physics.Arcade.SpriteWithDynamicBody[] = [];

    // 分数
    private score: number = 0;

    // 分数文本
    private scoreText: Phaser.GameObjects.Text | undefined;

    // 玩家
    private player: (Phaser.Physics.Arcade.Sprite & {
   
    body: Phaser.Physics.Arcade.Body; }) | undefined;

    // 重力
    private gravityY: number = 600;

    // 跳跃速度
    private jumpVelocity: number = 500;

    // 游戏结束
    private gameOver: boolean = false;

    // 云
    private clouds: Phaser.GameObjects.Image[] = [];

    // 石头
    private rocks: Phaser.GameObjects.Image[] = [];

    // 云石头时间监听
    private crTimer: Phaser.Time.TimerEvent | undefined;

    constructor() {
   
   
        super({
   
   
            key: 'MainScene',
            physics: {
   
   
                default: 'arcade',
                arcade: {
   
   
                    debug: true
                }
            },
        });
    }

    preload() {
   
   
        // 云和石头
        {
   
   
            const cloudA = new URL(`../Sprites/cloud A.png`, import.meta.url).href;
            const cloudB = new URL(`../Sprites/cloud B.png`, import.meta.url).href;
            const rockA = new URL(`../Sprites/rock A.png`, import.meta.url).href;
            const rockB = new URL(`../Sprites/rock B.png`, import.meta.url).href;
            this.load.image('cloud A', cloudA);
            this.load.image('cloud B', cloudB);
            this.load.image('rock A', rockA);
            this.load.image('rock B', rockB);
        }
    }

    create() {
   
   
        // 云和石头监听
        {
   
   
            this.crTimer = this.time.addEvent({
   
   
                delay: 1000,
                callback: this.setCloudsRocks,
                callbackScope: this,
                loop: true
            });
        }
    }

    update(time: number, delta: number) {
   
   
        super.update(time, delta);
        // 游戏结束
        {
   
   
            if (this.gameOver) {
   
   
                this.obstacleTimer && this.obstacleTimer.destroy();
                this.crTimer && this.crTimer.destroy();
                this.scoreText!.setText(`游戏结束, Score: ${
     
     this.score * 10}`);
                return
            }
        }
        // 云和石头
        {
   
   
            this.clouds.forEach(cloud => {
   
   
                cloud.x -= this.currentVelocity * 0.6;
                if (cloud.x < -100) {
   
   
                    cloud.destroy();
                }
            })
            this.rocks.forEach(rock => {
   
   
                rock.x -= this.currentVelocity * 0.8;
                if (rock.x < -100) {
   
   
                    rock.destroy();
                }
            })
        }
    }

    // 云和石头
    setCloudsRocks() {
   
   
        let cloudItem = Phaser.Math.RND.weightedPick([
            {
   
   key: 'cloud A', w: 20, h: 11},
            {
   
   key: 'cloud B', w: 11, h: 5}
        ]);
        let rockItem = Phaser.Math.RND.weightedPick([
            {
   
   key: 'rock A', w: 48, h: 24},
            {
   
   key: 'rock B', w: 16, h: 16}
        ]);
        let clodX = this.map!.widthInPixels * this.mapScale + 100;
        let clodY = Math.random() * this.map!.tileHeight * 2 * this.mapScale;
        let rockX = this.map!.widthInPixels * this.mapScale * (1 + Math.random());
        let rockY = this.map!.tileHeight * this.mapScale * 3 - rockItem.h / 2 * this.mapScale;
        let cloud = this.add.image(clodX, clodY, cloudItem.key).setScale(this.mapScale).setDepth(2);
        let rock = this.add.image(rockX, rockY, rockItem.key).setScale(this.mapScale).setDepth(2);
        this.clouds.push(cloud);
        this.rocks.push(rock);
    }
}

完整代码

image.png

import Phaser from 'phaser';

export default class MainScene extends Phaser.Scene {
   
   

    // 地图
    private map: Phaser.Tilemaps.Tilemap | undefined;

    // 天空图层
    private skyLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 地面图层
    private groundLayer: Phaser.Tilemaps.TilemapLayer | undefined;

    // 缩放系数
    private mapScale: number = 6.25;

    // 当前速度
    private currentVelocity: number = 1.5;

    // 速度
    private velocity: number = 1.5;

    // 加速度
    private acceleration: number = 1;

    // 距离
    private distance: number = 0;

    // 键盘
    private cursors: Phaser.Types.Input.Keyboard.CursorKeys | undefined;

    // 障碍物时间监听
    private obstacleTimer: Phaser.Time.TimerEvent | undefined;

    // 障碍物集合
    private obstacles: Phaser.Types.Physics.Arcade.SpriteWithDynamicBody[] = [];

    // 分数
    private score: number = 0;

    // 分数文本
    private scoreText: Phaser.GameObjects.Text | undefined;

    // 玩家
    private player: (Phaser.Physics.Arcade.Sprite & {
   
    body: Phaser.Physics.Arcade.Body; }) | undefined;

    // 重力
    private gravityY: number = 600;

    // 跳跃速度
    private jumpVelocity: number = 500;

    // 游戏结束
    private gameOver: boolean = false;

    // 云
    private clouds: Phaser.GameObjects.Image[] = [];

    // 石头
    private rocks: Phaser.GameObjects.Image[] = [];

    // 云石头时间监听
    private crTimer: Phaser.Time.TimerEvent | undefined;

    constructor() {
   
   
        super({
   
   
            key: 'MainScene',
            physics: {
   
   
                default: 'arcade',
                arcade: {
   
   
                    debug: true
                }
            },
        });
    }

    preload() {
   
   
        // 天空地面
        {
   
   
            const map = new URL(`../Tilemaps/map.json`, import.meta.url).href;
            const sky = new URL(`../Tilemaps/sky.png`, import.meta.url).href;
            const ground = new URL(`../Tilemaps/ground.png`, import.meta.url).href;
            this.load.tilemapTiledJSON('map', map);
            this.load.image('sky', sky);
            this.load.image('ground', ground);
        }
        // 障碍物
        {
   
   
            const {
   
   href: cactusA} = new URL(`../Sprites/cactus A.png`, import.meta.url);
            const {
   
   href: cactusB} = new URL(`../Sprites/cactus B.png`, import.meta.url);
            const {
   
   href: cactusC} = new URL(`../Sprites/cactus C.png`, import.meta.url);
            this.load.spritesheet('obstacle A', cactusA, {
   
   frameWidth: 12, frameHeight: 20});
            this.load.spritesheet('obstacle B', cactusB, {
   
   frameWidth: 21, frameHeight: 15});
            this.load.spritesheet('obstacle C', cactusC, {
   
   frameWidth: 28, frameHeight: 20});
        }
        // 分数
        {
   
   
            this.scoreText = this.add.text(16, 16, `Score: ${
     
     this.score}`, {
   
   fontSize: '32px', color: '#48ff00'});
            this.scoreText.setDepth(5);
        }
        // 玩家
        {
   
   
            const {
   
   href: player} = new URL(`../Sprites/player.png`, import.meta.url);
            this.load.spritesheet('player', player, {
   
   frameWidth: 16.8, frameHeight: 16});
        }
        // 云和石头
        {
   
   
            const cloudA = new URL(`../Sprites/cloud A.png`, import.meta.url).href;
            const cloudB = new URL(`../Sprites/cloud B.png`, import.meta.url).href;
            const rockA = new URL(`../Sprites/rock A.png`, import.meta.url).href;
            const rockB = new URL(`../Sprites/rock B.png`, import.meta.url).href;
            this.load.image('cloud A', cloudA);
            this.load.image('cloud B', cloudB);
            this.load.image('rock A', rockA);
            this.load.image('rock B', rockB);
        }
    }

    create() {
   
   
        // 添加键盘控制
        {
   
   
            this.cursors = this.input.keyboard.createCursorKeys();
        }
        // 创建天空地面
        {
   
   
            this.map = this.make.tilemap({
   
   key: 'map'});
            const sky = this.map.addTilesetImage('sky', 'sky');
            const ground = this.map.addTilesetImage('ground', 'ground');
            this.skyLayer = this.map.createLayer('sky', sky, 0, 0).setScale(this.mapScale);
            this.groundLayer = this.map.createLayer('ground', ground, 0, 0).setScale(this.mapScale);
            this.groundLayer.setCollision([4, 5, 6, 7, 8, 9, 10]);
        }
        // 障碍物
        {
   
   
            this.setObstacles();
        }
        // 玩家
        {
   
   
            this.player = this.physics.add.sprite(200, 100, 'player').setScale(this.mapScale).refreshBody();
            this.player.setDepth(4);
            // 左右
            this.anims.create({
   
   
                key: 'run',
                frames: this.anims.generateFrameNumbers('player', {
   
   start: 0, end: 3}),
                frameRate: 10,
                repeat: -1
            });
            // 跳跃
            this.anims.create({
   
   
                key: 'jump',
                frames: [{
   
   key: 'player', frame: 4}],
                frameRate: 20
            });
            // 死亡
            this.anims.create({
   
   
                key: 'died',
                frames: [{
   
   key: 'player', frame: 5}],
                frameRate: 20
            });

            this.player.setGravityY(this.gravityY);
        }
        // 玩家与地面碰撞
        {
   
   
            this.physics.add.collider(this.player, this.groundLayer);
        }
        // 玩家与障碍物碰撞游戏结束
        {
   
   
            this.physics.add.overlap(this.player, this.obstacles, this.hitObstacle, undefined, this);
        }
        // 云和石头监听
        {
   
   
            this.crTimer = this.time.addEvent({
   
   
                delay: 1000,
                callback: this.setCloudsRocks,
                callbackScope: this,
                loop: true
            });
        }
    }

    update(time: number, delta: number) {
   
   
        super.update(time, delta);
        // 游戏结束
        {
   
   
            if (this.gameOver) {
   
   
                this.obstacleTimer && this.obstacleTimer.destroy();
                this.crTimer && this.crTimer.destroy();
                this.scoreText!.setText(`游戏结束, Score: ${
     
     this.score * 10}`);
                return
            }
        }
        // 天空地面运动
        {
   
   
            if (this.cursors?.left.isDown) {
   
    // 减速
                this.currentVelocity = this.velocity - this.acceleration;
            } else if (this.cursors?.right.isDown) {
   
    // 加速
                this.currentVelocity = this.velocity + this.acceleration;
            } else {
   
    // 默认速度
                this.currentVelocity = this.velocity;
            }
            this.distance += this.currentVelocity;
            let isTolerance = Phaser.Math.Within(this.map!.tileWidth * this.mapScale, this.distance, 1); // 容差在1以内
            if (isTolerance) {
   
   
                let prev, current;
                for (let y = 0; y < this.map!.height; y++) {
   
   
                    for (let x = 1; x < this.map!.width; x++) {
   
   
                        if (y < 3) {
   
   
                            prev = this.skyLayer?.getTileAt(x - 1, y);
                            current = this.skyLayer?.getTileAt(x, y);
                            prev!.index = current!.index;
                        } else if (y >= 3) {
   
   
                            prev = this.groundLayer?.getTileAt(x - 1, y);
                            current = this.groundLayer?.getTileAt(x, y);
                            prev!.index = current!.index;
                            if (y === 3) {
   
   
                                current!.index = Phaser.Math.RND.weightedPick([4, 5, 6]);
                            }
                            if (y === 4) {
   
   
                                if (this.groundLayer?.getTileAt(x, 3).index === 6) {
   
   
                                    current!.index = 9;
                                } else {
   
   
                                    current!.index = Phaser.Math.RND.weightedPick([7, 8]);
                                }
                            }
                            if (y === 5) {
   
   
                                current!.index = Phaser.Math.RND.weightedPick([10]);
                            }
                        }
                    }
                }
                this.distance = 0;
            }
            this.skyLayer?.setX(-this.distance * 0.5);
            this.groundLayer?.setX(-this.distance);
        }
        // 障碍物速度
        {
   
   
            this.obstacles.forEach(obstacle => {
   
   
                obstacle.x -= this.currentVelocity;
                if (obstacle.x < -100) {
   
   
                    obstacle.destroy();
                }
            })
        }
        // 玩家移动跳跃
        {
   
   
            // 在地面上
            if (this.player?.body.onFloor()) {
   
   
                // 获取当前动画是否为 run
                this.player?.anims.getName() !== 'run' && this.player?.anims.play('run');
                // 在地面时可跳跃
                if (this.cursors?.space.isDown || this.cursors?.up.isDown) {
   
   
                    this.player?.setVelocityY(-this.jumpVelocity);
                }
            }
            // 不在地面上 动画jump
            else {
   
   
                this.player?.anims.play('jump');
            }
        }
        // 云和石头
        {
   
   
            this.clouds.forEach(cloud => {
   
   
                cloud.x -= this.currentVelocity * 0.6;
                if (cloud.x < -100) {
   
   
                    cloud.destroy();
                }
            })
            this.rocks.forEach(rock => {
   
   
                rock.x -= this.currentVelocity * 0.8;
                if (rock.x < -100) {
   
   
                    rock.destroy();
                }
            })
        }
    }

    // 放置障碍物
    setObstacles() {
   
   
        if (this.gameOver) return;
        this.score += 1;
        this.scoreText!.setText(`Score: ${
     
     this.score * 10}`);
        this.obstacleTimer && this.obstacleTimer.destroy();
        let delay = Math.cos(this.score / 600) * 3000;
        // 秒数小于2秒固定为2秒
        if (delay < 2000) {
   
   
            delay = 2000;
        }
        // 按左键固定秒数,避免障碍物过近
        if (this.cursors?.left.isDown) {
   
   
            delay = 6000;
        }
        this.obstacleTimer = this.time.addEvent({
   
   
            delay,
            callback: this.setObstacles,
            callbackScope: this,
            loop: true
        });
        let item = Phaser.Math.RND.weightedPick([
            {
   
   key: 'obstacle A', w: 12, h: 20},
            {
   
   key: 'obstacle B', w: 21, h: 15},
            {
   
   key: 'obstacle C', w: 28, h: 20}
        ]);
        let x = this.map!.widthInPixels * this.mapScale + 100;
        let y = this.map!.tileHeight * this.mapScale * 3 - item.h / 2 * this.mapScale;
        let obstacle = this.physics.add.sprite(x, y, item.key).setScale(this.mapScale).refreshBody();
        obstacle.setCircle(item.w / 2.5, 1); // 碰撞区域
        obstacle.setDepth(3);
        this.obstacles.push(obstacle);
    }

    // 角色碰撞障碍物
    hitObstacle(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, _obstacle: Phaser.Types.Physics.Arcade.GameObjectWithBody) {
   
   
        // 暂停物理引擎
        this.physics.pause();
        // player.body.gameObject.setTint(0xff0000);
        player.body.gameObject.anims.play('died');
        this.obstacleTimer && this.obstacleTimer.destroy();
        this.gameOver = true;
    }

    // 云和石头
    setCloudsRocks() {
   
   
        let cloudItem = Phaser.Math.RND.weightedPick([
            {
   
   key: 'cloud A', w: 20, h: 11},
            {
   
   key: 'cloud B', w: 11, h: 5}
        ]);
        let rockItem = Phaser.Math.RND.weightedPick([
            {
   
   key: 'rock A', w: 48, h: 24},
            {
   
   key: 'rock B', w: 16, h: 16}
        ]);
        let clodX = this.map!.widthInPixels * this.mapScale + 100;
        let clodY = Math.random() * this.map!.tileHeight * 2 * this.mapScale;
        let rockX = this.map!.widthInPixels * this.mapScale * (1 + Math.random());
        let rockY = this.map!.tileHeight * this.mapScale * 3 - rockItem.h / 2 * this.mapScale;
        let cloud = this.add.image(clodX, clodY, cloudItem.key).setScale(this.mapScale).setDepth(2);
        let rock = this.add.image(rockX, rockY, rockItem.key).setScale(this.mapScale).setDepth(2);
        this.clouds.push(cloud);
        this.rocks.push(rock);
    }
}
import Phaser from "phaser";

new Phaser.Game({
   
   
    type: Phaser.AUTO,
    parent: 'phaser3-game',
    width: 1024,
    height: 600,
    pixelArt: true,
    scene: MainScene,
});
目录
相关文章
|
3月前
|
安全 Java 调度
震撼揭秘!手撕并发编程迷雾,Semaphore与CountDownLatch携手AQS共享模式,让你秒懂并发神器背后的惊天秘密!
【8月更文挑战第4天】在Java并发编程中,AbstractQueuedSynchronizer (AQS) 是核心框架,支持独占锁与共享锁的实现。本文以Semaphore与CountDownLatch为例,深入解析AQS共享模式的工作原理。Semaphore通过AQS管理许可数量,控制资源的并发访问;而CountDownLatch则利用共享计数器实现线程间同步。两者均依赖AQS提供的tryAcquireShared和tryReleaseShared方法进行状态管理和线程调度,展示了AQS的强大功能和灵活性。
39 0
|
5月前
|
Java
【代码诗人】Java线程的生与死:一首关于生命周期的赞歌!
【6月更文挑战第19天】Java线程生命周期,如诗般描绘了从新建到死亡的旅程:创建后待命,`start()`使其就绪,获得CPU则运行,等待资源则阻塞,任务完或中断即死亡。理解生命周期,善用锁、线程池,优雅处理异常,确保程序高效稳定。线程管理,既是艺术,也是技术。
29 3
|
5月前
|
Java 开发者
震惊!Java多线程的惊天秘密:你真的会创建线程吗?
【6月更文挑战第19天】Java多线程创建有两种主要方式:继承Thread类和实现Runnable接口。继承Thread限制了多重继承,适合简单场景;实现Runnable接口更灵活,可与其它继承结合,是更常见选择。了解其差异对于高效、健壮的多线程编程至关重要。
34 2
|
6月前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)
|
安全 Java 数据库连接
ThreadLocal Java多线程下的影分身之术
后来,我把c删掉,变成了下面这样。如果我现在想查h,按照上面getEntry的逻辑,是不是遍历到3就停了,所以找不到h了? getEntry的逻辑表面确实是这样,但实际上getEntryAfterMiss、remove、gets时都会直接或者间接调用expungeStaleEntry会对表里的数据做整理。expungeStaleEntry()除了利用弱引用的特性对tab中Entry做清理外,还会对之前Hash冲突导致后移的Entry重新安放位置。所以不可能出现下面这种tab排放的。
34 0
|
存储 数据可视化 安全
造火箭 | 详解synchronized的前世今生
造火箭 | 详解synchronized的前世今生
54 0
|
JSON 移动开发 开发框架
使用 phaser3 从零实现一个战疫小游戏
在本文中,我将从零开发一个 H5 游戏,主要使用 phaser3 来制作的游戏。结合当下疫情的严峻形式,我也将一些元素融入到这款游戏中,同时希望疫情早日结束,早点摘下口罩,可以看到彼此脸上洋溢的笑容
913 0
|
存储 缓存 并行计算
CyclicBarrier:人齐了,老司机就发车了!(3)
CyclicBarrier:人齐了,老司机就发车了!(3)
126 0
CyclicBarrier:人齐了,老司机就发车了!(3)