基于HTML5/Javascrip的游戏开发框架Phaser
Phaser是一个开源的HTML5游戏框架,也就是传说中100行代码之内搞定Flappy Bird的神器,通过这个框架我们可以很容易地开发桌面和移动的小游戏。目前Phaser的最新版本是2.0.7,它同时支持WebGL和Canvas。像其它游戏框架一样,Phaser封装了很多游戏开发的特性。在这篇文章中我们将会通过Phaser创建一个简单的游戏。为了专注于对框架学习,在下文中我们不会用到Phaser的任何对象,如精灵和组。
搭建Phaser
Phaser是一个通过Yeoman构建的开源的框架,我们可以在Github上下载到最新的Phaser,下载之后通过以下命令搭建:
1. npm install -g generator-phaser-official
2. yo phaser-official
Phaser框架的文件结构
一个Phaser工程将会有如下的文件结构:
|-- app.js
|-- main.js
|-- prefabs
`-- states
|-- boot.js
|-- level_intro.js
|-- level_master.js
|-- level_round.js
|-- main_intro.js
|-- main_menu.js
`-- preload.js
Main.js作为程序入口,开始游戏。app.js用来定义一个Phaser应用,prefabs文件夹存放游戏对象,states文件夹用来保存游戏状态。
如果你想要在这些文件中引入纯脚本,我推荐你用RequireJS或是Browserify。
Phaser.state
Phaser state将会封装游戏中不同的状态,比如游戏的加载、主菜单、级别、帮助、暂停等等……当一个state开始的时候,你可以创建与这个state相关的对象,同时你也可以在不同的state之间切换,Phaser将会为你清理过时的游戏对象,你可以在新的state中继续创建游戏对象并显示它们。
你可以通过实现预设方法来创建状态,下面我举一些重要的例子:
- init – 当state开始的时候执行的方法,它传递一个参数使state之间共享数据。
- preload – 当state开始的时候执行的方法,它的作用是在游戏开始前加载所有的资源文件。
- create – preload方法执行后开始执行的方法,用来创建游戏对象。
- shutdown – 当一个state结束的时候执行的方法,用来清理过时的游戏对象。
State执行流程
下图是state的执行流程。Boot和Preload状态用来配置和加载游戏资源,MainMenu状态用来显示主菜单。其他的level状态是为了解决游戏过程中不同的级别和关数,每一个级别可以分成很多关,通过后进入下一关。
Game State
Boot state在preload的hook方法中加载预加载资源,并构建游戏的配置,如窗口缩放和输入内容。
File: states/boot.js
1. function Boot() {};
2.
3. Boot.prototype = {
4. preload: function() {
5. // load preloader assets
6. },
7. create: function() {
8. // setup game environment
9. // scale, input etc..
10.
11. this.game.state.start('preload');
12. }
13. };
Preload state加载所有的游戏资源,然后切换到main-intro状态。
File: states/preload.js
1. Preload.prototype = {
2. preload: function() {
3. // load all game assets
4. // images, spritesheets, atlases, audio etc..
5. },
6. create: function() {
7. this.game.state.start('main-intro');
8. }
9. };
MainIntro state 用来显示游戏的介绍、logo、用户信息等等……它不需要preload方法,执行完之后就会转换到main-menu状态。
File: states/main_intro.js
1. function MainIntroState() {};
2.
3. MainIntroState.prototype = {
4. create: function() {
5. // add main intro assets into the world
6. this.tweenFadeState();
7. },
8.
9. tweenFadeState: function() {
10. this.game.add.tween({})
11. .to({alpha: 1}, 2000)
12. .onComplete.add(function() {
13. this.game.state.start('main-menu');
14. }, this);
15. }
16. };
MainMenu state显示游戏的主界面,用户可以通过主菜单和程序进行交互。为了简单的演示,我在以下代码中添加了一个键盘触发事件,事件触发后转换到level-master状态。
File: states/main_menu.js
1. MainMenuState.prototype = {
2. create: function() {
3. this.enterKey = this.game.input.keyboard
4. .addKey(Phaser.Keyboard.ENTER);
5.
6. this.enterKey.onDown.add(this.tweenPlayState, this);
7. },
8. tweenPlayState: function() {
9. var tweenMenuShrink = this.game.add.tween({})
10. .to({x: 0, y: 0}, 200);
11.
12. var tweenFadeIn = this.game.add.tween({})
13. .to({alpha: 1}, 2000);
14.
15. tweenFadeIn.onComplete.add(function() {
16. this.game.state.start('level-master');
17. }, this);
18.
19. tweenMenuShrink.chain(tweenFadeIn);
20. tweenMenuShrink.start();
21. }
22. };
这里为了演示简化了tween操作,在正常的开发中,你应该在create方法中创建游戏对象,具体的创建方法可以参见Phaser的Demo和文档。
LevelMaster state在游戏界面上不显示任何的内容,它要做的只是决定选择转换到level-round state还是leverl-intro state,同时,它也会在不同的state之间更新和传递游戏数据。
File: state/level_master.js
1. LevelMasterState.prototype = {
2. init: function(levelData) {
3. if (!levelData) {
4. levelData = {
5. level: 0,
6. round: 1,
7. players: [
8. { score: 0, skill: 1 },
9. { score: 0, skill: 1 }
10. ]
11. };
12. }
13.
14. this.levelData = levelData;
15. this.winScore = 2;
16. },
17.
18. create: function() {
19. this.decideLevelState();
20. }
21. };
当一个新的level开始,level-intro state就开始了,level-intro state会显示对这个新level的介绍,比如当前是哪种级别,在level-intro之后,会转换到level-round,也就是游戏开始的地方。
当一个round结束之后,要么是新的level,要么就是新的round。这个逻辑在decideLevelState方法中实现,如果我们通过了一个level,游戏就会进入下一个level,当已经进入最高的level之后,会切换到下一个round。
this.levelData保存游戏的数据,像当前的level、round和游戏分数。我们在这些值发生逻辑变化和切换state的时候更新它们。
File: states/level_master.js
1. LevelMasterState.prototype = {
2. decideLevelState: function() {
3. if (this.isFirstLevel() || this.getWinningPlayer() !== -1) {
4. this.nextLevel();
5. } else {
6. this.nextRound();
7. }
8. },
9. nextLevel: function() {
10. this.levelData.level++;
11.
12. this.levelData.players.forEach(function(p) {
13. p.score = 0;
14. }, this);
15.
16. this.levelData.round = 1;
17.
18. this.game.state.start('level-intro', true, false, this.levelData);
19. },
20. nextRound: function() {
21. this.levelData.round++;
22. this.game.state.start('level-round', true, false, this.levelData);
23. }
24. };
LevelIntro state显示了level的介绍信息,如你正在哪个level中。我们可以通过传递levelDate参数来保存游戏数据。在create方法中,如果用户进入第一个level,我们可以通过一个技能菜单来获取levelData,技能菜单指的是用户选择的的难度级别,这个值由你来设定。结束的时候会切换到level-round state。
File: states/level_intro.js
1. LevelIntroState.prototype = {
2. init: function(levelData) {
3. this.levelData = levelData;
4. },
5. create: function() {
6. var tweenIntro = this.tweenIntro();
7.
8. if (this.levelData.level === 1) {
9. var tweenSkillMenuPop = this.tweenSkillMenuPop();
10.
11. tweenIntro.chain(tweenSkillMenuPop);
12. tweenSkillMenuPop.onComplete.add(this.levelStart, this);
13. } else {
14. tweenIntro.onComplete.add(this.levelStart, this);
15. }
16. },
17. levelStart: function() {
18. this.game.state.start('level-round', true, false, this.levelData);
19. },
20. tweenIntro: function() {
21. var tween = this.game.add.tween({})
22. .to({alpha: 0}, 1000, Phaser.Easing.Linear.None, true);
23.
24. return tween;
25. },
26. tweenSkillMenuPop: function() {
27. var tween = this.game.add.tween({})
28. .to({x: 1, y: 1}, 500, Phaser.Easing.Linear.None, true);
29.
30. return tween;
31. }
32. };
最后LevelRound state是游戏当前所发生的位置,你可以通过update方法进行更新。为了简化演示,我在state结束的时候添加了一个点击回车键的交互事件,它会返回到lever-master,在level-master的初始位置得到传递的levelData数据。
File: states/level_round.js
1. LevelRoundState.prototype = {
2. init: function(levelData) {
3. this.levelData = levelData;
4. },
5. create: function() {
6. this.enterKey = this.game.input.keyboard
7. .addKey(Phaser.Keyboard.ENTER);
8.
9. this.enterKey.onDown.add(this.roundEnd, this);
10. },
11. roundEnd: function() {
12. this.nextRound();
13. },
14. nextRound: function() {
15. this.game.state.start('level-master', true, false, this.levelData);
16. }
17. };
我们已经完成了整个state的流程环,它们看起来像下面这样:
1. Boot -> Preload ->
2. main-intro -> main-menu ->
3. level-master -> Level1 ->
4. level-master -> L1 Round1 ->
5. level-master -> L1 Round2 ->
6. level-master -> Level2 ->
7. level-master -> L2 Round1 ->
你可以通过在level-round state中设置一个事件转换到main-menu state来结束游戏的流程。
开始游戏
现在我们将会运行这个Phaser游戏,将这个div放到你的页面中,Phaser将会把它的canvas放在这里。
File: index.html
1. <div id="game-area"></div>
我们需要创建一个Phaser.Game,把我们所有的state加入到StateManager中并开始Boot state。
File: app.js
1. function Game() {}
2.
3. Game.prototype = {
4. start: function() {
5. var game = new Phaser.Game(640, 480, Phaser.AUTO, 'game-area');
6.
7. game.state.add('boot', BootState);
8. game.state.add('preload', PreloadState);
9. game.state.add('main-intro', MainIntroState);
10. game.state.add('main-menu', MainMenuState);
11. game.state.add('level-master', LevelMasterState);
12. game.state.add('level-intro', MainIntroState);
13. game.state.add('level-round', LevelRoundState);
14. game.state.start('boot');
15. }
16. };
最后,都过以下代码触发这个游戏:
File: main.js
1. var game = new Game();
2. game.start();
总结
这只是利用Phaser框架开发了一个简单的游戏架子,你可以在文章上方点击下载这个Demo。同时,Phaser官方为你的开发游戏提供了很多资源,像精灵、动画、声音、物理、缩放等等,你可以访问Phaser的官网进行深入的学习。