中国・上海のWeb制作会社訪問ツアーなど、総額約150万円の豪華賞品が当たるアプリアワードを行う「TapApp(タップ*アップ)プロジェクト」と連動した本連載。4回目では、前回に引き続き、サイバーエージェント(以下、CA)のデベロッパー・平木聡氏に、CAが運営する「Ameba」上で提供されているゲーム「ULTIMATE RACER」を例に、JavaScript高速化テクニックの一部を解説してもらった。また今回も、本誌開発者向け技術動向記事の執筆でおなじみ、後藤大地氏が技術監修を担当している。

平木聡 Ameba事業本部スマートフォンDiv.ディベロップメントグループ ディベロッパー。DTPオペレーターからWebデベロッパーへの転身を経てCAに入社。



Session C 既存スクリプト最適化作業の実際

実際のプロジェクトにおいては、ゼロからの開発よりも既存コードの改良作業のほうがむしろ頻繁になることが往々にしてある。そこで、高速化を図るための工夫を実例を通してみていきたい。

本作はレースゲームであるため、下記のランキング画面表示の高速化が必要になった。そこで機能追加を行う際、より高速な表示を行えるように改良を加えた。既存のスクリプトに対するチューニングだったため、元のソースコードの構造を大きく変えることはせず、個々の処理をより高速なものに置き換えた。本ページに記したのが、改良後のランキング部分のソースの一部だ。改良前のソースとあわせて参考にしてほしい。(平木)

ランキング画面に「バトル申請」機能を追加することになったため、その開発と同時にパフォーマンス面でも改良を実施。以下のような変更を加えた



window.amb.sp_race.mycart.updateRanking = function(json) {
    if ($('input[name=promoteStatus]').val() == 'TOTALUP') {
        return;
    }
    varplayerRank = json.data.playerRank;
    var data = json.data.roundRankingBeans;

    varrankingElement = $('#leagueRanking');
    var html = '';
    rankingElement.empty();

// 改良点1
    varpromotionNumber = $('input[name=promotionNumber]').val() >> 0;
    vardemotionNumber = $('input[name=demotionNumber]').val() >> 0;
    varplayerUserId = $('input[name=playerUserId]').val() >> 0;

    varelementHtml = '<h3>RANKING</h3>' + "\n";
    elementHtml += '<ol class="playerList promotion">' + "\n";

    $(data).each(function(index, ranking) {
      varuserAttr = ranking.userAttribute;
      varuserMachineList = ranking.userMachineList;
      varuserAttrMachine = userMachineList[0];
      var grade = ranking.gradeId;
      varbattleIndicator;

// 改良点2
      for (var i = 0, len = userMachineList.length; i < len; i++) {
        if (userMachineList[i].attribute === userAttr) {
            userAttrMachine = userMachineList[i];
        }
      }

      if (index !== 0 && index === promotionNumber) {
          html += '</ol>' + "\n";
          html += '<hr>' + "\n";
          html += '<h4>△リーグ' + (grade * 1 + 1) + ' 昇格圏内△</h4>' + "\n";
          html += '<ol class="playerList remain">' + "\n";
      }
// 改良点3
      if ((index + 1) === demotionNumber) {
          html += '</ol>' + "\n";
          html += '<hr class="closerHr">' + "\n";
          html += '<h4>▽リーグ' + (grade * 1 - 1) + ' 降格圏内▽</h4>' + "\n";
          html += '<ol class="playerList demotion">' + "\n";
      }

      html += '<li>' + "\n";

      var userId = ranking.userId;
      if (playerUserId === userId) {
          html += '<div id="player" class="playerDetail">' + "\n";
      } else {
// 改良点4
         switch(ranking.battleRequestType) {
           case 0:
             battleIndicator = ' noRequestGpBattle';
             break;
           case 1:
             battleIndicator = ' requestGpBattle';
             break;
           case 2:
             battleIndicator = '';
             break;
           default:
             battleIndicator = '';
           }
             html += '
' + "\n"; }

※以下、Beforeは改良前のソース、Afterが改良後のソース

改良点1 整数を取る場合はparseintよりも右シフト演算の方が高速であるため、処理を変更



// Before
varpromotionNumber = parseInt($('input[name=promotionNumber]').val(), 10);

// After
varpromotionNumber = $('input[name=promotionNumber]').val() >> 0;


改良点2 for文で要素の長さが動的に変わらないならば、いったん変数に代入してから変数を参照するように変更



// Before
for (var i = 0; i<userMachineList.length; i++) {
  if (userMachineList[i].attribute == userAttr) {

// After
for (var i = 0, len = userMachineList.length; i<len; i++) {
  if (userMachineList[i].attribute === userAttr) {


改良点3 厳密等価演算子を使うことで型変換のコストを削減



// Before
if ((index + 1) == demotionNumber) {

// After
if ((index + 1) === demotionNumber) {


改良点4 if構文よりもswitch構文の方が処理が高速なので、記述を変更



// Before
html += '<div class="playerDetail' + (battleRequest ? ' requestGpBattle': '') + '">' + "\n";

// After
switch(ranking.battleRequestType) {
  case 0:
    battleIndicator = ' noRequestGpBattle';
    break;
  case 1:
    battleIndicator = ' requestGpBattle';
    break;
  case 2:
    battleIndicator = '';
    break;
  default:
    battleIndicator = '';
  }
html += '<div class="playerDetail' + battleIndicator + '">' + "\n";


Session D ゲーム中のコードに見る高速化Tips

最後に高速なレスポンスが特に要求されるレースイベントのソースコードから、パフォーマンスを追求した箇所を紹介。それぞれ小さなテクニックだが、これらの積み重ねによってUXは大きく向上する。

平木氏が開発したレースイベント部分のコードから、高速化を意識した部分をいくつか挙げてもらった。このほか、ネイティブメソッド使用の重視、HTML/CSS側で実現可能な機能をスクリプト側へ持ち込まないことなどを心がけているという。(後藤)

反復にsetTimeoutを使用

1秒ずつ減算するカウントダウンなど反復処理を行う場合、setInterval関数を用いることがあるが、setTimeout関数でも目的を達成できる場合、これを再帰して使うことでUIのモタツキを軽減することができる。



(function timer() {
  varnowDatetime = new Date(),
    diffSecond = (self.remainTimes.endDatetime.getTime() -
nowDatetime.getTime()) / 1000,
    remainDays, remainHours, remainMinutes, remainSeconds;

// 終了時刻を過ぎたら
// 必ず0を返すようにする
  if (diffSecond< 0) { diffSecond = 0; }

// DOM要素を更新する
// (23:59:59 フォーマット)
// 60 * 60 * 24 = 86400
  remainDays = Math.floor(diffSecond / 86400);
  remainHours = Math.floor((diffSecond % 86400) / 3600);
  remainMinutes = Math.floor((diffSecond % 3600) / 60);
  remainSeconds = Math.floor(diffSecond % 60);

  $day.text(remainDays);
  $times.text(('00' + remainHours).substr(-2) + ':' +
    ('00' + remainMinutes).substr(-2) + ':' +
    ('00' + remainSeconds).substr(-2));
  setTimeout(timer, 1000);
}());


変数を活用しDOM走査を減らす

ゲーム内で燃料等を表示するステータスバー関連の処理。ローカル変数を適切に使用することでDOMツリーの走査処理を減らしている。



vareventrace = this,
  race = window.amb.sp_race,
  $fuel = $('#fuel'),
  $fuelInfo = $fuel.fi nd('em.value'),
  $fuelOldCapacity = $fuelInfo.eq(0),
  $fuelNewCapacity = $fuelInfo.eq(1),
  $fuelGauge = $fuelInfo.eq(2),
  $itemBtn = $fuel.fi nd('.useItem'),
  $fuelMessage = $('.applied, .completeMessage', $fuel),
  $itemList = $fuel.fi nd('.itemList'),
  $fuelNum = $itemList.fi nd('em').eq(0),
  $machineFuel = $('#machineFuel').fi nd('em.value');


次回はソーシャルアプリケーションの開発に携わるCAのデザイナー、ディレクターに、ソーシャルUIデザインについて解説してもらう予定だ。

※サービス開発運用状況によって、仕様に変更が入る可能性がありますのでご了承ください。
※当記事は、株式会社マイナビ刊行のWeb Designing2012年11月号掲載の記事を加筆、修正したものです。

スマホ向けアプリコンテスト「TapApp Awards」開催中!!


スマホアプリ開発技術の啓蒙と次世代の才能発掘を目的とした「TapApp(タップ*アップ)」プロジェクトでは、スマホ向けアプリアワードを開催中です。詳細は、下記Web ページに記載していますので、ご確認の上、どしどしご応募ください。

  • 【募集部門】フリー部門◎「繋がる」をキーワードとしたアプリケーションをあなたのアイデアで創ってください。
  • 【募集部門】テーマ部門◎ 下記のいずれかを選び、それをテーマとしたアプリケーションをあなたのアイデアで創ってください。[Clock(時計)] [カレンダー/スケジューラー][毎日3 分遊びたくなるゲーム] [リズムで遊ぶアプリケーション][温かみを感じるアプリケーション] [Photo Library(写真共有)]
  • 【開催スケジュール】募集期間◎~2013年1月15日(火)、結果発表◎ 2013年3月 中旬
  • 【賞金/ 副賞】「上海クリエイティブオフィスツアー」ほか、総額約150万円

詳細はTapApp(タップ*アップ)プロジェクト公式サイトをご覧ください。※内容が変更になる場合があります。