这是我参与更文挑战的第5天,活动详情查看: 更文挑战
前言
开发网页游戏,很久之前用AS3做合金弹头,现在Flash慢慢没落之后,新一代的H5游戏引擎崛起,比较知名的有coco2d.js、Unity-2D、laya,以及老东家的 Egret2D、Egret3D,这些主流引擎都非常强,如果你对TS比较熟练的话,这里推荐你用egret,如果是你是原生JS的爱好者(其实本文的主角也支持TS,不过既然你都用TS了,何不尝试egret呢),今天我将带你一起领略一款轻量高效又免费的H5引擎:Phaser。
Phaser.js背景
Phaser是一个HTML5游戏框架,致力于帮助开发者快速制作强大的跨浏览器HTML5游戏。不像有些框架,Phaser已完全调配手机浏览器。Phaser唯一的浏览器要求是支持canvas标签,Phaser支持两种渲染模式:canvas和webgl,基于浏览器支持可自由切换,其实它也是基于pixi来实现的,可以在支持Canvas的浏览器中直接安装Phaser来进行游戏开发,仅仅在html文件中引入一个phaser.min.js即可上手开发游戏。
黑马编辑器
工欲善其事必先利其器,一个好的开发工具,可以解放你的双手,更快更高效的完成开发工作,如果你是一位大牛,用记事本作为工具相信也没人拦着你。
黑马编辑器,可以说是为phaser量身定制的工具,可以快速设计地图等等游戏所需要的素材,在黑马编辑器里你可以实现素材和代码的联调,不必再依赖于PS切图,同时支持输出安卓设置和webapp的应用。具体功能不多提,放个链接大家亲自去尝试一下:mightyeditor.mightyfingers.com
Phaser生命周期
创建一个phaser游戏世界,首先需要 new 一个Phaser.Game实例
Phaser.Game(width, height, renderer, parent, state, transparent, antialias, physicsConfig)
// width: 游戏的宽度,单位为px
// height: 游戏的高度,单位为px
// renderer: 使用哪种渲染方式,分别有Phaser.CANVAS、Phaser.WEBGL 、Phaser.AUTO
// parent: 用来放置canvas元素的父元素,可以是一个元素id,也可以是dom元素本身
// state: state可以理解游戏首先加载的场景(选填)
// transparent: 是否使用透明的canvas背景(选填)
// antialias: 是否启用抗锯齿(选填)
// physicsConfig: 游戏物理系统配置参数(选填)
// 举个例子
window.onload = function() {
var game = new Phaser.Game(600,400,Phaser.CANVAS,'root');
this.init = function(){
// 初始化
}
this.preload= function(){
// 资源加载
}
this.create = function(){
// 创建游戏场景
}
this.update = function(){
// 更新周期
}
this.render = function(){
// 渲染完毕后执行的方法
}
}
// 这段代码的意思就是在id为root的dom上创建一个宽600高400的canvas区域作为游戏世界
Phaser最常用的四个生命周期是:preload(加载)、create(准备就绪)、update(更新周期)、render(渲染完成)。
init
预加载时期,这个阶段,一般会预加载一些必要的资源图片,比如loading图、背景音乐以及少量其他资源,因为在某个阶段加载过多资源,会造成用户等待时间过长,如果用户当时的网络状态不佳,那就更糟糕了,所以这个时候一般优先加载必要的资源,以及loading图,不能以黑屏的形式展现给用户,告诉用户游戏正在加载中,将其他多余的资源放在不同的函数里的preload里加载,类似负载均衡的效果。
preload
游戏中有很多资源,比如图片、精灵、音频、视频、纹理、JSON文件等等,尽管我们有预加载的场景,但是为了缩短进入页面时加载的时间,可以分摊一些到其他场景,也就是其他场景的preload方法里。
create
如果存在preload方法,则会在加载完毕后执行此方法;否则将在进入该场景时直接执行此方法。这个create方法相当于react里的componentDidMount,是在资源获取之后加载,并且只会加载一次。
update
update是更新周期自动执行的方法,方法里的内容会不断支持和更新,相当于vue里的watch函数,例如在 play 场景的 update 方法中可以去检测两个物体是否有接触。
render
在create方法渲染完毕后执行,一般在这个方法中渲染物体的边缘,观察物体的碰撞区域,以及一些其他的自定义函数。
其他的生命周期方法
loadRender()、loadUpdate()、paused()、pausedUpdate()、preRender()、resize()、resumed()、shutdown()。这些生命周期函数并非不常用,根据项目的业务需求适当增添。
Phaser场景管理
在正常的页游项目中,一般会有若干个游戏分支、游戏场景、关卡地图等等。那么只写一个主函数往往会代码的整洁性不好,可迭代性低。常见的场景管理一般以模块、游戏对象来划分js文件和函数,比如:
window.onload = function(){
var game = new Phaser.Game(800, 450, Phaser.CANVAS, "gamebox");
game.state.add('boot', Boot);
game.state.add('loading', Loading);
game.state.add('menu', Menu);
game.state.add('main', GameMain);
game.state.start('boot')
}
function Boot(game){
this.preload = function() {
game.load.spritesheet('loading', './img/tap-to-play.png', 330, 40);
}
this.create = function() {
if(!game.device.desktop){
this.scale.scaleMode = Phaser.ScaleManager.EXACT_FIT;
}
game.state.start("loading")
}
}
在页面加载完成之后,实例化一个Phaser.Game,然后给game增加4个函数Boot、Loading、Menu、GameMain。并在游戏实例化完成时即执行Boot函数。我们可以在Boot函数里加载loading图和必要的先行文件,然后在资源加载并渲染完之后,执行Loading函数,一次类推。当然这里的Boot函数,你也可以给loading增加事件,只有用户点击之后才会进入下一个环节,这个根据业务功能随意发挥。
资源的加载
在各个模块函数的proload方法里,一般加载本模块需要的资源素材,比如
function preload(){
// game.load.image(key,url);
// 加载图片 key是资源名称(自定义),url是资源的相对路径
game.load.image("maptiles", "img/maptiles.png");
// game.load.spritesheet(key,url,frameWidth,frameHeight,frameMax);
// 加载精灵图 key是资源名称(自定义),url是资源相对路径,frameWidth是帧宽,frameHeight是帧高,frameMax是帧总数(选填)
game.load.spritesheet("npc-no1", "img/npc-2.png", 64, 64);
// game.load.atlas(key,textureURL,atlasURL,atlasData,format)
// 加载贴图纹理集 key是资源名称(自定义),textureURL是资源路径,atlasURL是描述文件路径,atlasData是数据对象,format是数据格式(JSON或XML)
// game.load.audio(key,urls);
// 加载音频 key是资源名称(自定义),urls(资源路径,可以多个)
game.load.audio('bullet','./img/load.ogg');
// game.load.audiosprite(key,urls,jsonURL,jsonData);
// 加载声音集 key是资源名称(自定义),urls是资源路径,jsonURL是描述分段信息的文件路径,jsonData是分段数据
// 以上只是常见的api,并未全部列举。
}
资源在preload方法里加载之后,可以添加一个加载指示条
game.load.onLoadComplete.add(function() {
// 资源加载完成事件
var progress = game.load.progress;
game.add.text(game.world.centerX,game.world.centerY,progress,{
fill:"#FFF",
fontSize:"16px"
});
// 加载完成之后会立刻执行create函数,所以会很短暂
});
// 具体样式可以改成图片或者其他的,这里只是用数字演示资源加载进度
在create方法里,需要将图片、精灵等资源添加到舞台上
this.create = function() {
// 游戏场景的创建
game.add.image(0,0,'worldbg').scale.setTo(1);
// 背景音乐
var loadmusic = game.add.audio('bullet');
loadmusic.play();
// 玩家
player = game.add.sprite(2,2,'player')
game.physics.arcade.enable(player); // player开启物理属性
player.body.bounce.y = 0;
player.body.gravity.y = 0;
player.body.collideWorldBounds = true; // 开启player与世界的边缘检测
player.inputEnabled = true; // 启动事件
player.input.disableDrag(); // 禁止拖拽玩家精灵
player.animations.add('up',[12,13,14,15],8,true);
player.animations.add('down',[0,1,2,3],8,true);
player.animations.add('left',[4,5,6,7],8,true);
player.animations.add('right',[8,9,10,11],8,true);
}
注意:资源的加载一般在预加载中完成,之后才执行 create 方法,如果需要在游戏其他位置加载资源,应该使用game.load.start()。在运行项目时,如果你的资源里包含了音视频、json等等,需要开启服务器环境下,原因是跨域。开启服务器环境最简洁的做法:在项目文件夹下http-server -a 127.0.0.1 -p 9999,打开localhost:9999即可
如何让舞台中的player任务动起来,细心的同学已经发现我再前面的代码中给player添加了4个animations方法,分别是上、下、左、右,当执行player.animations.play(“up”)的时候,舞台中的任务会切换12-15之间的帧动画。这里放一张任务的精灵图,你便明白了。
现在只是增加了animations帧动画,还没有给出触发方法,比如键盘、鼠标或者丧心病狂的遥控器?!
var cursors;
var animationX = 80;
var animationY = 80;
function update() {
cursors = game.input.keyboard.createCursorKeys(); // 开启Keyboard对象
player.body.velocity.x = 0;
player.body.velocity.y = 0; // 消除惯性
if(cursors.left.isDown){
player.animations.play('left');
player.body.velocity.x = -animationX;
} else if(cursors.right.isDown){
player.animations.play('right');
player.body.velocity.x = animationX;
} else if(cursors.up.isDown){
player.animations.play('up');
player.body.velocity.y = -animationY;
} else if(cursors.down.isDown){
player.animations.play('down');
player.body.velocity.y = animationY;
} else {
// player.body.velocity.x = 0;
player.frame = 0; // 不动时停留在第0帧
}
}
这里很好理解,首先定义一个cursors来创建Keyboard对象,并自定义每次移动时的 上下左右偏移量(这里是80),定义Keyboard对象之后,就可以通过键盘操作人物player的animations动画了,比如每次按up键,人物都会执行animations.play('up'),并发生位置偏移。当然使人物移动不仅仅这一种方式,今天更重要的是认识Phaser和它的生命周期,玩转phaser游戏引擎,下一次继续。