1. 程式人生 > >【phaser.js學習筆記(3)】開發H5遊戲“穿越小行星”並適配微信小遊戲

【phaser.js學習筆記(3)】開發H5遊戲“穿越小行星”並適配微信小遊戲

這篇筆記主要記錄使用phaser.js開發一個完整HTML5遊戲的整個過程,並將web端程式適配到微信小遊戲。  

1、遊戲基本架構

由於phaser社群目前僅有phaser2對微信小程式的支援,因此我選擇phaser v2.6.2作為遊戲的引擎。為便於開發除錯,以單獨的phaser.min.js方式引入檔案。遊戲主要分三個場景,開始場景,遊戲場景和重新開始場景,index.html檔案如下。 

<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Game</title
> <style> body { background: #000000; margin: 0; padding: 0; } canvas { display:block; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0; }  </style></head><body><script src="./js/phaser.min.js"
></script><script src="./js/start.js"></script><script src="./js/game.js"></script><script src="./js/restart.js"></script></body></html>

2、開始場景

開始場景需要星空背景、標題、開始按鍵和下方火焰,開發完成的效果如下。  


start.js為入口檔案,內容如下。 

let game;
// 全域性遊戲設定const gameOptions = { // 初始分數 scoreInit:
1000, // 本局分數 score: 0, // 螢幕寬高 width: 750, height: 1334, // 重力 gravity: 200, // 牆 rectWidth: 100, wallWidth: 5, // 地球 earthRadius: 100, // 飛船速度 speed: 600}
window.onload = () => { // 配置資訊 const config = { // 介面寬度,單位畫素 // width: 750, width: gameOptions.width, // 介面高度 height: gameOptions.height, // 渲染型別 renderer: Phaser.AUTO, parent: 'render' }; // 宣告遊戲物件 game = new Phaser.Game(config); // 新增狀態 game.state.add('start', Start);  game.state.add('game', Game); game.state.add('restart', Restart); // 開始介面 game.state.start('start');}
class Start extends Phaser.State { // 構造器 constructor() { super("Start"); }
// 預載入 preload() { // 圖片路徑 const images = { 'earth': './assets/img/earth.png', 'sat1': './assets/img/sat1.png', 'sat2': './assets/img/sat2.png', 'sat3': './assets/img/sat3.png',      'rocket': './assets/img/rocket.png',      'play': './assets/img/play.png',      'title': './assets/img/title.png',      'fire': './assets/img/fire.png',      'over': './assets/img/over.png',      'restart': './assets/img/restart.png',      'particle1': './assets/img/particulelune1.png', 'particle2': './assets/img/particulelune2.png', 'station': './assets/img/station.png' }; // 載入圖片    for (let name in images) {      this.load.image(name, images[name]); } // 載入天空盒 this.load.spritesheet('skybox', './assets/img/stars.png', 480, 640, 5); // 音樂路徑 const audios = {      'bgMusic':'./assets/audio/music.mp3',      'jump':'./assets/audio/jump.wav',      'explosion':'./assets/audio/explosion.mp3' } // 載入音樂    for(let name in audios){      this.load.audio(name, audios[name]);    } }
create() { // 播放背景音樂 const bgMusic = this.add.audio('bgMusic', 0.3, true); bgMusic.play(); // 螢幕比例係數 const screenWidthRatio = gameOptions.width / 480; const screenHeightRatio = gameOptions.height / 640;
// 星星閃爍 const skybox = game.add.sprite(0, 0, 'skybox'); skybox.width = gameOptions.width; skybox.height = gameOptions.height; const twinkle = skybox.animations.add('twinkle'); skybox.animations.play('twinkle', 3, true);
// 標題 const title = this.add.sprite(gameOptions.width / 2, gameOptions.height / 5, 'title'); title.width *= screenWidthRatio; title.height *= 0.8 * screenHeightRatio; title.anchor.set(0.5); this.add.tween(title).to( {y: gameOptions.height / 4}, 1500, Phaser.Easing.Sinusoidal.InOut, true, 0, -1, true);
// 開始按鈕 const startButton = this.add.group(); startButton.x = this.world.width / 2; startButton.y = gameOptions.height * 0.65; startButton.scale.set(0.7);
// 開始按鈕中加入地球、火箭 const earthGroup = this.add.group(); const earth = this.add.sprite(0, 0, 'earth'); earth.scale.set(screenHeightRatio * 1.7); earth.anchor.set(0.5); earthGroup.add(earth); const rocket = this.add.sprite(0, 0, 'rocket'); rocket.anchor.set(0.5, 1); rocket.scale.set(0.25 * screenHeightRatio); rocket.y = -140 * screenHeightRatio; earthGroup.add(rocket); this.add.tween(earthGroup).to( {rotation: Math.PI * 2}, 5000, Phaser.Easing.Linear.Default, true, 0, -1); // 整體加入到開始按鈕 startButton.add(earthGroup);
// 開始按鈕中加入播放鍵 const playButton = this.add.sprite(10, 0, 'play'); playButton.scale.set(0.7 * screenHeightRatio); playButton.anchor.set(0.5); startButton.add(playButton);
// startButton可點選,只能掛載到earth上 earth.inputEnabled = true; earth.events.onInputDown.add(function () { this.play(); }, this);
// 下方火焰 const fire = this.add.sprite(0, gameOptions.height * 0.98, 'fire'); fire.width = gameOptions.width; this.add.tween(fire).to( {y: gameOptions.height * 0.9}, 1000, Phaser.Easing.Sinusoidal.InOut, true, 0, -1, true); }
play() { this.state.start('game');  }}

window.onload中宣告遊戲物件game,傳入配置資訊。Start繼承場景狀態類Phaser.State,preload方法中完成圖片、音訊的載入,其中starts.png被橫向分為5份,依次變換,展現背景星空的閃爍。create方法將在場景被建立時呼叫。將sprite元素依次加入,sprite的疊放順序是加入順序的倒序,即加入越早越底層。通過tween(sprite名)可以新增動畫,Phaser.Easing.XX為動畫的變化曲線,可參考官方文件。當點選按鈕時,呼叫this.state.start('game')切換狀態名為‘game’的遊戲狀態。  

3、遊戲場景

遊戲的主要玩法是:玩家駕駛的火箭隨小行星轉動,點選螢幕完成跳躍。當檢測到火箭包圍盒與另一行星包圍盒重疊時,火箭登陸到另一行星並隨之轉動。下方火焰的速度將隨著分數的增長而不斷增長。當火焰吞沒火箭時,遊戲結束,記錄分數。  


game.js檔案包含場景狀態類Game,如下所示。

class Game extends Phaser.State { // 構造器 constructor() { super("Game"); }
create() { // 物理引擎 // 上下要對稱 this.world.setBounds(0, -1000000, 480, 1000000); this.physics.startSystem(Phaser.Physics.ARCADE);
// 初始化引數 this.score = gameOptions.scoreInit; this.gravity = gameOptions.gravity; this.screenWidthRatio = gameOptions.width / 480; this.screenHeightRatio = gameOptions.height / 640;
// 生成sprite // 星星閃爍 // const skybox = game.add.sprite(0, 0, 'skybox'); const skybox = this.add.sprite(0, 0, 'skybox'); skybox.width = gameOptions.width; skybox.height = gameOptions.height; const twinkle = skybox.animations.add('twinkle'); skybox.animations.play('twinkle', 3, true); skybox.fixedToCamera = true; // 生成左右牆體 this.walls = this.add.group();    for(let lr of ['left', 'right']) { let wall;      if (lr === 'left') {        wall = this.add.graphics(- gameOptions.rectWidth + gameOptions.wallWidth, 0);        wall.type = 'l';      } else {        wall = this.add.graphics(this.camera.view.width - gameOptions.wallWidth , 0);        wall.type = 'r';      }      wall.beginFill(0xFFFFFF);      wall.drawRect(0, 0, 100, this.camera.view.height);      wall.endFill();      this.physics.arcade.enable(wall);      wall.body.immovable = true;      wall.fixedToCamera = true;      this.walls.add(wall);    }
// 生成地球和小行星 this.asteroids = this.add.group(); const earthRadius = gameOptions.earthRadius * this.screenWidthRatio; // const earth = this.add.sprite(this.world.width / 2, this.world.height / 3 * 2, 'earth'); const earth = this.add.sprite(gameOptions.width / 2, -gameOptions.height * 0.22, 'sat2'); earth.scale.set(this.screenWidthRatio * 0.1); earth.anchor.setTo(0.5, 0.5); earth.radius = earthRadius; earth.width = earthRadius * 2; earth.height = earthRadius * 2;
// 生成火箭 // const rocket = this.add.sprite(this.world.width / 2, this.world.height / 3 * 2 - earthRadius, 'rocket'); const rocket = this.add.sprite(gameOptions.width / 2, -gameOptions.height / 3 * 2 - earthRadius, 'rocket'); rocket.anchor.set(0.5, 0.55); // 調節行星生成,避免出界    rocket.radius = 15; rocket.scale.set(0.25); this.physics.arcade.enable(rocket); // 著陸星球 rocket.landed = { asteroid: earth,      angle: - Math.PI / 2 }; this.rocket = rocket; this.camera.follow(this.rocket); // 生成行星 this.generateAsteroids();
// 生成火焰 const fire = this.add.sprite(0, -gameOptions.height / 10, 'fire'); fire.width = gameOptions.width; fire.height = gameOptions.height / 3 * 2; this.physics.arcade.enable(fire); fire.body.immovable = true; this.fire = fire;
// 灰塵特效 const dust = this.add.emitter();    dust.makeParticles(['particle1', 'particle2']);    dust.gravity = 200;    dust.setAlpha(1, 0, 3000, Phaser.Easing.Quintic.Out); this.dust = dust; // 分數,放到後面,越晚加入越在上層 const scoreText = this.add.text( gameOptions.width - 20, 10, '分數 ' + this.score, { font: this.screenWidthRatio * 30 + 'px Arial', fill: '#ffffff' } ); scoreText.anchor.x = 1; scoreText.fixedToCamera = true; this.scoreText = scoreText;
// 點選互動    this.input.onDown.add(() => {      this.jump(); }); // 載入音樂 this.jumpAudio = this.add.audio('jump', 0.3); this.explosionAudio = this.add.audio('explosion', 0.2); }
jump() {    if (this.rocket.landed) { this.rocket.body.moves = true; const speed = gameOptions.speed; this.rocket.body.velocity.x = speed * Math.cos( this.rocket.landed.angle + this.rocket.landed.asteroid.rotation); this.rocket.body.velocity.y = speed * Math.sin( this.rocket.landed.angle + this.rocket.landed.asteroid.rotation);
this.rocket.body.gravity.y = this.gravity; this.rocket.leftTime = Date.now(); this.rocket.landed = null; this.jumpAudio.play(); } else if (this.rocket.type) { // 觸牆 const speed = gameOptions.speed; const gravity = gameOptions.gravity; if (this.rocket.type === 'l') { this.rocket.body.velocity.x = speed; this.rocket.body.velocity.y = -0.2 * speed; // this.rocket.body.gravity.y = gravity; } else if (this.rocket.type === 'r') { this.rocket.body.velocity.x = -speed; this.rocket.body.velocity.y = -0.2 * speed; // this.rocket.body.gravity.y = gravity; } this.rocket.leftTime = Date.now(); this.rocket.type = false; this.jumpAudio.play(); }  }
generateAsteroids() { // 生成小行星帶 // 生成資料 const getRatio = (min, max) => { return Math.min(this.score / 10000, 1) * (max - min) + min; } const getValue = () => { return { distance: this.screenHeightRatio * this.rnd.between(getRatio(50, 150), getRatio(100, 200)), angle: this.rnd.realInRange(-Math.PI * 0.15, -Math.PI * 0.85), radius: this.screenHeightRatio * this.rnd.between(getRatio(60, 20), getRatio(90, 40)), rotationSpeed: this.rnd.sign() * this.rnd.between(getRatio(1, 3), getRatio(3, 6)) }; }
// 生成第一顆小行星 if(this.asteroids.children.length === 0) { const values = getValue(); this.asteroids.add(this.generateOneAsteroid( this.world.width / 2, - gameOptions.height * 0.4 - 2 * values.radius, values.radius, values.rotationSpeed )); } // console.log(this.asteroids.children[0].angle)
// 生成其他小行星 const maxDistance = this.camera.view.height;    while(this.asteroids.children[this.asteroids.children.length - 1].y >= this.rocket.y - maxDistance){ const previousAsteroid = this.asteroids.children[this.asteroids.children.length - 1]; let newOne; let values;      do{ values = getValue();        newOne = {          x: previousAsteroid.x + Math.cos(values.angle) * (values.distance + previousAsteroid.radius + values.radius),          y: previousAsteroid.y + Math.sin(values.angle) * (values.distance + previousAsteroid.radius + values.radius) } } while(newOne.x - this.rocket.radius * 2 - values.radius < 10 || newOne.x + this.rocket.radius * 2 + values.radius > this.world.width);      this.asteroids.add(this.generateOneAsteroid(newOne.x, newOne.y, values.radius, values.rotationSpeed)); } }
generateOneAsteroid(x, y, radius, rotationSpeed) { const rnd = Math.random(); let oneAsteroid; // 設定生成不同小行星的概率 if (rnd < 1 / 4) { oneAsteroid = this.add.sprite(this.screenWidthRatio * x, y, 'sat1'); } else if (rnd < 1 / 2) { oneAsteroid = this.add.sprite(this.screenWidthRatio * x, y, 'sat2'); } else { oneAsteroid = this.add.sprite(this.screenWidthRatio * x, y, 'sat3'); } oneAsteroid.anchor.setTo(0.5, 0.5); oneAsteroid.radius = radius;    oneAsteroid.width = radius * 2;    oneAsteroid.height = radius * 2; this.physics.arcade.enable(oneAsteroid); oneAsteroid.body