青瓷引擎

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

近期出现一款魔性的消除类HTML5游戏《神奇的六边形》,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏。

(点击图片可进入游戏体验)

因内容太多,为方便大家阅读,所以分成部分来讲解。

本文为第四部分,主要包括:

16.分数往上飘动画

17.形状飞入动画

18.其他动画表现添加

19.游戏结束界面

20. 添加LOGO

21. 渲染优化

若要一次性查看所有文档,也可点击这里

十六. 分数往上飘的动画

在表现加分时,分数会有个缩放的效果,然后往上移动并淡出。这些效果可以通过Tween Group来组合实现。

1. 在board节点下,创建UIText节点,取名为score,属性设置如下:

  • 文本居中显示
  • 文本颜色为白色,大小40,外加6像素描边

2. 为score节点,添加TweenScale组件,控制缩放动画,属性设置如下图(注意设置Tween Group=1):

3. 为score节点,添加TweenPosition组件,控制节点向上移动,属性设置如下图(注意设置Tween Group=1):

4. 为score节点,添加TweenAlpha组件,控制节点淡出,属性设置如下图(注意设置Tween Group=1):

5. 将score节点拖拽到Assets/prefab目录,创建预制。然后从场景中删除。

6. 在Scripts/ui下新建脚本:FlyScore.js

 1 /**
 2   * 分数飘出来的特效
 3   */
 4  var FlyScore = qc.defineBehaviour('qc.tetris.FlyScore', qc.Behaviour, function() {
 5      var self = this;
 6  }, {
 7      scorePrefab: qc.Serializer.PREFAB
 8  });
 9 
10  /**
11   * 开始播放冒分数动画
12   */
13  FlyScore.prototype.play = function(pos, score) {
14      var self = this;
15 
16      var scoreOb = self.game.add.clone(self.scorePrefab, self.gameObject);
17      scoreOb.text = '' + score;
18      var tp = scoreOb.getScript('qc.TweenPosition');
19      scoreOb.anchoredX = qc.Tetris.board.data[pos].x;
20      scoreOb.anchoredY = qc.Tetris.board.data[pos].y;
21      tp.from = new qc.Point(scoreOb.x, scoreOb.y);
22      tp.to = new qc.Point(tp.from.x, tp.from.y - 80);
23      tp.resetGroupToBeginning();
24      tp.playGroupForward();
25      self.game.timer.add(600, function() {
26          scoreOb.destroy();
27      });
28  };

 

7. 将FlyScore脚本挂载到board节点,设置scorePrefab属性为Assets/prefab/score.bin。保存场景。

 

十七. 形状飞入动画

1. 双击Assets/prefab/Blocks.bin,编辑预制

2. 选中Blocks节点,添加TweenPosition组件,属性设置如下:

3. 保存预置

4. 打开Scripts/ui/Pool.js,添加flyIn接口,处理飞入动画的播放

 1 /**
 2   * 播放飞入的动画
 3   */
 4  Pool.prototype.flyIn = function(index) {
 5      var self = this, o = self.gameObject, children = o.children;
 6      var offset = o.width * (0.5 - 0.165);
 7 
 8      // 先确保位置都正确
 9      self.resize();
10 
11      if (index === 0) {
12          var o = children[0], c = o.getScript('qc.tetris.BlocksUI');
13          c.flyIn(offset);
14      }
15      if (index === 0 || index === 1) {
16          var o = children[1], c = o.getScript('qc.tetris.BlocksUI');
17          c.flyIn(offset);
18      }
19 
20      var o = children[2], c = o.getScript('qc.tetris.BlocksUI');
21      c.flyIn(offset);
22  };

 

5. 打开Scripts/ui/BlocksUI.js,添加flyIn接口,处理单个形状飞入动画

 1  /**
 2   * 飞入动画
 3   */
 4  BlocksUI.prototype.flyIn = function(offset) {
 5      var self = this,
 6          tp = self.getScript('qc.TweenPosition');
 7 
 8      tp.delay = 0.5;
 9      tp.to = new qc.Point(self.gameObject.x, self.gameObject.y);
10      tp.from = new qc.Point(tp.to.x + offset, tp.to.y);
11      tp.resetToBeginning();
12      tp.playForward();
13  };

 

6. 运行游戏,查看下效果:已经可以正常游戏了不是?

 

十八. 其他动画表现添加

目前还缺少两个表现:加分动画(数字跳动)、形状回弹效果。其方法和之前讲述的大致一样,这里简略做个说明。

加分动画

1. 修改CurrentScore.js代码,添加动画播放代码:

 1 var CurrentScore = qc.defineBehaviour('qc.tetris.CurrentScore', qc.Behaviour, function() {
 2      var self = this;
 3      self.runInEditor = true;
 4  }, {
 5  });
 6 
 7  /**
 8   * 初始化处理
 9   */
10  CurrentScore.prototype.awake = function() {
11      var self = this, div = self.gameObject.div;
12 
13      div.className = 'score_current';
14      self.setScore(qc.Tetris.score.current);
15  };
16 
17  /**
18   * 更新最新的高分
19   */
20  CurrentScore.prototype.setScore = function(best) {
21      best = best || qc.Tetris.score.current;
22 
23      // 做动画表现,从old -> best
24      var old = this.gameObject.div.innerHTML * 1;
25      this.delta = best - old;
26      if (this.delta <= 0)
27          this.gameObject.div.innerHTML = '' + best;
28 
29      // 0.2s内需要播放完毕,计算每秒增加的数量
30      this.step = this.delta / 0.2;
31 
32      // 播放缩放动画
33      var ts = this.getScript('qc.TweenScale');
34      ts.resetToBeginning();
35      ts.playForward();
36  };
37 
38  /**
39   * 动画表现
40   */
41  CurrentScore.prototype.update = function() {
42      if (this.delta <= 0) {
43          // 动画表现完毕了
44          return;
45      }
46 
47      var step = Math.round(this.step * this.game.time.deltaTime / 1000);
48      this.delta -= step;
49      var old = this.gameObject.div.innerHTML * 1 + step;
50      if (old > qc.Tetris.score.current) {
51          old = qc.Tetris.score.current;
52          this.delta = 0;
53      }
54      this.gameObject.div.innerHTML = '' + old;
55  };

 

2. 为场景节点UIRoot/score添加TweenScale组件,属性设置如下:

形状回弹效果

修改BlocksUI.js代码,添加backAni方法:

 1 /**
 2      * 退回到原来位置的动画表现
 3      */
 4     BlocksUI.prototype.backAni = function(x, y) {
 5         var self = this, o = self.gameObject,
 6             tp = self.getScript('qc.TweenPosition');
 7         if (tp.enable) return;
 8         tp.delay = 0;
 9         tp.from = new qc.Point(x, y);
10         tp.to = new qc.Point(self.gameObject.x, self.gameObject.y);
11         tp.stop();
12         tp.resetToBeginning();
13         self.gameObject.interactive = false;
14         tp.onFinished.addOnce(function() {
15             self.gameObject.interactive = true;
16             o.parent.getScript('qc.tetris.Pool').resize();
17         });
18         tp.playForward();
19     };

 在onDragEnd方法中,当无法放入棋盘的分支中,加入backAni的调用:

 1 BlocksUI.prototype.onDragEnd = function(e) {
 2         var self = this,
 3             o = self.gameObject;
 4         self.drag = false;
 5 
 6         if (self.flagBlocks.visible && self.lastPos) {
 7             // 放到这个位置中去
 8             self.drop = true;
 9             qc.Tetris.operation.putIn(self.index, self.lastPos, self.data);
10         }
11         else {
12             // !!!!!!
13             // 在这个分支中修改为如下代码
14             var x = o.x, y = o.y;
15             self.reset();
16             o.parent.getScript('qc.tetris.Pool').resize();
17             self.backAni(x, y);
18         }
19 
20         // 显示标记可以干掉了
21         self.flagBlocks.destroy();
22         delete self.flagBlocks;
23     };

 

   

 

十九. 游戏结束界面

游戏界面包含2个页面:

 

这两个页面使用html+css元素快速搭建(DOM节点)。步骤如下:

1. 在UIRoot下创建Dom节点,取名GameOver

       

  • 居中显示,大小为340*441
  • 缩放1.5倍
  • 设置节点可以交互(碰撞范围非常大,这样底部游戏所有的元素都无法接收事件了)
  • 设置className=gameover

2. 在Scripts/ui下新建脚本:GameOverUI.js

 1 /**
 2   * 游戏结束界面
 3   */
 4  var GameOverUI = qc.defineBehaviour('qc.tetris.GameOverUI', qc.Behaviour, function() {
 5      var self = this;
 6      qc.Tetris.gameOver = self;
 7      self.runInEditor = true;
 8  }, {
 9      shareClue: qc.Serializer.PREFAB
10  });
11 
12  GameOverUI.prototype.awake = function() {
13      var div = this.gameObject.div;
14      var score = qc.Tetris.score.current;
15      var percent = 40;
16 
17      this.rawHtml =
18          '<div class="gameover_title">Game Over</div>' +
19          '<div class="gameover_score">__SCORE__</div>' +
20          '<div class="gameover_pos">你击败了全球__PERCENT__%的玩家</div>' +
21          '<div class="gameover_desc">让朋友们来膜拜大神吧!</div>' +
22          '<div class="gameover_share" onclick="qc.Tetris.gameOver.share()" ontouchstart="qc.Tetris.gameOver.share()">马上告诉他们</div>' +
23          '<div class="gameover_restart" onclick="qc.Tetris.gameOver.restart()" ontouchstart="qc.Tetris.gameOver.restart()">再玩一次</div>' +
24          '<div class="gameover_act">' +
25          '    <div class="gameover_logo"></div><div class="gameover_act_desc">点击关注送好礼</div> ' +
26          '</div>' +
27          '<div class="clear"></div>';
28      this.rawHtml = this.rawHtml.replace('__SCORE__', '' + score);
29      this.rawHtml = this.rawHtml.replace('__PERCENT__', '' + percent);
30      div.innerHTML = this.rawHtml;
31  };
32 
33  GameOverUI.prototype.onDestroy = function() {
34      delete qc.Tetris.gameOver;
35  };
36 
37  GameOverUI.prototype.share = function() {
38      // 打开share界面
39      this.game.add.clone(this.shareClue, this.gameObject.parent);
40  };
41 
42  GameOverUI.prototype.restart = function() {
43      this.gameObject.destroy();
44      qc.Tetris.operation.restart();
45  };

本界面主要通过内置的DOM来进行处理,具体不多作解释(您需要有一定的web前端开发基础)

1. 打开Assets/css/style.css,添加如下样式表:

 1 /* Game Over */
 2  .gameover {
 3      text-align: center;
 4      width: 100%;
 5      font-family: arial, sans serif;
 6      background: url("../raw/bg.png") no-repeat;
 7      color: #000000;
 8  }
 9  .gameover_title {
10      font-size: 40px;
11      margin-top: 10px;
12      height: 50px;
13      text-align: center;
14  }
15  .gameover_score {
16      font-size: 90px;
17      margin-top: -15px;
18      height: 98px;
19      text-align: center;
20  }
21  .gameover_pos {
22      text-align: center; font-size: 28px;
23      height: 40px;
24  }
25  .gameover_desc {
26      text-align: center; color: #ffffff;
27      height: 30px; font-size: 20px; line-height: 100%;
28  }
29  .gameover_share {
30      background: url("../raw/btn_blue.png") center no-repeat;
31      height: 76px;
32      line-height: 76px;
33      font-size: 30px;
34      color: #ffffff;
35      text-align: center;
36  }
37  .gameover_restart {
38      background: url("../raw/btn_yellow.png") center no-repeat;
39      text-align: center; color: #ffffff;
40      height: 76px;
41      line-height: 76px;
42      font-size: 30px;
43      margin-top: 10px;
44  }
45 
46  .gameover_logo {
47      float: left;
48      background: url("../raw/logo.png") no-repeat;
49      width: 64px;
50      height: 62px;
51      margin: 8px 0px 0px 2px;
52  }
53  .gameover_act_desc {
54      color: #ffffff;
55      float: right;
56      width: 250px;
57      text-align: left;
58      height: 62px;
59      line-height: 62px;
60      margin-top: 8px;
61      font-size: 28px;
62  }
63  .clear { clear: both; }

 

 

2. GameOverUI.js脚本挂载到GameOver对象,刷新查看下效果。

3. 将GameOver节点拖拽到Assets/prefab下,创建预制。然后从场景中删除。游戏结束界面就完成了。下面构建分享页面

4. 在UIRoot下新建Dom节点:shareClue,参数设置如下:

 

大小设置为铺满整个屏幕,className=share

5.在shareClue新建Dom节点:arraw,用来显示箭头,定位为右上角,参数设置如下:

 

6. 在shareClue新建Dom节点:desc,用来显示提示语内容。参数设置如下:

7. 在Scripts/ui新建文件ShareClue.js

 1 /**
 2   * 分享提示页面
 3   */
 4  var ShareClue = qc.defineBehaviour('qc.tetris.ShareClue', qc.Behaviour, function() {
 5      var self = this;
 6      self.runInEditor = true;
 7  }, {
 8      descNode: qc.Serializer.NODE
 9  });
10 
11  /**
12   * 初始化
13   */
14  ShareClue.prototype.awake = function() {
15      this.descNode.div.innerHTML = '请点击右上角<br/>分享给您的好友吧<br/>看下他们能取得多少分';
16  };
17 
18  /**
19   * 点击时干掉本页面
20   */
21  ShareClue.prototype.onClick = function() {
22      this.gameObject.destroy();
23  };

 

8. 将ShareClue脚本挂载到shareClue节点上,设置Desc Node为下面的desc节点

9. 编辑Assets/css/style.css,添加样式表:

 

 

 1 /* 分享提示 */
 2 .share {
 3     background-color: #000000;
 4     opacity:0.5;
 5     filter:alpha(opacity=50);
 6     text-align: center; color: #ffffff;
 7 }
 8 .share_clue {
 9     background-image: url("../raw/arrows.png");
10 }
11 .share_desc {
12     color: #ffffff; font-size: 60px; text-align: center;
13     line-height: 70px;
14 }

 

10. 保存场景并刷新之,查看效果。

11. 将shareClue拖拽到Assets/prefab,创建预置,然后从场景中删除。

12. 选中UIRoot节点,设置UIManager组件的gameOverPrefab=Assets/prefab/GameOver.bin

13. 双击Assets/prefab/GameOver.bin编辑预置,设置shareClue = Assets/prefab/shareClue.bin

 

14. 打开Scripts/logic/Board.js,补齐死亡逻辑:

 1 Object.defineProperties(Board.prototype, {
 2     /**
 3      * @property {boolean} die - 当前是否已经死亡了
 4      * @readonly
 5      */ 
 6     die: {
 7         get: function() {
 8             // 如果有单个点形状的,一定死不了
 9             var pool = qc.Tetris.Shapes.pool;
10             for (var i = 0; i < pool.length; i++) {
11                 if (pool[i].list.length === 1) return false;
12             }
13 
14             // 逐一检查,各形状能否扔来进来
15             for (var pos in this.data) {
16                 for (var i = 0; i < pool.length; i++) {
17                     if (this.checkPutIn(pos, pool[i].list)) return false;
18                 }
19             }
20             return true;
21         }
22     }
23 });

 

15. 在Scripts/operation创建脚本Restart.js,处理重新开始游戏的逻辑:

 1 /**
 2  * 请求重新开始游戏
 3  */
 4 qc.Tetris.operation.restart = function() {
 5     var game = qc.Tetris.game,
 6         ui = game.ui;
 7 
 8     // 清空棋盘信息
 9     qc.Tetris.board.restart();
10 
11     // 当前分数清0
12     qc.Tetris.score.current = 0;
13 
14     // 3个形状重新替换掉
15     qc.Tetris.Shapes.restart();
16 
17     // 界面初始化
18     ui.restart();
19 };

 

16. 游戏的整体逻辑已经完成,测试看看

 

二十. 添加Logo

具体请自行参考工程理解,涉及的场景节点有:UIRoot/logo和UIRoot/company
涉及的脚本有:Scripts/ui/Company.js
涉及的样式表:

1    /* logo */
2     .logo {
3         background-image: url("../raw/qici.png");
4     }
5     .company {
6         color:#ffffff;font-size: 24px;
7     }

 

 

二十一. 渲染优化

本游戏,使用图片比较少,并且大部分都采用DOM的方式渲染,非常高效。
唯一可以做优化的是3个形状:每个形状下面挂载了多个格子节点,并且大部分时间是保持不变的,因此可以将这些格子节点缓存到canvas以提升渲染效率(多耗了点内存)。步骤如下:

1. 双击Assets/Prefab/Blocks.bin编辑预置,在预置根节点添加内置组件:CacheAsBitmap,设置属性如下图:

 

这个组件的用途是将节点渲染到独立的canvas上并缓存起来。

2. 保存预制。当内部的格子发生变化时,需要“设脏”(确保缓存能被刷新)。打开BlocksUI.js,在reset方法最后面加入:

 

 1 self.getScript('qc.CacheAsBitmap').dirty = true; 

 

添加帧率查看

在UIRoot挂载Dom节点:debug,属性设置如下:

2. 编辑style.css,添加样式表:

 1 .debug { color:green; font-size:18px; } 

3. 为debug挂载内置脚本:DebugViewer,刷新下页面,就可以查看帧率情况了:

其中,FPS为游戏实际运行帧率;Total为游戏一次主循环所使用的时间;Logic为游戏逻辑损耗的时间(即Preupdate、Update等),Render为渲染时间。

4. 在最终发布时,需要将debug隐藏掉

 

    《神奇的六边形》 到此就分享完毕了,感谢各位的耐心阅读,若发现问题,还请大家及时指出,帮助我们不断完善。以后还将陆续分享其他好游戏的开发经验,望大家继续关注,谢谢!

 

上一篇:JS开发HTML5游戏《神奇的六边形》(三) 

 

 

 

 

posted on 2015-11-16 10:16  青瓷引擎  阅读(6003)  评论(11编辑  收藏  举报