前言
phaser是一个优秀的前端canvas库,封装了很多底层的实现,可以用来制作游戏,h5场景等。今年1月新发布了phaser3,到今天为止已经更新到了3.30。
声明
本游戏来自于phaser小站的官方教程,加入了一些个人的注释,本文旨在帮助各位观众老爷快速上手。
各位看官也可以直接移步phaser官网查看教程
小贴士
每一个步骤后面都贴了代码,如果因为我的写作方式让您难以接受,可以直接到每个步骤后面复制代码
准备工作
你需要一份phaser3.js
也可能需要一份文档
如果有一份随时查阅的范例当然更好
一个本地服务器
一份包含素材的空[项目]()
开始制作
这里是为观众老爷们准备的github仓库,有我们需要的素材和脚本文件
git clone https://github.com/YexChen/canvas_game.git
制作基本的游戏场景
打开我们的项目文件夹,修改index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/phaser.min.js"></script>
<style>
body{
}
</style>
</head>
<body>
<script>
//生成配置文件
const config = {
//初始化游戏类型
type : Phaser.AUTO,
width : 800,
height : 600,
//物理引擎
physics : {
default : ‘arcade‘,
arcade : {
//重力设置
gravity : {y : 300},
debug : false
}
},
//场景设置
scene : {
preload,
create,
update
}
}
//初始化游戏
let game = new Phaser.Game(config)
//游戏主要函数
function preload(){
}
function create(){
}
function update(){
}
</script>
</body>
</html>
然后在命令行运行http-server,打开浏览器,效果是不是出来了呢?
在上面的脚本中,我们定义了三个函数,preload,create,update,分别代表游戏中的预加载,初始化函数,更新函数。
加载主要素材
在preload函数中加入以下代码段:
this.load.image("sky","./img/sky.png")
this.load.image("star","./img/star.png")
this.load.image("ground","./img/platform.png")
this.load.image("bomb","./img/bomb.png")
this.load.spritesheet("dude","./img/dude.png",{frameWidth:32,frameHeight:48})
spritesheet是精灵图的加载方式,frameWidth是每帧的宽度,frameHeight是帧的高度,有兴趣的朋友们可以量一量
加载主要场景
摸了这么久的鱼,也该看点成果了吧,我们来制作主场景:
在create函数中加入:
this.add.image(400,300,"sky")
保存,刷新,我们的界面上是不是出现了一片蓝天呢?
this.add.image(offsetX,offSetY,imagename)
有兴趣的朋友们可以调下参数,试一下(0,0,‘sky‘)是在哪个地方的
让我们来继续添加场景吧,紧跟着上一句输入以下代码:
platforms = this.physics.add.staticGroup()
platforms.create(400,568,"ground").setScale(2,2).refreshBody()
platforms.create(600,400,"ground")
platforms.create(0,300,"ground")
platforms.create(600,200,"ground")
platforms.create(0,100,"ground")
create(x,y,imagename)
selScale(x,y):把图片缩放x,y倍,如果不设置y的话就按x的倍数缩放
这个refreshbody大家可以去掉,后面会有惊喜的
这样场景就绘制出来了,各位看官也可以自己设置参数,制作属于自己的游戏场景
尽量不要做出这种反人类设计就行。。emmm,你的游戏你做主咯
可能内容多,大家可能会打错地方,发一下完整的代码段:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/phaser.min.js"></script>
<style>
body{
}
</style>
</head>
<body>
<script>
//生成配置文件
const config = {
//初始化游戏类型
type : Phaser.AUTO,
width : 800,
height : 600,
//物理引擎
physics : {
default : ‘arcade‘,
arcade : {
//重力设置
gravity : {y : 300},
debug : false
}
},
//场景设置
scene : {
preload,
create,
update
}
}
//初始化游戏
let game = new Phaser.Game(config)
//游戏主要函数
function preload()
{
this.load.image("sky","./img/sky.png")
this.load.image("star","./img/star.png")
this.load.image("ground","./img/platform.png")
this.load.image("bomb","./img/bomb.png")
this.load.spritesheet("dude","./img/dude.png",{frameWidth:32,frameHeight:48})
}
function create()
{
this.add.image(400,300,"sky")
platforms = this.physics.add.staticGroup()
platforms.create(400,568,"ground").setScale(2,2).refreshBody()
platforms.create(600,400,"ground")
platforms.create(0,300,"ground")
platforms.create(600,200,"ground")
platforms.create(0,100,"ground")
}
function update()
{
}
</script>
</body>
</html>
人物的制作
什么都有了,主角怎么能少呢?
紧接着上一行,写下代码:
player = this.physics.add.sprite(100,450,‘dude‘)
刷新一下,是不是看到我们的男主角生成出来。。然后又入土为安了呢?
聪明的你应该会想到:是缺少了碰撞函数,那么,让我们来添加碰撞函数吧,紧接着添加:
player.setBounce(0.2)
player.setCollideWorldBounds(true)
刷新页面,哇塞
我们的男主真入土为安了!
嗯,这不是我们想要的结果,至少不是我的。。我们好像忘记给障碍物添加碰撞了,我们来加一下吧:
this.physics.add.collider(player,platforms)
大家还记得哪个refreshbody吗?如果你当时删掉了它,那么碰撞就还是不会成立(话说这种东西作者去内置一个方法不就好了么)
这里贴出目前完整的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/phaser.min.js"></script>
<style>
body{
}
</style>
</head>
<body>
<script>
//生成配置文件
const config = {
//初始化游戏类型
type : Phaser.AUTO,
width : 800,
height : 600,
//物理引擎
physics : {
default : ‘arcade‘,
arcade : {
//重力设置
gravity : {y : 300},
debug : false
}
},
//场景设置
scene : {
preload,
create,
update
}
}
//初始化游戏
let game = new Phaser.Game(config)
//游戏主要函数
function preload()
{
this.load.image("sky","./img/sky.png")
this.load.image("star","./img/star.png")
this.load.image("ground","./img/platform.png")
this.load.image("bomb","./img/bomb.png")
this.load.spritesheet("dude","./img/dude.png",{frameWidth:32,frameHeight:48})
}
function create()
{
this.add.image(400,300,"sky")
platforms = this.physics.add.staticGroup()
platforms.create(400,568,"ground").setScale(2,2).refreshBody()
platforms.create(600,400,"ground")
platforms.create(0,300,"ground")
platforms.create(600,200,"ground")
platforms.create(0,100,"ground")
player = this.physics.add.sprite(100,450,‘dude‘)
player.setBounce(0.2)
player.setCollideWorldBounds(true)
this.physics.add.collider(player,platforms)
}
function update()
{
}
</script>
</body>
</html>
添加动画效果和键盘控制器
要是不能操控的话,那这游戏也太佛系了,我们来添加一动画效果吧
Phaser类有个anims成员,用来管理所有的动画效果(说白了就是改变图片嘛),接下来我们通过代码感受一下,添加到上述代码后面:
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
})
然后我们初始化遥控器吧:
cursors = this.input.keyboard.createCursorKeys()
按下键盘方向键上下左右,诶?为什么没反应?
我们好像忘记在update函数中监听键盘了,难怪没反应,
在update函数中添加以下代码:
if(cursors.left.isDown)
{
player.setVelocityX(-50)
player.anims.play("left",true)
}
else if(cursors.right.isDown)
{
player.setVelocityX(50)
player.anims.play("right",true)
}
else{
player.setVelocityX(0)
player.anims.play(‘turn‘)
}
if(cursors.up.isDown && player.body.touching.down){
player.setVelocityY(-300)
}
好,现在移动我们人物,哇,走的怎么这么慢!各位自己改下参数吧,每个人都有不同的游戏爱好,你一定可以找到最适合自己的配置的,当然啦,也可以玩出滑冰模式,月球模式,鬼畜模式,神仙模式,鬼人正邪模式等等。。开发游戏主要靠想象力对吧
贴一下我们的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/phaser.min.js"></script>
<style>
body{
}
</style>
</head>
<body>
<script>
//生成配置文件
const config = {
//初始化游戏类型
type : Phaser.AUTO,
width : 800,
height : 600,
//物理引擎
physics : {
default : ‘arcade‘,
arcade : {
//重力设置
gravity : {y : 300},
debug : false
}
},
//场景设置
scene : {
preload,
create,
update
}
}
//初始化游戏
let game = new Phaser.Game(config)
//游戏主要函数
function preload()
{
this.load.image("sky","./img/sky.png")
this.load.image("star","./img/star.png")
this.load.image("ground","./img/platform.png")
this.load.image("bomb","./img/bomb.png")
this.load.spritesheet("dude","./img/dude.png",{frameWidth:32,frameHeight:48})
}
function create()
{
this.add.image(400,300,"sky")
platforms = this.physics.add.staticGroup()
platforms.create(400,568,"ground").setScale(2,2).refreshBody()
platforms.create(600,400,"ground")
platforms.create(0,300,"ground")
platforms.create(600,200,"ground")
platforms.create(0,100,"ground")
player = this.physics.add.sprite(100,450,‘dude‘)
player.setBounce(0.2)
player.setCollideWorldBounds(true)
this.physics.add.collider(player,platforms)
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
})
cursors = this.input.keyboard.createCursorKeys()
}
function update()
{
if(cursors.left.isDown)
{
player.setVelocityX(-50)
player.anims.play("left",true)
}
else if(cursors.right.isDown)
{
player.setVelocityX(50)
player.anims.play("right",true)
}
else{
player.setVelocityX(0)
player.anims.play(‘turn‘)
}
if(cursors.up.isDown && player.body.touching.down){
player.setVelocityY(-300)
}
}
</script>
</body>
</html>
早苗教你画星星
好了,人物有了,接下来应该做点道具了吧,我们来画点星星,在create函数中添加代码:
stars = this.physics.add.group({
key : ‘star‘,
repeat : 11,
setXY : {x: 20,y: 0,stepX:70}
})
stars.children.iterate(function(child){
//设置一下碰撞效果
child.setBounceY(Phaser.Math.FloatBetween(0.4,0.8))
})
this.physics.add.collider(stars,platforms)
我们初始化了一些星星,添加了小小的碰撞效果,然而。。。
并不能吃到星星!就像一大盘香喷喷羊蝎子在你面前你却不能吃(我这篇博客定到晚12点发就好了)
因为没有写星星和男主的碰撞函数,我们来在后面写一行
this.physics.add.overlap(player,stars,collectStar,null,this)
overlap(obj1,obj2,overcallback,processcallback,回掉中的上下文(this))
然后在文件底部加一个函数:
function collectStar(player,star)
{
//让star实体消失
star.disableBody(true,true)
}
好了,现在可以正常的吃星星了
贴上目前的代码段:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/phaser.min.js"></script>
<style>
body{
}
</style>
</head>
<body>
<script>
//生成配置文件
const config = {
//初始化游戏类型
type : Phaser.AUTO,
width : 800,
height : 600,
//物理引擎
physics : {
default : ‘arcade‘,
arcade : {
//重力设置
gravity : {y : 300},
debug : false
}
},
//场景设置
scene : {
preload,
create,
update
}
}
//初始化游戏
let game = new Phaser.Game(config)
//游戏主要函数
function preload()
{
this.load.image("sky","./img/sky.png")
this.load.image("star","./img/star.png")
this.load.image("ground","./img/platform.png")
this.load.image("bomb","./img/bomb.png")
this.load.spritesheet("dude","./img/dude.png",{frameWidth:32,frameHeight:48})
}
function create()
{
this.add.image(400,300,"sky")
platforms = this.physics.add.staticGroup()
platforms.create(400,568,"ground").setScale(2,2).refreshBody()
platforms.create(600,400,"ground")
platforms.create(0,300,"ground")
platforms.create(600,200,"ground")
player = this.physics.add.sprite(100,450,‘dude‘)
player.setBounce(0.2)
player.setCollideWorldBounds(true)
this.physics.add.collider(player,platforms)
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
})
cursors = this.input.keyboard.createCursorKeys()
stars = this.physics.add.group({
key : ‘star‘,
repeat : 11,
setXY : {x: 20,y: 0,stepX:70}
})
stars.children.iterate(function(child){
//设置一下碰撞效果
child.setBounceY(Phaser.Math.FloatBetween(0.4,0.8))
})
this.physics.add.collider(stars,platforms)
this.physics.add.overlap(player,stars,collectStar,null,this)
}
function update()
{
if(cursors.left.isDown)
{
player.setVelocityX(-200)
player.anims.play("left",true)
}
else if(cursors.right.isDown)
{
player.setVelocityX(200)
player.anims.play("right",true)
}
else{
player.setVelocityX(0)
player.anims.play(‘turn‘)
}
if(cursors.up.isDown && player.body.touching.down){
player.setVelocityY(-400)
}
}
function collectStar(player,star)
{
//让star实体消失
star.disableBody(true,true)
}
</script>
</body>
</html>
计分系统和炸弹,以及游戏结束
如果这个游戏没有计分系统和炸弹的话,那么这个游戏也太过于佛系了
在preload前面加上一行:
let score = 0
let scoreText
let gameover = false
然后在create函数中尾部添加:
bombs = this.physics.add.group()
scoreText = this.add.text(16,16,"score : 0",{fontSize: ‘32px‘,fill: "#000"})
这是一个炸弹群组,我们在所有星星被吃光以后使用这个群组添加炸弹
修改collectstar函数为:
function collectStar(player,star)
{
//让star实体消失
star.disableBody(true,true)
score += 1000
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(true)
bomb.setCollideWorldBounds(true)
bomb.setVelocity(Phaser.Math.Between(-200,200),20)
bomb.allowGravity = false
}
}
然后在create函数中加上碰撞:
this.physics.add.collider(bombs,platforms)
this.physics.add.collider(player,bombs,bombbbb,null,this)
在文件尾部加上撞击函数:
function bombbbb()
{
this.physics.pause()
//涂色,我觉得绿绿的比较好看
player.setTint(0x00ff00)
player.anims.play("turn")
gameover = true
}
至此,我们的游戏就大功告成啦。。诶,好像我的人物还能动?这个问题就留给大家自己解决了哈哈
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/phaser.min.js"></script>
<style>
body{
}
</style>
</head>
<body>
<script>
//生成配置文件
const config = {
//初始化游戏类型
type : Phaser.AUTO,
width : 800,
height : 600,
//物理引擎
physics : {
default : ‘arcade‘,
arcade : {
//重力设置
gravity : {y : 300},
debug : false
}
},
//场景设置
scene : {
preload,
create,
update
}
}
//初始化游戏
let game = new Phaser.Game(config)
let score = 0
let scoreText
let gameover = false
//游戏主要函数
function preload()
{
this.load.image("sky","./img/sky.png")
this.load.image("star","./img/star.png")
this.load.image("ground","./img/platform.png")
this.load.image("bomb","./img/bomb.png")
this.load.spritesheet("dude","./img/dude.png",{frameWidth:32,frameHeight:48})
}
function create()
{
this.add.image(400,300,"sky")
platforms = this.physics.add.staticGroup()
platforms.create(400,568,"ground").setScale(2,2).refreshBody()
platforms.create(600,400,"ground")
platforms.create(0,300,"ground")
platforms.create(600,200,"ground")
player = this.physics.add.sprite(100,450,‘dude‘)
player.setBounce(0.2)
player.setCollideWorldBounds(true)
this.physics.add.collider(player,platforms)
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
})
cursors = this.input.keyboard.createCursorKeys()
stars = this.physics.add.group({
key : ‘star‘,
repeat : 11,
setXY : {x: 20,y: 0,stepX:70}
})
stars.children.iterate(function(child){
//设置一下碰撞效果
child.setBounceY(Phaser.Math.FloatBetween(0.4,0.8))
})
this.physics.add.collider(stars,platforms)
this.physics.add.overlap(player,stars,collectStar,null,this)
bombs = this.physics.add.group()
scoreText = this.add.text(16,16,"score : 0",{fontSize: ‘32px‘,fill: "#000"})
this.physics.add.collider(bombs,platforms)
this.physics.add.collider(player,bombs,bombbbb,null,this)
}
function update()
{
if(cursors.left.isDown)
{
player.setVelocityX(-200)
player.anims.play("left",true)
}
else if(cursors.right.isDown)
{
player.setVelocityX(200)
player.anims.play("right",true)
}
else{
player.setVelocityX(0)
player.anims.play(‘turn‘)
}
if(cursors.up.isDown && player.body.touching.down){
player.setVelocityY(-400)
}
}
function collectStar(player,star)
{
//让star实体消失
star.disableBody(true,true)
score += 1000
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(true)
bomb.setCollideWorldBounds(true)
bomb.setVelocity(Phaser.Math.Between(-200,200),20)
bomb.allowGravity = false
}
}
function bombbbb()
{
this.physics.pause()
//涂色,我觉得绿绿的比较好看
player.setTint(0x00ff00)
player.anims.play("turn")
gameover = true
}
</script>
</body>
</html>