GC DevKit 快速入门 -- 游戏概览(三)

接上节 http://www.cnblogs.com/hangxin1940/archive/2013/04/11/3015553.html

启动流程

在构造函数init中,我们通过监听'app:start'事件来处理上层的通知,然后调用 start_game_flow 方法来播放动画序列,最后调用play_game来开始游戏。

function start_game_flow () {
  var that = this;

  animate(that._scoreboard).wait(1000)
    .then(function () {
      that._scoreboard.setText(text.READY);
    }).wait(1500).then(function () {
      that._scoreboard.setText(text.SET);
    }).wait(1500).then(function () {
      that._scoreboard.setText(text.GO);
      //开始游戏 ...
      game_on = true;
      play_game.call(that);
    });
}

Ready, Set, Go!这三个单词在切换时,有一个短暂的停顿,每次都会更新计分板的内容,并且加载下一步动画。在最后一步动画,会调用play_game.call(that)方法。that只是this的一个引用而已,只是方便在不同的上下文中进行正确的引用。

游戏开始

play_game 函数包含了几个定时器,tick函数用来随机的让鼹鼠从洞中冒出来,一个计时器有来每秒钟在一个TextView更新倒计时,并且在超过时间时结束游戏。

function play_game () {
  var i = setInterval(bind(this, tick), mole_interval),
      j = setInterval(bind(this, update_countdown), 1000);

    //复位所有定时器、标记以及倒计时
  setTimeout(bind(this, function () {
    game_on = false;
    clearInterval(i);
    clearInterval(j);
    setTimeout(bind(this, end_game_flow), mole_interval * 2);
    this._countdown.setText(":00");
  }), game_length);

  //让倒计时可见,并且删除开始提示信息
  setTimeout(bind(this, function () {
    this._scoreboard.setText(score.toString());
    this._countdown.style.visible = true;
  }), game_length * 0.25);

  //没有时间时,置红倒计时文字
  setTimeout(bind(this, function () {
    this._countdown.updateOpts({color: '#CC0066'});
  }), game_length * 0.75);
}

function tick () {
    //随机选择一个鼹鼠
  var len = this._molehills.length,
      molehill = this._molehills[Math.random() * len | 0];

    //如果选择鼹鼠已经是激活的,那么选择另一个
  while (molehill.activeMole) {
    molehill = this._molehills[Math.random() * len | 0];
  }
  molehill.showMole();
}

function update_countdown () {
  countdown_secs -= 1;
  this._countdown.setText(":" + (("00" + countdown_secs).slice(-2)));
}

结束序列

在游戏倒计时结束后,游戏的结束动画就会开始,由end_game_flow函数播放,它会检查游戏得分,并且将它显示出来,而鼹鼠则持续不断的冒出就好像它在嘲笑你。

function end_game_flow () {
  var isHighScore = (score > high_score),
      end_msg = get_end_message(score, isHighScore);

  this._countdown.setText(''); //清除倒计时信息
  //重设记分牌
  this._scoreboard.updateOpts({
    text: '',
    x: 10,
    size: 17,
    verticalAlign: 'top',
    textAlign: 'left',
    multiline: true
  });

  //检查是否是最高分并播放相应动画
  if (isHighScore) {
    high_score = score;
    this._molehills.forEach(function (molehill) {
      molehill.endAnimation();
    });
  } else {
    var i = (this._molehills.length-1) / 2 | 0; //居中鼹鼠
    this._molehills[i].endAnimation(true);
  }

  this._scoreboard.setText(end_msg);
  //短时间的延迟再允许触摸复位
  setTimeout(bind(this, emit_endgame_event), 2000);
}

一旦新的记分板被设置,并且鼹鼠动画正在播放,会在2秒的时间后来监听用户的触摸事件

function emit_endgame_event () {
  this.once('InputSelect', function () {
    this.emit('gamescreen:end');
    reset_game.call(this);
  });
}

用户点击后,一个gamescreen:end事件会被发送,上层程序所处理这个事件,之后视图堆栈会弹出并关闭这个游戏视图,显示出标题视图,以便用户重新开始游戏。

鼹鼠资源:MoleHill.js

MoleHill类是另外一个比较大的类,它存放于./src/MoleHill.js

对齐组件

一个鼹鼠洞是由3种图像堆叠而成的:底层的鼹鼠洞,鼹鼠,以及上层的鼹鼠洞。鼹鼠的动画是在Y轴进行上下活动,并且会给它一个矩形遮罩,用以盖住鼹鼠图片超出的部分。鼹鼠看起来就像跳出洞口等待敲它一样。

this.build = function () {
  var hole_back = new ui.ImageView({
    superview: this,
    image: hole_back_img,
    //...
  });

  this._inputview = new ui.View({
    superview: this,
    clip: true,
    //...
  });

  this._moleview = new ui.ImageView({
    superview: this._inputview,
    image: mole_normal_img,
    //...
  });

  var hole_front = new ui.ImageView({
    superview: this,
    canHandleEvents: false,
    image: hole_front_img,
    //...
  });

  //...

  this._inputview.on('InputSelect', bind(this, function () {
    if (this.activeInput) {
      sound.play('whack');
      this.emit('molehill:hit');
      this.hitMole();
    }
  }));
};

这副图形象的说明了三个ImageView是如何摞成一个鼹鼠的:

devkit

如果鼹鼠的身体下方伸出了鼹鼠洞那就完蛋了,我们必须让人感觉鼹鼠的身体下部分在草地下面。这里可以使用剪贴蒙板来创建一个新的View,只需要将clip属性置为true,这样任何附加于这个视图的子视图将只显示它范围内的图像。在上面的图中,鼹鼠只显示它所附加的剪贴蒙板中自己的图像(黑色方框内)。

我们同样也将这个剪贴蒙板视作一个按钮区域,用来检测是玩家否敲到正确的鼹鼠身上,只要触摸落在矩形区域内,并且鼹鼠是被激活的,都视为一次成功的敲击。但是,还有一个问题,用户的输入被鼹鼠图像捕获,但是在它之前还有一个鼹鼠洞的图像,它遮住了下面的区域,这样使得事件不能被正常的处理。我们可以通过将上层图像的canHandleEvents属性设置为false,来让触摸事件达到预期效果。它可以让事件穿过这个视图,继而让下方的控件捕获。

此外,在build函数中,我们给Animator对象赋了一个函数引用,我们将在游戏过程中使用到它。

this._animator = animate(this._moleview);

我们可以在任意时间执行这个函数,用来播放鼹鼠动画,当然它不会视为有效的游戏动作。这样引用有个好处,每次想播放动画时是要调用它就行,不必每次都创建一个新的Animator对象。

鼹鼠动画

MoleHill类中定义了三个动画序列:鼹鼠跳出洞、鼹鼠回洞以及结束动画中鼹鼠慢慢的出来并"嘲笑"玩家。它们都使用Animator对象进行定义:

//鼹鼠出洞
this.showMole = function () {
  if (this.activeMole === false) {
    this.activeMole = true;
    this.activeInput = true;

    this._animator.now({y: mole_up}, 500, animate.EASE_IN)
      .wait(1000).then(bind(this, function () {
        this.activeInput = false;
      })).then({y: mole_down}, 200, animate.EASE_OUT)
      .then(bind(this, function () {
        this.activeMole = false;
      }));
  }
};

//打鼹鼠
this.hitMole = function () {
  if (this.activeMole && this.activeInput) {
    this.activeInput = false;

    this._animator.clear()
      .now((function () {
        this._moleview.setImage(mole_hit_img);
      }).bind(this))
        .then({y: mole_down}, 1500)
        .then(bind(this, function () {
          this._moleview.setImage(mole_normal_img);
          this.activeMole = false;
          this.activeInput = false;
      }));
    }
};

//结束动画
this.endAnimation = function () {
  this.activeInput = false;
  this._animator.then({y: mole_up}, 2000)
  .then(bind(this, function () {
    this._interval = setInterval(bind(this, function () {
      if (this._moleview.getImage() === mole_normal_img) {
        this._moleview.setImage(mole_hit_img);
      } else {
        this._moleview.setImage(mole_normal_img);
      }
    }), 100);
  }));
};

animate函数插入了一个JavaScript对象属性,如果它传入了一个View对象,那么会为其插入这个对象的样式属性。这种方式十分的便利,因为很有可以我们会基于当前样式进行动画。

举个例子,如果一个鼹鼠在洞中,我们让它出洞,一下是showMole函数:

this._animator.now({y: mole_up}, 500, animate.EASE_IN)
  .wait(1000).then(bind(this, function () {
    this.activeInput = false;
  })).then({y: mole_down}, 200, animate.EASE_OUT)
    .then(bind(this, function () {
      this.activeMole = false;
  }));

首先,动画对象调用 .now({y: mole_up}, 500, animate.EASE_IN) 函数,它会立即操作动画对象的Y轴属性,并且把它定义为this._moleview对象。因为主要的动画是一个View类的实例,我们实际上实在操作他的 style.y 属性,或者说鼹鼠图像在屏幕上的垂直位置。mole_up变量在文件一开始被设置为5,它是想对于父视图this._inputview的偏移量。这个动画的第一步执行了半秒钟/500毫秒,缓慢的移动到最终位置,最终鼹鼠露出了头。

然后,第二部分的动画会执行.wait(1000)方法暂停1秒,然后继续下一个动画。这样看起来,鼹鼠会钻出洞后然后飞快的又进到洞里。如果这段时间点击鼹鼠,就会被记上分数。

最后 .then( ... ) 被调用,它通过一个回调函数被立即执行,这个方法将鼹鼠洞的activeInput置为false,这样这个鼹鼠洞就不会接受输入事件。这个动作结束,动画会进入下一个动画序列。

我们现在期望鼹鼠能回到洞里,所以调用 .then({y: mole_down}, 200, animate.EASE_OUT) ,正如我们看到的那样,.then函数可以用多种方式来调用。这里,我们通过之前的.now()来调用,这把鼹鼠在Y轴的位置进行改变,使之缓慢的降低,进入洞中。

整个动画结束后,我们最后使用一次 .then( ... ) 来让这个鼹鼠的属性activeMole变为false,整个动画流程就结束了。

声音

我们使用一个单独的控制器来加入声音,它位于./src/soundcontroller.js:

import AudioManager;

exports.sound = null;

exports.getSound = function () {
  if (!exports.sound) {
    exports.sound = new AudioManager({
      path: 'resources/sounds',
      files: {
        levelmusic: {
          path: 'music',
          volume: 0.5,
          background: true,
          loop: true
        },
        whack: {
          path: 'effect',
          background: false
        }
      }
    });
  }
  return exports.sound;
};

在这里,我们在程序启动时创建了一个AudioManager对象,并且在任何时候调用getSound函数都会返回这个对象。

我们回到 ./src/Application.js 文件的 initUI 函数来看看它的使用细节。

this.initUI = function () {
  //...

  var sound = soundcontroller.getSound();

  //...

  titlescreen.on('titlescreen:start', function () {
    sound.play('levelmusic');
    GC.app.view.push(gamescreen);
    GC.app.emit('app:start');
  });

  gamescreen.on('gamescreen:end', function () {
    sound.stop('levelmusic');
    GC.app.view.pop();
  });
};

当用户按下开始按钮后,titlescreen:start事件会被触发,背景音乐就会被播放。如果levelmusicloop 属性为true,那么整个声音会被循环播放,直到游戏结束,这些都在 ./src/soundcontroller.js 文件中的new AudioManager(...)中设置

总结

打鼹鼠游戏很简单,但它是一个可工作的完整的游戏,我们通过它学习了引擎的api如何组织到一起。

posted on   黑暗伯爵  阅读(354)  评论(0编辑  收藏  举报

编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述
历史上的今天:
2008-04-13 (c#)数据结构与算法分析 --树

导航

< 2013年4月 >
31 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 1 2 3 4
5 6 7 8 9 10 11

统计

点击右上角即可分享
微信分享提示