zoukankan      html  css  js  c++  java
  • Phaser.js开发小游戏之洋葱头摘星星

    今天我们讲一下官方提供的一个经典案例:洋葱头摘星星。游戏内容是很精彩。

    因为宇宙大爆炸,天上的经常会有一些星星掉到地球上,洋葱头为了收集星星中的能量,就会很勤奋的收集落到地球上的每一棵星星。但是也需要注意安全,因为从天上掉下除了星星,还有发射性物质,如果不小心收集到了放射性物质,就会有生命危险。

    OK,现在开始制作游戏。

    制作游戏的过程其实和我们平时做网站一样的:

    1. 了解需求
    2. 设计师设计页面
    3. 切图布局
    4. 游戏功能制作

    需求我们已经了解了,设计师我们暂时没有,也暂时不用切图,先用官网提供的素材资源,也就是游戏中的图片,素材源码zip包

    注意:在开发 Phaser.js 小游戏项目的时候,一定要注意一点的就是要开启一个服务器来运行游戏,因为 Phaser.js 内部有用到 ajax 请求本地资源,如果不开始本地服务器就会加载不到静态资源

    引入资源

    拿到素材后,把里面的 assets 放到项目根目录下面,然后新建一个 index.html,在 html 文件中引入 phaser.js 文件

    <script src="../../lib/phaser.min.js"></script>
    

    第一步:创建游戏主程序

    let game = new Phaser.Game({
        type:Phaser.AUTO,
         800,
        height: 600,
        scene: {
            preload: preload,
            create: create,
            update: update
        },
        physics:{
          default:"arcade",
          arcade:{
            gravity:{
              y:300
            },
            debug:true
          }
        }
    })
    
    //预加载资源
    function preload (){}
    //创建场景
    function create (){}
    //实时监听每帧更新
    function update (){}
    
    

    首先我们用 Phaser.Game 创造出我们的游戏世界,并将游戏中的配置传入到游戏世界中。例如地球是我们世界

    type

    type 是告诉游戏世界是用 canvas 还是用 WebGL 渲染,用 Phaser.AUTO 它将自动尝试使用WebGL,如果浏览器或设备不支持,它将回退为Canvas。可以看成我们地球是什么物质创建的。

    width、height

    widthheight 就是告诉游戏世界的面积有多大,我们这里是 800px * 600px。也就是地球的大小。

    scene

    scene 是开启游戏场景,也就是我们需要在世界中创建游戏的生态。可以看成是地球的生态圈物质:空气、水、动物、植物....

    scene.preload

    scene.preload 是预加载资源的函数,在这里面先把游戏资源加载好,以备后期使用

    scene.create

    scene.create 主要用来创建场景,将资源加载进去,并且处理游戏中的逻辑,比如物体碰撞之后的处理之类的

    scene.update

    scene.update 每一帧的时候就会执行,有两个参数:time,delta,time 是执行了多长时间,单位是 msdelta 是间隔时间,默认是 16ms,也就说是每个 16ms 就会执行一次 update

    physics

    physics 开启游戏中的物理引擎,就像我们地球上的重力、摩擦力...各种物理元素

    physics.default

    一定要加上这个属性,不然物理环境不会生效。这个属性的作用就是配置采用什么样的物理引擎。有三个配置:'arcade''impact''matter'。三个物理引擎区别后面再细说。一般我们都用的是 'arcade'

    physics.arcade

    arcade 物理引擎配置

    physics.arcade.gravity

    设置重力加速度,px/s 为单位

    physics.arcade.gravity.y

    设置 y 轴上的重力加速度

    physics.arcade.gravity.debug

    是否开启调试模式,如果开启了,就会给每个元素加上边框,还有移动的方向。

    预加载资源

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

    在这里 this 指向的是 Phaser.Scene 对象,this.load 返回一个 Phaser.Loader.LoaderPlugin 对象,然后用 setBaseURL 设置静态资源的基本路径。

    image 方法两个参数:资源别名、资源路径。例如:sky 就是这个资源的快捷名称,sky.png 就是资源路径。这个方法就是用来预加载对应的图片,这个图片是静态的,就是不会动的那种。

    spritesheet 方法他们称为是精灵表单,就和我们 CSS sprite 差不多,将一些图片合并放到一张大图上,但是区别在于每一张图片的尺寸大小是一样的。一般这个方法用到的地方就是需要经常变动的元素,比如一个人走路的不同状态,就会将不同的状态放到一张图上,并且根据帧的变动移动图片的位置,就像 CSS 中 backgroun-position 移动背景图一样。

    frameWidthframeHeight 就是展示的宽高

    preload 函数就是预加载了游戏中的各种资源,并且对每个资源设置了别名,方便后面引用,同时设定精灵元素,方便后面动态切换图片位移。

    做完这些工作后,页面上还是啥都没有,要想页面中有东西,就需要在 create 函数中将资源添加到场景中

    添加元素到场景

    添加背景图
    function create(){
        this.add.image(400,300,"sky");
    }
    

    this.add 返回的是 Phaser.GameObjects.GameObjectFactory 对象。

    this.add.image 创建一个游戏图片对象,并添加到场景中。它有四个参数,我们这里主要就用到前三个,第一个参数是 x 轴,第二个参数是 y 轴,第三个参数就是在 preload 中预加载的资源别名。

    在这里我们要重点注意的是 xy 轴的方式,在 Phaser3 中,资源的坐标不是从左上角定位的,而是从元素的中心开始定位的

    因此我们代码中设置的 400 和 300,就是 800/2600/2,也就是游戏世界的中心坐标。

    当宽高和游戏世界一样,中心坐标也是游戏世界一样,那么四个角的坐标也就和游戏世界对齐了。

    当然我们也可以改变原点坐标,this.add.image(0, 0, 'sky').setOrigin(0, 0),这样我们就可以从左上角对齐了。

    添加背景图之后游戏世界就变成下面这样了:

    添加平台

    背景图添加完了之后,我们就要添加几个平台,添加一点游戏难度。

    var platforms;
    
    function create ()
    {
        this.add.image(400, 300, 'sky');
        
        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');
    }
    

    在代码中,我们添加了 this.physics,使用这个属性必须在配置中添加 physics,不然就会报错。

    this.physics 返回 Phaser.Physics.Arcade. ArcadePhysics 对象。

    this.physics.add 返回 Phaser.Physics.Arcade.Factory 对象。

    this.physics.add.staticGroup() 主要作用是创建一个静态物理组,并返回一个 Phaser.Physics.Arcade.StaticGroup。我们在这里使用这个方式的原因将同一种静态物体组织到一起,只要控制全体就像控制个体一样。最后我们将返回的 StaticGroup 对象赋值给 platforms

    platforms.create 主要就是创建一个游戏对象并添加到静态物理组中。它原本有 6 个参数,我们这里用到了三个。

    前三个参数的作用和上面的 this.add.image 一样。

    platforms.create 返回的是一个 Game Object,一般返回的是 Phaser.Physics.Arcade.Sprite 对象。

    我们在代码中先是添加了一个平台对象,并用 setScale 将平台放大 2 倍。setScale 主要有两个参数:xy。如果没有设置 yy 就会用 x 值。setScale(2) 相当于 setScale(2,2)setScale 返回的是一个 Phaser.Physics.Arcade.Sprite 对象。

    当我们放大平台后,需要用 refreshBody 方法将游戏场景刷新一下。

    我们一共添加了 4 个平台,添加完之后的样子是这样的:

    添加英雄

    好了,地球已经被我们创建完成了,现在我们要添加英雄了,让他在游戏中诞生。

    我们需要在 create 函数中添加下面的代码:

    let 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
    });
    

    this.physics.add.sprite 我们将英雄添加到游戏场景中。返回的是 Phaser.Physics.Arcade.Sprite 实例

    然后用 setBounce 设置英雄的弹跳能力,如果是 0 就不会跳起来,如果是 1 就会一直跳来跳去。我们在这里设置的是 0.2

    setCollideWorldBounds(true) 这个方法的作用就是让英雄是否与世界边界碰撞,也就是我们平常写拖拽的时候,是否会超过浏览器的边界。如果为 false 那么英雄就会掉下去看不到了。我们这里设置的是 true,英雄不会掉到世界外面。

    OK,这个时候我们就可以看到英雄在游戏中了

    下面我们开始设置英雄的动作动画了。这些动画操作其实和预加载一样,是我们先声明在 create 方法中,当我们要使用的时候才会用里面的 key 值。

    this.animsPhaser.Animations.AnimationManager 实例。 Phaser.Animations.AnimationManager它是一个全局的对象,它主要用来创建动画以及配置动画,它可以将动画直接绑定全局的 Game 对象上,可以在游戏中的任意 Scene 中用到这个动画。

    this.anims.create 它会创建一个动画并将它添加到动画管理对象上。创建完成后,就可以到任意 Scene 中调用这个动画了。如果创建动画的名称已经存在了,它就会返回 false,如果不存在就会返回一个 Phaser.Animations.Animation 实例。在 this.anims.create 中需要传入动画的配置对象 config,用来设置不同的动画规则。

    在这里我们创建了三个动画:leftturnright,在三个动画中,我们都传入了不同的配置:

    key

    key 的作用是动画的名称

    frames

    frames 的作用是告诉动画使用精灵图片中的哪一个索引位置上的图片,上面我们说了精灵图片是将每一帧的图片放到一张大图上,而这个配置的作用就是让动画使用精灵图片中的哪一帧图片了。

    frames 的值是一个数组,数组中的每一项都是一个 Phaser.Types.Animations.AnimationFrame 对象

    例如:left 中传入的是 this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }), this.anims.generateFrameNumbers 返回的就是一个 Phaser.Types.Animations.AnimationFrame 数组对象。它第一个参数接受的是精灵图的 key 名,第二个参数是一个 Phaser.Types.Animations.GenerateFrameNames 配置。告诉动画选择精灵图片中的哪些位置的图片,像 left 中选择的就是 0-3 索引上的图片,right 中传入的是 5-8 索引上图片

    turn 里面传入的是一个 Phaser.Types.Animations.AnimationFrame 数组,frame:4 就是说用的是精灵图中索引为 4 的图片。

    其实和 CSSbackground-position 差不多,只是 background-position 需要我们自己计算,这里用 frames 自动帮我们选择了

    frameRate

    frameRate 的作用是帧率,也就是每秒钟播放多少张图片,例如 left 里面就是每秒播放 10 张图片,就是不停的切换 0-3 索引上的图片

    repeat

    repeat 重复的次数,-1 表示无限重复

    让英雄站到平台上

    现在英雄还不能站到平台上,我们要再 create 中加一个碰撞监测,让英雄站到平台上:

    this.physics.add.collider(player, platforms);
    

    键盘控制

    一般我们游戏都是用键盘在操作,大部分都是上下左右,我们这游戏也是一样,用键盘上面的上下左右键来控制。

    但是这个时候有个问题,就是上下左右键的事件逻辑写在哪里呢?这里和我们浏览器里面的监听有点不听,一般浏览器里面的监听是监听 window 下的按的是哪一个键,然后根据按的键来做对应的操作。当然,这个逻辑也一样,也是根据按的键来做对应的操作,只是并不是监听 window 下的事件,而是在 update 回调中监听你按的是哪个键,也就是每一帧更新的时候就会判断你按了哪一个键。

    OK,我们了解了这个方式之后,我们就在 update 回调中开始写对应的逻辑

    function update(time,detla){
        let cursors = this.input.keyboard.createCursorKeys();
        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.inputPhaser.Input.InputPlugin 实例

    this.input.keyboardPhaser.Input.Keyboard.KeyboardPlugin 实例

    this.input.keyboard.createCursorKeys 返回一个 Phaser.Types.Input.Keyboard.CursorKeys 实例,这个实例里面包含了 updownleftrightspaceshift 6个键。

    同时上面 6 个键属于 Phaser.Input.Keyboard.Key 实例,有一个 isDown 属性,用来判断是否被按下了。

    首先我们代码里面做了一个逻辑判断,如果没有按左右键就会将水平速度设为 0,并执行 turn 动画:

    player.setVelocityX(0);
    player.anims.play('turn');
    

    对于按了左右键要执行的事情,也就很简单了,left 就是将水平速度设为 -160right160

    按上键的时候,就会将垂直速度改为 -330330 像素每秒,由于设置了重力,他就会自动落回地面。

    再按上键的时候,我们还做了一个边缘监测,player.body.touching.down 这个是判断英雄是否和地面接触了,否则他就会一直往上跳的。

    加上 player.body.touching.down 后,就正常了,现在游戏画面就是这样的了:

    让星星落下来

    我们现在要做的一个事就是让星星从天上掉下来了。

    我们先要计划要在我们游戏中放几颗星星,在我们例子中暂时定 12 颗,颗数无所谓了,你们可以自己定。

    既然有 12 颗星星,那么我们肯定不可能一颗一颗的去生成以及管理。所以我们就要用 来管理。上面的平台用的是 静态组,因为星星要动,所以就用

    create 函数中添加下面的代码:

    let stars = this.physics.add.group({
        key: 'star',
        repeat: 11,
        setXY: { 
            x: 12, 
            y: 0, 
            stepX: 70 
        }
    });
    stars.children.iterate(function (child) {
        child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
    });l
    

    this.physics.add.group 创建一个 物理组 对象,可以传入两个参数:

    在这里我们传入的是 Phaser.Types.GameObjects.Group.GroupCreateConfig

    key

    key 引用的游戏资源别名

    repeat

    repeat 表示重复使用多少次 key 资源,从 0 开始,我们设置 11,就是生成 12 颗星星

    setXY

    setXY 是用来设置资源在游戏中的位置

    setXY.x

    资源的水平位置

    setXY.y

    资源的垂直位置

    setXY.stepX

    每个资源在水平位置从 0 开始递增的值。例如我们代码里面设置的是 70,那么第一颗星星 x0,第二颗 x70,第三颗 x140,以此类推

    this.physics.add.group返回的是 Phaser.Physics.Arcade.Group 对象实例,然后将实例赋值给 stars

    stars.children物理组中的 成员,它是一个 Phaser.Structs.Set 对象实例,其实可以看成是一个 Set 类型的数据。

    stars.children.iterate 就是遍历 Phaser.Structs.Set,在这里就是对每一颗星星进行遍历处理。

    child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8)); 就是设置每一颗星星的垂直弹力值。

    这样我们游戏就会是下面这样的画面:

    将星星放到平台上

    上面的图片好像有点奇怪,居然直接落下去了,本来我们打算让星星放到平台上的。

    既然如此,我们只需要让星星和平台产生碰撞就好了:

    this.physics.add.collider(stars, platforms);
    

    游戏就成为下面这样的画面了:

    摘星星

    总算到了最关键一步了。

    要摘星星,其实我们可以看成是英雄和星星碰撞的时候让星星隐藏。在 Phaser3 中物体碰撞除了有碰撞检测事件 collider 之外,还有物体覆盖事件 overlap,就像下图一样。

    我们在 create 中添加覆盖检测代码:

    this.physics.add.overlap(player, stars, collectStar, null, this);
    function collectStar (player, star){
        star.disableBody(true, true);
    }
    

    this.physics.add.overlap 用来创建一个碰撞覆盖对象,可以监听到两个物体是否发生了碰撞覆盖。它会返回一个 Phaser.Physics.Arcade.Collider 对象实例。

    它可以传入 5 个参数:

    1. 前面两个是覆盖的游戏元素对象
    2. 第三个是覆盖的回调函数
    3. 第四个也是覆盖的回调函数,但是必须返回一个 boolean 值。
    4. 第五个是函数执行的作用域对象指向

    然后我们在覆盖的回调函数中将星星隐藏了。

    最后的效果就是下面的图:

    加上得分数据

    现在我们加上一个得分的文字显示,每次获取一个星星就将得分 +10

    create 函数中加上下面的代码:

    scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
    

    collectStar 函数中添加下面的代码:

    score += 10;
    scoreText.setText('Score: ' + score);
    

    scorescoreText 添加到全局代码中。

    this.add.text 是创建一个文字对象并添加到当前的场景中。返回一个 Phaser.GameObjects.Text 实例,将实例赋值给 scoreText

    它传入 4 个参数:

    1. 文字 x 坐标
    2. 文字 y 坐标
    3. 文字内容
    4. 文字的样式配置

    然后在覆盖监听回调中,做了两个事情:

    1. score 加 10,也就是每次碰到一个星星就加 10
    2. setText 设置 scoreText 的文字内容,将最新的 score 显示上去。

    最后游戏就会成为下面的样子,在左上角显示我们的星星:

    加上炸弹

    现在我们加个炸弹,让游戏添加点难度,增加游戏可玩性。

    首先我们在 create 中添加一个 炸弹物理组

    bombs = this.physics.add.group();
    

    在全局设置 bombs 变量

    然后我们添加一个创建炸弹的方法

    function createBomb(bombs){
        var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
        var bomb = bombs.create(x, 16, 'bomb');
            bomb.setBounce(1);
            bomb.setCollideWorldBounds(true);
            bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
    }
    

    传入刚才添加的炸弹物理组 bombs

    我们先设置一下炸弹的 x 轴位置,判断英雄的 x 位置是否小于 400,这个 400 就是游戏宽度的一半,就是判断英雄在左边还是右边,如果小于 400 就是在左边,如果在左边,炸弹就从 400-800 也就是右边落下,如果英雄在左边就从右边落下。

    再然后就创建一个炸弹元素,y 轴设为 16,引用 bomb 资源

    将炸弹的弹力值设为 1,让它一直弹,如果不弹那还有什么难度。

    然后设置边界碰撞,不让炸弹掉出去了。

    再设置炸弹在 xy 轴上的速度

    我们在两个地方创建炸弹,一个在 create 中创建,也就是刚进游戏的时候就创建炸弹。还有一个就是当星星都获取完了之后,再创建一次。

    当星星都获取完的代码,我们需要修改一下 collectStar 函数,也就是英雄和星星的覆盖回调函数:

    function collectStar (player, star){
        star.disableBody(true, true);
        score += 10;
        scoreText.setText('Score: ' + score);
    
        if (stars.countActive(true) === 0)
        {
            stars.children.iterate(function (child) {
                child.enableBody(true, child.x, 0, true, true);
            });
            createBomb(bombs)
        }
    }
    

    stars.countActive(true) === 0 这个就是判断星星激活的数量是否为 0,如果为 0 还需要将星星用 enableBody 重新添加到场景中。然后创建炸弹。

    OK,现在创建炸弹完成,我们要开始处理英雄碰撞炸弹的情况

    this.physics.add.collider(player, bombs, hitBomb, null, this);
    function hitBomb (player, bomb){
        this.physics.pause();
        player.setTint(0xff0000);
        player.anims.play('turn');
        gameOver = true;
    }
    
    

    首先我们创建一个英雄和炸弹的碰撞检测

    然后再碰撞回调中处理逻辑。

    先让物理环境暂停,再将英雄变成红色,然后运行 turn 动画,最后将 gameOver 变量改成 true

    这样我们游戏就处理完了。

    现在游戏就是这样了:

    最后,如果要下载游戏,可以下载上面的素材源码zip包

    这篇文章写了三天左右,内容有点多,大家可以收藏后分开看。

  • 相关阅读:
    暗通道先验去雾算法及其几何意义的解释
    几种去雾算法介绍
    大气散射模型的推导
    散射介质环境中偏振成像图像的去散射方法
    最近的笔面试题知识整理一
    了解 Web Service
    数梦工厂笔试题回顾一----finally在return之后执行还是之前?
    Struts2的配置文件的加载
    Struts2中Action配置
    struts.xml的语法
  • 原文地址:https://www.cnblogs.com/fws407296762/p/14011182.html
Copyright © 2011-2022 走看看