网站地图    收藏   

主页 > canvas引擎 > Phaser游戏引擎 >

phaser3入门教程-快速入门

来源:未知    时间:2021-06-16 21:55 作者:小飞侠 阅读:

[导读] 第一节 - 前言 欢迎来看我们的第一个Phaser 3 游戏实例教程!在此我们将学习怎样做一个小游戏,它会有个玩家(player)在平台(platform)上来回跑、跳,收集星星,躲避坏蛋。在此过程...

phaser3入门教程-快速入门

第一节 - 前言

image

欢迎来看我们的第一个Phaser 3 游戏实例教程!在此我们将学习怎样做一个小游戏,它会有个玩家(player)在平台(platform)上来回跑、跳,收集星星,躲避坏蛋。在此过程中,我们将解释这个框架的一些核心特性。

Phaser是什么?

Phaser是一个HTML5游戏框架,它的目的是辅助开发者真正快速地制作强大的、跨浏览器的HTML5游戏。 做这个框架,主要是想发掘现代浏览器(兼及桌面和移动两类系统)的优点,。对浏览器的唯一要求是,支持画布(canvas)标签。

基本要求

下载这个zip文件,它含有本教程每一步的代码和资源。

你需要有一点非常、非常基础的JavaScript知识。

也请确认,你已经读过《起步指南》,它会引导你怎样下载Phaser,搭建本地开发环境,再让你看一眼Phaser工程的结构及其核心函数。

如果你已经看过《起步指南》,那么应当下载了Phaser,已经把一切搭建好,准备好写代码了。请下载本教程所需资源,并解压到你的web服务器根目录下。

在你的编辑器中选中、打开part1.html页面,让我们详细看看它的代码。前面是一小段HTML样板代码,它引入了Phaser;接下来的代码结构如下:

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    scene: {
        preload: preload,
        create: create,
        update: update    }};var game = new Phaser.Game(config);function preload (){}function create (){}function update (){}

这个config(配置)对象意味着你怎么配置Phaser游戏。有很多选项可以放在这个对象里,当你的Phaser知识增加时,你会碰到它们。不过在本教程中,我们只打算设置渲染器(renderer)、尺寸和默认Scene(场景)。

一个Phaser.Game对象实例(instance)赋值给一个叫game的局部变量,上述配置对象传给这个实例。这将开始启动Phaser的过程。

在Phaser 2 中,对象game用作几乎所有内部系统的入口,并常常是通过全局变量访问它。在Phaser 3 中不再如此,在全局变量中存储游戏实例不再有用。

属性type可以是Phaser.CANVAS,或者Phaser.WEBGL,或者Phaser.AUTO。这是你要给你的游戏使用的渲染环境(context)。推荐值是Phaser.AUTO,它将自动尝试使用WebGL,如果浏览器或设备不支持,它将回退为Canvas。Phaser生成的画布元素(canvas element)将径直添加到文档中调用脚本的那个节点上,不过也可以在游戏配置中指定一个父容器,如果你需要的话。

属性width和height设定了Phaser即将生成的画布元素的尺寸,在此例中是800 x 600 像素。这是游戏显示所用的分辨率,而你的游戏世界(world)可以是任意尺寸。

本教程后面还会涉及配置对象的scene属性的更多细节。

第二节 - 加载资源

By Richard Davey on 20th February 2018   @photonstorm

让我们加载游戏所需资源。要做到这一点,你要在场景中的一个叫preload(预加载)的函数内部,调用Phaser的Loader(加载器)。Phaser启动时会自动找到这个函数,并加载里面定义好的所有资源。

目前preload函数是空的。把它改为:

function preload (){
    this.load.image('sky', 'assets/sky.png');
    this.load.image('ground', 'assets/platform.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 }
    );}

这样将加载5个资源:4张图(image)和一个精灵表单(sprite sheet)。也许对于某些人,它看起来够明白了,但我还想说说第一个参数,它叫资源的key(键值,即'sky','bomb')。这个字符串是一个链接,指向已加载的资源,你在代码中生成游戏对象时将用到它。你可以随意使用任何有效的JavaScript字符串作为键值。

显示图像

要显示已经加载的一张图像,我们把下面的代码到create(生成)函数中:

this.add.image(400, 300, 'sky');

你可以在part3.html中看到这行代码。如果你是在浏览器中加载的,你现在应该看到一个游戏画面,布满蓝色天空作为背景:

image

400和300是图像坐标的x值和y值。为什么是400和300呢?这是因为,在Phaser 3 中,所有游戏对象的定位都默认基于它们的中心点。这个背景图像的尺寸是800 x 600像素,所以,如果我们显示它时将它的中心定在0 x 0,你将只能看到它的右下角。如果我们显示它时定位在400 x 300,你能看到整体。

提示:你可以用setOrigin(设置原点)来改变这种情况。比如代码this.add.image(0, 0, 'sky').setOrigin(0, 0),将把图像的绘制定位点重置为左上角。在Phaser 2 中,定位点是通过属性anchor(锚点)获取的,但在Phaser 3 中则通过属性originX和originY。

游戏对象的显示顺序与你生成它们的顺序一致。所以,如果你想放一个星星的精灵在背景上,你就要保证在添加天空(sky)图像之后才添加星星(star)图像:

function create (){
    this.add.image(400, 300, 'sky');
    this.add.image(400, 300, 'star');}

如果你先放star(星星)图像,它将被sky(天空)图像盖住。

第三节 - 建立游戏世界

By Richard Davey on 20th February 2018   @photonstorm

在底层,代码this.add.image生成一个新的Image(图形)类游戏对象,并把它添加到当前场景的显示列表(display list)中。你的所有游戏对象都活在这个列表中。你可以把图像放置在任何位置,Phaser不会介意。当然,如果图像位于0x0到800x600这个区域之外,那么你视觉上看不到它,因为它已“脱离画面”,但它仍旧在场景中存在。

场景(Scene)自身没有确定的尺寸,在所有方向上都是无限延展的。镜头(Camera)系统控制着你观看场景的视野,你可以随意移动、推拉已激活的镜头。你还可以另外生成一些镜头,用于别的观看场景的视野。这一话题已经超出本特定教程,完全可以说,Phaser 3 的镜头系统,能力大大地超过Phaser 2的。以前完全不可能的东西现在可以了。

现在让我们搭建场景,添加一张背景图像和几个平台。这是更新后的create函数:

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的调用。这意味着我们在使用Arcade(游乐场)物理系统(Physics system),不过在此之前我们还需要把它添加到游戏配置中,以便告诉Phaser我们的游戏需要它。所以让我们更新一下,引入对物理系统的支持。这是修订后的游戏配置:

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 300 },
            debug: false
        }
    },
    scene: {
        preload: preload,
        create: create,
        update: update    }};

新加的是physics属性。这些代码就位之后,如果运行它(你可以在本教程的zip文件中part4.html中找到),你将看到一个更有游戏样子的场景:

image

我们有了一个背景和一些平台,可以这些平台到底怎么样才能运作起来呢?

第四节 - 平台

By Richard Davey on 20th February 2018   @photonstorm


我们刚加了一堆代码到create函数中,此函数应该更详尽地解释一下。首先,这一部分:


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

这一句生成一个静态物理组(Group),并把这个组赋值给局部变量platforms。在Arcade物理系统中,有动态的和静态的两类物体(body)。动态物体可以通过外力比如速度(velocity)、加速度(acceleration),得以四处移动。它可以跟其他对象发生反弹(bounce)、碰撞(collide),此类碰撞受物体质量和其他因素影响。


与此明显不同的是,静态物体只有位置和尺寸。重力对它没有影响,你不能给它设置速度,有东西跟它碰撞时,它一点都不动。名副其实,完全是静态的。所以用作地面和平台很完美,我们打算让玩家在上面来回跑动。


那么什么是组呢?如其名所示,是把近似对象组织在一起的手段,控制对象全体就像控制一个统一的个体。你也可以检查组与其他游戏对象之间的碰撞。组能够生成自己的游戏对象,这是通过便利的辅助函数如create实现的。物理组会自动生成已经开启物理系统的子项(children),省得你处理时跑腿。


平台组做好了,我们现在可以用它生成平台:


platforms.create(400, 568, 'ground').setScale(2).refreshBody();


platforms.create(600, 400, 'ground');

platforms.create(50, 250, 'ground');

platforms.create(750, 220, 'ground');

这就生成了场景,如前所见。


在预加载时,我们输入了图像'ground'。它是个简单的绿色长方形,尺寸是400 x 32像素,将用于我们的基础平台:


image.png


上述代码的第一行,添加一张新的地面图像到400 x 568的位置(请记住,图像定位基于中心点)——问题是,我们需要这个平台撑满游戏的宽度。否则玩家就会掉出边界。要做到这一点,我们用函数setScale(2)把它按x2(两倍)缩放。现在它的尺寸是800 x 64了,恰好符合我们的要求。要调用refreshBody(),这是因为我们缩放的是一个 静态 物体,所以必须把所作变动告诉物理世界(physics world)。


地面已经缩放、就位,现在该别的平台了:


platforms.create(600, 400, 'ground');

platforms.create(50, 250, 'ground');

platforms.create(750, 220, 'ground');

这个步骤跟前面完全相同,只是不需要缩放,因为他们的尺寸本来就正好。


3个平台已经放到画面各处,距离合适,以便玩家能蹦上去。


那么让我们添加玩家。

第5节 - 玩家

By Richard Davey on 20th February 2018   @photonstorm


我们已经有了可爱、诱人的平台,但还没有人在上面跑动。让我们改一下。


做一个新的变量player,并把下面的代码添加到create函数中。你可以在part5.html中看到这些:


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

});

这里有两件不同的事情:生成物理精灵(sprite),生成精灵能用到的几个动画。


物理精灵

代码第一部分生成精灵:


player = this.physics.add.sprite(100, 450, 'dude');


player.setBounce(0.2);

player.setCollideWorldBounds(true);

这样生成一个新的精灵,叫player(玩家),位于100 x 450像素,在游戏的下部。精灵是通过物理游戏对象工厂函数(Physics Game Object Factory,即this.physics.add)生成的,这意味着它默认拥有一个动态物体。


精灵生成后,被赋予0.2的一点点反弹(bounce)值。这意味着,它跳起后着地时始终会弹起那么一点点。然后精灵设置了与世界边界(bound)的碰撞。——边界默认在游戏尺寸之外。我们(通过player.setCollideWorldBounds(true))把游戏(的世界边界)设置为800 x 600后,玩家就不能不跑出这个区域了。这样会让玩家停下来,不能跑出画面边界,或跳出顶边。


动画

如果回顾一下preload函数,你会看到'dude'是作为精灵表单(sprite sheet)载入的,而非图像。这是因为它包含了动画帧(frame)。完整的精灵表单是这个样子的:


image


总共有9帧,4帧向左跑动,1帧面向镜头,4帧向右跑动。注意:Phaser支持翻转精灵,以节省动画帧,不过因为这是教程,我们将保持老派做法。


我们定义两个动画,叫'left'和'right'。这是'left'动画:


this.anims.create({

    key: 'left',

    frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),

    frameRate: 10,

    repeat: -1

});

'left'动画使用0, 1, 2, 3帧,跑动时每秒10帧。'repeat -1'告诉动画要循环播放。


这是我们的标准跑动周期。反方向的动画把这些重复一下,键值用'right'。最后一个动画键值用'turn'(转身)。


补充信息:在Phaser 3 中,动画管理器(Animation Manager)是全局系统。其中生成的动画是全局变量,所有游戏对象都能用到它们。它们分享基础的动画数据,同时管理自己的时间轴(timeline)。这就使我们能够在某时定义一个动画,却可以应用到任意多的游戏对象上。这有别于Phaser 2,那时动画只属于据以生成动画的特定游戏对象。

第6节 - 添加物理系统

By Richard Davey on 20th February 2018   @photonstorm

Phaser支持多种物理系统,每一种都以插件形式运作,任何Phaser场景都能使用它们。在本文写作时,已经装有Arcade, Impact, Matter.js三种物理系统。针对本教程,我们将给我们的游戏使用Arcade物理系统,它简单,轻量,完美地支持移动浏览器。

物理精灵在生成时,即被赋予body(物体)属性,这个属性指向它的Arcade物理系统的Body。它表示精灵是Arcade物理引擎中的一个物体。物体对象有很多属性和方法,我们可以玩一下。

比如,在一个精灵上模仿重力效果,可以这么简单写:

player.body.setGravityY(300)

这是个随意的值,但逻辑讲,值越大你的对象感觉越重,下落越快。如果你把这些加到你的代码里,或者运行part5.html,你会看到玩家不停地往下落,完全无视我们先前生成的地面:

image

原因在于,我们还没有测试地面和玩家之间的碰撞。

我们已经跟Phaser说,我们的地面和平台将是静态物体。但是我们没有那么做,反而生成了动态的。如此,当玩家和他们碰撞时,玩家会停止一瞬,然后全部崩塌。这是因为,除非别那么说,否则地面精灵是会移动的物体,当玩家碰到它时,碰撞导致的力会作用语地面,因此两个物体交换彼此的速度,于是地面也开始下落。

要想玩家能与平台碰撞,我们可以生成一个碰撞对象。该对象监控两个物体(可以是组),检测二者之间的碰撞和重叠事件。如果发生事件,这时它可以随意调用我们的回调函数。不过仅仅就与平台间的碰撞而言,我们没必要那么做:

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

碰撞器(Collider)是施魔法的地方。它接收两个对象,检测二者之间的碰撞,并使二者分开。在本例中,我们把玩家精灵和平台组交给它。它很聪明,可以执行针对所有组成员的碰撞,所以这一个调用就能处理与组合以及所有平台的碰撞。结果就有了一个稳固的平台,不再崩塌:

image

第7节 - 键盘控制

By Richard Davey on 20th February 2018   @photonstorm

碰撞很棒了,不过我们非常想玩家动起来。你可能想到了,去找文档,搜一搜怎样添加事件监听器,但这里不需要。Phaser有内置的键盘管理器,用它的一个好处体现在这样一个方便的小函数:

cursors = this.input.keyboard.createCursorKeys();

这里把四个属性up, down, left, right(都是Key对象的实例),植入光标(cursor)对象。然后我们要做的就是在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);}

我们添加了很多代码,不过都还相当易读。

它做的第一件事,是查看方向左键是不是正被按下。如果是,我们应用一个负的水平速度,开动奔跑动画'left'。如果是方向右键正被按下,我们按字面意思做反向动作。通过清除速度值,再如此设置,一帧一帧,形成一个“走走停停”(stop-start)式的运动。

玩家精灵只有键被按下时才移动,抬起时立即停止。Phaser也允许你用动量(momentum)和加速度(acceleration)生成更为复杂的动作,不过这里已经得到我们的游戏所需要的效果了。键盘检测的最后部分,如果没有键被按下,就设置动画为'turn',水平速度为0。

赶快

代码的最后部分添加了跳起功能。方向up键是跳起键,我们检查它有没有被按下。不过我们同时也检测玩家是不是正与地面接触,否则在半空中还会往上跳。

如果所有这些条件都符合,我们应用一个垂直速度,330像素每秒。玩家会自动落回地面,因为有重力。控制已经就位,我们现在有了一个可以探索的游戏世界。请加载part7.html,玩一玩。尝试调整各个值,比如跳起值330,调低,调高,看看会有什么效果。

image

第8节 - 收集星星

By Richard Davey on 20th February 2018   @photonstorm

该给我们的小游戏定个目标了。让我们撒几颗星星到场景中,让玩家来收集。要做到这一点,我们会生成一个新的组,叫'stars',再充实它。在生成函数中,我们加入如下代码(这些可以在part8.html中看到):

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

这个过程跟我们生成平台组近似。因为需要星星移动、反弹,我们生成动态物理组,而不是静态的。

组可以接收配置对象,以便于设置。在本例中,组配置对象有3个部分:首先,它设置纹理key(键值)为星星图像。这意味着配置对象生成的所有子项,都将被默认地赋予星星纹理。然后,它设置重复值为11。因为它自动生成一个子项,重复11次就意味着我们总共将得到12颗,这正好是我们的游戏所需要的。

最后的部分是setXY——这用来设置组的12个子项的位置。每个子项都将如此放置:初始是x: 12,y: 0,然后x步进70。这意味着第一个子项将位于12 x 0;第二个离开70像素,位于82 x 0;第三个在152 x 0,依次类推。'step'(步进)值用于组生成子项时加以排布,真是很方便的手段。选用值70是因为,这意味着所有12个子项将完美地横跨着布满画面。

下一段代码遍历组中所有子项,给它们的bounce.y赋予0.4到0.8之间的随机值,反弹范围在0(不反弹)到1之间(完全反弹)。因为星星都是在y等于0的位置产出的,重力将把它们往下拉,直到与平台或地面碰撞为止。反弹值意味着它们将随机地反弹上来,直到最终恢复安定为止。

如果现在我们这样就运行代码,星星会落下并穿过游戏底边,消失不见了。要防止这个问题,我们就要检测它们与平台的碰撞。我们可以再使用一个碰撞器对象来做这件事:

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

与此类似,我们也将检测玩家是否与星星重叠:

this.physics.add.overlap(player, stars, collectStar, null, this);

这会告诉Phaser,要检查玩家与组中任何一颗星星的重叠。如果检测到,他们就会被传递到'collectStar'函数:

function collectStar (player, star){
    star.disableBody(true, true);}

简单来说,星星带着个已关闭的物体,其父级游戏对象被设置为不活动、不可见,即将它从显示中移除。现在运行一下游戏,我们得到一个玩家,它左冲右突的,跳起,从平台反弹,收集头顶上落下的星星。不错,毕竟就这么几行、多半看起来还很好理解的代码:)

image

第9节 - 计分

By Richard Davey on 20th February 2018   @photonstorm

最后我们打算给游戏增加两处改进:一个需要躲避的敌人,它会杀死玩家;收集到星星时得分。首先是得分。

为了做这个,我们将使用游戏对象Text(文本)。在此我们生成两个新的变量,一个持有实际得分,一个文本对象本身:

var score = 0;var scoreText;

scoreText在create函数中构建:

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

16 x 16是显示文本的坐标位置。'score: 0'是要显示的默认字符串,接下来的对象包含字号、填充色。因为没有指定字体,实际上将用Phaser默认的,即Courier。

下一步我们要调整collectStar函数,以便玩家捡到一颗星星时分数会提高,文本会更新以反映出新状态:

function collectStar (player, star){
    star.disableBody(true, true);

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

这样一来,每颗星星加10分,scoreText将更新,显示出新的总分。如果运行part9.html,你可以看到星星掉下来,收集星星时分数会提高。

image

最后一节我们将添几个坏蛋。


Part 10 - 跳跳弹

By Richard Davey on 20th February 2018   @photonstorm

现在该添加一些坏蛋,以此给我们的游戏收尾。这将给游戏增添很棒的挑战因素,这是此前缺乏的。

想法是这样的:你第一次收集到所有星星后,将放出一个跳跳弹。这个炸弹只是随机地在平台上各处跳,如果收集它,你就死了。所有星星会重新产出,以便你可以再次收集,如果你完成了,又会放出另一个炸弹。这将给玩家一个挑战:别死掉,取得尽可能高的分数。

我们首先需要的东西是给炸弹用的一个组,还有几个碰撞器:

bombs = this.physics.add.group();this.physics.add.collider(bombs, platforms);this.physics.add.collider(player, bombs, hitBomb, null, this);

炸弹当然会跳出平台,如果玩家碰到它们,我们将调用hitBomb函数。这个函数所作的就是停止游戏,使玩家变成红色:

function hitBomb (player, bomb){
    this.physics.pause();

    player.setTint(0xff0000);

    player.anims.play('turn');

    gameOver = true;}

现在看来还不错,不过我们要放出一个炸弹。要做到这一点,我们改一下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);

        });

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

    }}

我们使用一个组的方法countActive,看看有多少星星还活着。如果没有了,那么玩家把它们收集完了,于是我们使用迭代函数重新激活所有星星,重置它们的y位置为0。这将使所有星星再次从画面顶部落下。

下一部分代码生成一个炸弹。首先,我们取一个随机x坐标给它,始终在玩家的对侧画面,以便给玩家个机会。然后生成炸弹,设置它跟世界碰撞,反弹,拥有随机速度。

最终结果是个很棒的小炸弹精灵,它在画面上跳呀跳。尺寸小,开始的时候易于躲避。不过数量增加后就变得比较棘手!

image

我们的游戏已经做好了:)

结论

现在你已经学会怎样生成有物理属性的精灵,学会控制它的动作,学会使它与其他对象在一个小小的游戏世界里互动。你还可以做很多事情,以便增强它。为什么不扩展平台的尺寸并允许镜头摇动呢?也许可以增加不同类型的坏蛋,不同分值的收集活动,或者给玩家一个血条(health bar)。

或者,为了做个非暴力型的,你可以把它做成比快游戏(speed-run),仅仅挑战人们去尽可能快地收集星星。

有了本教程中所学到的东西,还有你能得到几百个实例的帮助,你现在已经为将来的项目准备了牢靠的基础。不过你总还会有疑问,需要建议,或者想分享你一直在做的东西,到时候请随意到Phaser论坛请求帮助。

Facebook即时游戏

Phaser 3 完全支持生成Facebook即时游戏。现在你已经学会怎样做Phaser游戏,为什么不看看怎样方便地转换为即时游戏呢?在我们专门 的《起步指南》里有。


自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论