40歳からのキャリアチェンジ

20代はエンジニア・PM、30代はWeb系エンジニア向けのキャリアアドバイザー。40代の今はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連あたり

簡易シューティングゲーム解説エントリを書いてきます

f:id:h5y1m141:20120227081125p:plain

前回書いたこのエントリそこそこはてブされたのに気づいたので、簡易シューティングゲーム解説エントリを頑張って書いてこうかと思います

前回も触れましたが、サンプルとしてWindowsPhone向けの以下書籍にあったサンプルアプリをベースに作ってみました

Windows Phoneゲーム プログラミング
田中 達彦
ソフトバンククリエイティブ
売り上げランキング: 345685

ゲーム仕様について

上記書籍のシューティングゲームの仕様の中で

砲台はいちばん下の中央に固定し、動かない

というのがありました。
フリックした方向&量をベースにして、砲台から発射する弾道の計算をするロジックがあるのですが、それをTitaniumMobile+QuickTiGame2dでどのように実装するのかちょっとわからないので、

  • タッチした箇所に砲台が移動する
  • 砲台から出る弾は一定のスピードで直進していく

という仕様にしました

QuickTiGame2dを使ったゲーム開発の流れ

前にも書いたように今回ゲーム開発にはじめてトライしてみました。取り組む前にイメージ出来なかったのは、
「ゲームの流れってどういうロジックなんだろう??」
っていうところでした。
これについては、WindowsPhoneの書籍とはいえ、上記書籍でとても参考になる記述がありました。

XNAを使用したプログラムは、図3-1のような流れで実行されます。プログラムの実行時に初期化のメソッド(関数)が呼ばれます。次に、コンテンツを読み込むためのメソッドが呼ばれます。そのあとは1/30秒ごとに状態の更新を行うメソッドと、描画を行うためのメソッドが呼ばれます。

これをふまえて、QuickTiGame2dをつかって開発する流れは大きくこんな感じになるかと思います

  1. quicktigame2d.createSpriteを使って必要な画像の読み込み
  2. quicktigame2d.createSceneでSceneオブジェクト生成し、読み込んだ画像をSceneオブジェクトに配置するためにaddメソッドを利用する
  3. quicktigame2d.createGameViewでGameViewオブジェクトを生成。1/30秒ごとに状態の更新を行うメソッドのに相当するのが、GamViewオブジェクトのenterframeというイベントリスナーなので、そこに必要な処理を記述する

※ 2012/02/28 追記
LearningTiさんからこんなtweetされました。



そんなわけで、動画キャプチャしてみました。(※実は、iPhone Simulator Captureっていうのがあることすら知らなかったっw)


※ここまで追記

ソースコード

ひとまず出来たソースはこんな感じです。

// app.js
var win1 = Titanium.UI.createWindow({
    title:'Tab 1',
    backgroundColor:'#fff'
});

/*
  ゲームの初期設定
 */
var quicktigame2d = require('com.googlecode.quicktigame2d');
var game = quicktigame2d.createGameView();
var scene = quicktigame2d.createScene();
var totalScore = 0;
game.screen = {width:320, height:480};
game.fps = 30;
game.color(0, 0, 0);
game.pushScene(scene);

var MAXALIENS = 10;   // 敵の表示最大数の設定
var MAXBULLETS = 5; // 弾の最大数
var aliens = [];
var aliensSpeed = [];
var bullets = [];
var bulletsSpeed = [];

var tank = quicktigame2d.createSprite({image:'images/tank.png'});

/*
  横位置:画面下辺の中央に設置するために画面横幅の半分を取得
  さらに画像の幅の半分を引くことで描画する位置のX座標が計算可能

  縦位置:画面の高さから画像の高さ分を引くことで計算可能
*/
tank.x = (320/2) - (tank.width/2);
tank.y = 480 - tank.height;
var back = quicktigame2d.createSprite({image:'images/back.png'});
back.x = (320/2) - (back.width/2);
back.y = 480 - back.height;
scene.add(back);
scene.add(tank);

/*
  敵と砲台から出る弾の初期設定
*/
for(var i=0;i<MAXALIENS;i++){
  aliens[i]= quicktigame2d.createSprite({image:'images/alien1.png'});
  aliensSpeed[i] = 10; //敵の移動スピード設定
  scene.add(aliens[i]);
}

for(var j=0;j<MAXBULLETS;j++){
  bullets[j]= quicktigame2d.createSprite({image:'images/bullet.png'});
  bullets[j].x = initBulletsPostion();
  bullets[j].y = tank.y - (bullets[j].height);
  bulletsSpeed[j] = 20;
  scene.add(bullets[j]);
}


game.addEventListener('onload', function(e) {

  for (var i = 0; i < MAXALIENS; i++) {
    aliens[i].x = Math.random() * game.screen.width;
    aliens[i].y = -100;
  }
  game.start();
});
game.addEventListener('enterframe', function(e) {

  bulletCollidesWithAliens();
  updateAliensPosition();
  updateBulletPosition();


});

game.addEventListener('touchmove',function(e){
  tank.x = e.x;
});

win1.add(game);
win1.open();

/*
  敵の位置の再計算を行い、非表示にしたいものがあったら画面外に
  配置させることで結果的に非表示にする
 */
function updateAliensPosition(){
  for (var i = 0; i < MAXALIENS; i++) {
    aliens[i].y += aliensSpeed[i] * Math.random();
    if(aliens[i].y > 480){
      aliens[i].y = -100;
    }
  }
}


/*
  弾の位置の再計算を行い、非表示にしたいものがあったら画面外に
  配置させることで結果的に非表示にする。
 */
function updateBulletPosition(){
  for (var i = 0; i < MAXBULLETS; i++) {
    bullets[i].y -= bulletsSpeed[i];
    // 弾が画面外に出た時にはタンクの砲台位置に弾を再設定

    if(bullets[i].x < 0 ||bullets[i].x > 320 || bullets[i].y < 0 || bullets[i].y > 480){
      bullets[i].x = initBulletsPostion();
      bullets[i].y = tank.y - (bullets[i].height);
    }
  }

}

function bulletCollidesWithAliens(){
  for (var i = 0; i < MAXBULLETS; i++) {
    for(var j=0;j<MAXALIENS;j++){
      var flg = bullets[i].collidesWith(aliens[j]);
      if(flg){
        totalScore +=100;
        Ti.API.info('スコアー:' + totalScore);
        aliens[j].y = -100;

        //弾道をタンクの位置にセットしなおす
        bullets[i].x = initBulletsPostion();
        bullets[i].y = tank.y - (bullets[i].height);
      }
    }
  }
}

function initBulletsPostion(){
  // 弾道は配列で保持しているけれど、幅はすべて同じなので
  // 配列の先頭に格納している弾道の幅を取得した上で
  // 弾道を砲台の箇所に位置するように処理
  return tank.x + (tank.width/2) -(bullets[0].width/2);
}

以下環境で開発&実機テストしました。この程度の処理なら全然ストレスなく遊べますねー

  • OS X 10.7.2
  • Titanium Mobile(SDK 1.8.1)
  • quicktigame2d(version 0.4)
  • 実機はiPod Touch(第四世代のやつだけどiOS4.xのままでアップデートしてない)

ソースの解説は次回以降それぞれの箇所について説明していこうと思います