8

前言

phaser作为一款流行的游戏/动画框架,受到很多web开发者的青睐,最近笔者在逛意大利开发者:emanueleferonato论坛的时候发现了这款小游戏,所以就照着说明做了一下,在这里记录下来.

开发准备

nodejs+npm
http-server插件
phaser脚本
飞刀和靶子的图像

或者

git clone https://github.com/YexChen/canvas_game.git

这个项目里面有phaser的脚本和需要的图像文件

开始制作

搭建基本的phaser项目

创建一个基本的html文件,引入phaser文件

<!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.js"></script>
</head>
<body>
  <script>
  
  </script>
</body>
</html>

别忘了在当前目录下启动http-server,启动服务器.打开localhost:8080(或其他端口)来查看项目

启动服务器

那么现在,我们需要干什么呢?
我们现在需要在页面加载时加载一个游戏实例,代码如下:

let game,knifeGroup
let gameConfig = {
  rotateSpeed : 5,
  throwSpeed : 150,
  minAngle : 10
}
window.onload = function(){
    game = new Phaser.Game({
        type: Phaser.CANVAS,
        width: 600,
        height: 700,
        backgroundColor: 0xdddddd,
        scene: [playGame]
    })
}

Phaser.Game 是phaser游戏的构建函数,定义了实例的类型,宽高,背景颜色,场景等信息,大家可以console.log(Phaser)看一下定义.
playGame是接下来的场景.
gameConfig 是游戏的参数,方便修改

接下来我们定义一下我们的场景:

class playGame extends Phaser.Scene{
      constructor(){
        super("playGame")
      }

      preload(){

      }

      create(){

      }

      update(){

      }
    }

场景在游戏中相当于戏曲中的每一幕,通过Phaser.Scene.start来进行调用.

一个游戏对象对应多个场景

在Phaser游戏中,场景创建时会先运行preload函数,用来预加载图片模型.然后运行create函数,执行初始化代码,最后在每一步中调用update函数更新

预加载图片

preload(){
        this.load.image("target","image/target.png")
        this.load.image("knife","image/knife.png")
}

在preload中加入以上代码,把图片注册进来.

加载物体

create(){
    this.target = this.add.image(game.config.width/2,game.config.height/5 *2,"target").setScale(0.5,0.5)
    this.target.depth = 1
    this.knife = this.add.image(game.config.width/2,game.config.height/5*4,"knife").setScale(0.5,0.5)
    this.knifeGroup = this.add.group()
    console.log(this)
}

this.add.image通过提供的宽高和上一步中提供的url来生成Image类型的对象(和原生的不一样!),
对象的原型链上的setScale(x,y)函数可以调整图像的缩放.
knifeGroup 是空的group对象,用来存放之后的飞刀集合

让我们的图像动起来

修改update函数:

update(){
        this.target.angle += gameOptions.rotateSpeed
}

好的,至此我们的项目基础就结束了,接下来来做飞刀的逻辑吧

扔飞刀逻辑

我们首先需要监听用户的鼠标事件,可以使用Phaser内置的函数来实现,在created中加入:

    this.canThrow = true
    this.input.on("pointerdown",this.throwKnife,this)

throwKnife 是我们扔飞刀的处理函数,我们写在update后面:

throwKnife(){
    if(!this.canThrow){
      return
    }
    this.canThrow = false
    this.tweens.add({
      targets: [this.knife],
      y: this.target.y+this.knife.height/8 * 3,
      duration: gameOptions.throwSpeed,
      callbackScope: this,
      onComplete: function(tween){
        
      },
    })
  }

我们在用户按下鼠标左键时,检测是否可扔,如果可扔的话就让我们的飞刀做一个tweens动画,this.tweens是一个tweens管理器,官方文档比较残废,部分参数如下:

target : tweens动画目标
y : 目标的y坐标,
duration: 动画时间
callbackScope: 回调函数的this值
onComplete: 完成时的回调函数

飞刀可以飞啦

飞刀插上去以后,我们要判断这个飞刀是不是和其它飞刀的重合,笔者这里的判断方式是在每一个飞刀插到盘面上时把当前轮盘的角度保存下来,当下一次投掷的时候判断当前盘面旋转度和以往的旋转度距离是否小于最小值,如果小于最小值就游戏结束,否则就插一次飞刀.
灵魂画师
我们在上面的onComplete函数里面写下代码:

let isLegal = true
let children = this.knifeGroup.getChildren()
for(var i=0;i<children.length;i++){
  let child = children[i]
  if(Math.abs(Phaser.Math.Angle.ShortestBetween(this.target.angle,child.impactAngle))<gameOptions.minAngle){
    isLegal = false
    break
  }
}
if(isLegal){
  this.canThrow =  true
  let newKnife = this.add.image(this.target.x,this.target.y+this.knife.height/8 * 3,"knife").setScale(0.5,0.5)
  newKnife.impactAngle = this.target.angle
  this.knifeGroup.add(newKnife)
  this.knife.y = game.config.height/5 * 4
}
else{
  this.tweens.add({
    targets: [this.knife],
    y: game.config.height+this.knife.height,
    rotation: 5,
    duration: gameOptions.throwSpeed*4,
    callbackScope: this,
    onComplete(tween){
      this.scene.start("playGame")
    }
  })
}

我们判断之前所有飞刀的impactAngle值,如果没有和当前角度相差10度的,我们就插入新的飞刀,否则播放动画以后重启游戏.

更新每一把飞刀的位置

好的,我们扔飞刀的逻辑已经完成了,接下来我们只需要遍历每一个knifeGroup里面的子飞刀的位置,并在update更新函数里面更新他们的位置,游戏就算做完了.
在update函数里面添加:

update(){
    this.target.angle += gameOptions.rotateSpeed
    let children = this.knifeGroup.getChildren()
    for(var i=0;i< children.length;i++){
      let child = children[i]
      child.angle += gameOptions.rotateSpeed
      let ang = Phaser.Math.DegToRad(child.angle)
      child.x = this.target.x - Math.sin(ang) * this.target.width/4
      child.y = this.target.y + Math.cos(ang) * this.target.width/4
    }
  }

我们在飞刀移动时计算每一步偏移的角度,从而判断出子飞刀child的x,y位移.
图片描述

我们的小游戏就做完了,完整代码如下,顺便做了下记分牌:

<!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.js"></script>
</head>
<body>
  <script>
    let game,knifeGroup,scoreText,score
    let gameOptions = {
      rotateSpeed : 5,
      throwSpeed : 150,
      minAngle : 10
    }
    window.onload = function(){
      game = new Phaser.Game({
        type: Phaser.CANVAS,
        width: 600,
        height: 700,
        backgroundColor: 0xdddddd,
        scene: [playGame]
      })
    }
    class playGame extends Phaser.Scene{
      constructor(){
        super("playGame")
      }

      preload(){
        this.load.image("target","image/target.png")
        this.load.image("knife","image/knife.png")
      }

      create(){
        this.target = this.add.image(game.config.width/2,game.config.height/5 *2,"target").setScale(0.5,0.5)
        this.target.depth = 1
        this.knife = this.add.image(game.config.width/2,game.config.height/5*4,"knife").setScale(0.5,0.5)
        this.knifeGroup = this.add.group()
        console.log(this)
        this.canThrow = true
        this.input.on("pointerdown",this.throwKnife,this)
        score = 0
        scoreText = this.add.text(16,16,"score:0",{fontSize:"32px",fill:"#000"})
      }

      update(){
        this.target.angle += gameOptions.rotateSpeed
        let children = this.knifeGroup.getChildren()
        for(var i=0;i< children.length;i++){
          let child = children[i]
          child.angle += gameOptions.rotateSpeed
          let ang = Phaser.Math.DegToRad(child.angle)
          child.x = this.target.x - Math.sin(ang) * this.target.width/4
          child.y = this.target.y + Math.cos(ang) * this.target.width/4
        }
      }

      throwKnife(){
        if(!this.canThrow){
          return
        }
        this.canThrow = false
        this.tweens.add({
          targets: [this.knife],
          y: this.target.y+this.knife.height/8 * 3,
          duration: gameOptions.throwSpeed,
          callbackScope: this,
          onComplete: function(tween){
            let isLegal = true
            let children = this.knifeGroup.getChildren()
            for(var i=0;i<children.length;i++){
              let child = children[i]
              if(Math.abs(Phaser.Math.Angle.ShortestBetween(this.target.angle,child.impactAngle))<gameOptions.minAngle){
                isLegal = false
                break
              }
            }
            if(isLegal){
              this.canThrow =  true
              let newKnife = this.add.image(this.target.x,this.target.y+this.knife.height/8 * 3,"knife").setScale(0.5,0.5)
              newKnife.impactAngle = this.target.angle
              this.knifeGroup.add(newKnife)
              this.knife.y = game.config.height/5 * 4
              score += 100
              scoreText.setText("score:" +score)
            }
            else{
              this.tweens.add({
                targets: [this.knife],
                y: game.config.height+this.knife.height,
                rotation: 5,
                duration: gameOptions.throwSpeed*4,
                callbackScope: this,
                onComplete(tween){
                  this.scene.start("playGame")
                }
              })
            }

          },
        })
      }
    }
  </script>
</body>
</html>

比那名居天子
499 声望28 粉丝