ニコ生ゲームを作ろうと思ったらすぐ作ろう その4

第4回は前回起動したサンプルゲームのゲーム性を高めていきます。

■目次

【改造の方針】

【改造内容】

  • 各行ごとの積み数を作る (配列を作ってみる)
  • 積み数に応じてタイルを増やす (配列を参照する)
  • 射撃に応じて積み数を操作する
  • 積み数の一致をチェックする (配列を操作する)
  • 積み数をリセットしてランダムに積む
  • 積み数を得点に反映させる
  • 積み数の超過をチェックする

 


 

【改造の方針】

残弾数程度ではまだまだゲームとは言えません。
具体的に物足りないところを挙げてみます。

  • 標的がない
  • タイルの意味がない
  • 爽快感がない
  • ゲームの展開が単調、というか無い

 

作品として最低限の面白さが生まれるように以下の変更をやってみます。

  • ショットによってタイルが積もるようにする
  • タイルの数が一致すると消えるようにする
  • タイルをたくさん貯めると高得点にする
  • 最後まで貯まるとミスになるようにする

 

単に敵を倒すシューティングゲームにすると、泥棒バスターと似てしまいそうなので、
横向きのテトリスクォースのようにしてみます。

 

【改造内容】 各行ごとの積み数を作る (配列を作る)

タイルは7行作りました。
それぞれの行の数字の積み数を保存し、その数だけ右からタイルを表示します。

 

bulletの数字を作ったときのように var stack = 0; をloadの中に置きます。
7列あるので7つの値が必要です。

var stack1 = 0;
 ︙
var stack7 = 0;

上のように7個名前をつくってもいいのですが、今回は配列を使います。

var stack = [4, 3, 2, 1, 2, 3, 4];

このようにカンマで区切られた複数の値を一つにまとめることができます。

利用するときは stack[0] のように番号を指定すると、
カッコの中の一番最初の数字4が呼び出されます。番号は0から始まり6までです。



【改造内容】積み数に応じてタイルを増やす (配列を参照する)

stackの値に合わせてタイルが消えたりついたりするようにします。

また新しい書き方を導入しますが・・・ちょっと説明が重いので、とりあえず下のものをコピペしてください。タイルを繰り返し配置したところに置き換えます。

var tiless = ;
for (var j = 0; j < 12; j++) {
  var tiles =
;
  for (var i = 0; i < 7; i++) {
    var tile = new g.FilledRect({
      scene: scene, cssColor: "royalblue", parent: scene,
      x: g.game.width - 100 - 90 * j, y: 90 * i + 47,
      width: 86, height: 86, opacity: 1, tag: {row: i, column: j,},
    });
    tiles[i] = tile;
  }
  tiless[j] = tiles;
}

scene.onUpdate.add(function () {
  for (var j = 0; j < 12; j++) {
    for (var i = 0; i < 7; i++) {
      if (stack[tiless[j][i].tag.row] <= tiless[j][i].tag.column) {
        tiless[j][i].opacity = 0;
      } else {
        tiless[j][i].opacity = 1;
      }
      tiless[j][i].modified();
    }
  }
});


細かい話をすると以下の方法を導入しています。

  • 常に更新する処理 scene.onUpdate.add
  • tile に tag というパラメータを追加
  • tag に row , column という2つのパラメータを収納 とその利用方法 .tag.row
  • tile を複数収納する tiles という配列
  • tiles を複数収納する tiless という配列 とその利用方法 tiless[j][i]

このままコピペで使い回せるといいのですが、その時やりたいことによると思います。

もし暗号解読が趣味の人はにらめっこして理解して見てください。多次元配列、連想配列などで検索すると役立つかも。

 

【改造内容】射撃に応じて積み数を操作する

射撃に応じて積み数を操作するには、弾がどの行にあるか判別する必要があります。
行0~行6のどこにいるかの判定式は下のようになります。

Math.floor ( (shot.y + shot.height/2 - 45) / 90 )

この数値が0~6のどれかの整数になります。
Math.floorは小数点以下を切り捨てて整数にする関数ですね。

 

行の番号がわかればその積み数は stack[] のカッコの中に番号を入れたものなので、

stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )]

これが操作すべき積み数になります。

飛んでいく弾が消えるタイミングでこの値を増やしましょう。

// 弾の初期座標を、プレイヤーの少し右に設定します
shot.x = player.x + player.width;
shot.y = player.y + player.height/2 - shot.height/2;
shot.onUpdate.add(function () {
  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x > g.game.width) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});

ここで数値を1だけ増加させる表現 ++ を使用しています。
ついでにshotが発生する高さshot.yを微調整し、
さらについでにshotのスピードも遅いので、shot.x += 40;に変更します。


  



これでなんとなく射撃でタイルが積み上がってきていますが、射撃が画面端まで行かないと反応しないのが感触が悪いですね。
弾が消える位置も積み数に応じて変更していきましょう。

shot.onUpdate.add(function () {
  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});

射撃したものが固まってタイルになっている雰囲気にしたいですが、とりあえずここまでで。

  

  

【改造内容】積み数の一致をチェックする

積み数が揃って、きれいにならされたら得点が入ることにしましょう。
打つ場所をミスると状況が悪化するので、若干緊張感が生まれます。

得点を増やす処理は、scene.onPointDownCapture.add(function () {のすぐ下にあります。
長い名前ですが、g.game.vars.gameState.scoreがスコアになります。

// 制限時間以内であればタッチ1回ごとにSCOREに+1します
if (time > 0) {
  g.game.vars.gameState.score++;
  scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
  scoreLabel.invalidate();
}


タイミングは積み数が変わるタイミングにしましょう。
積み数が揃ったかどうかの判定を追加し、上記の得点処理を移動します。


shot.onUpdate.add(function () {
  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score++;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});


Math.absはカッコ内を絶対値にする関数です。
+=は前にある数値に後ろの値を加えて、前にある数値に代入します。
match == 0のイコールを2つ重ねているのは、等しいかどうかを判定するものです。
match = 0 だとmatchの値を0にするという入力の意味になってしまいます。


【改造内容】積み数をリセットしてランダムに積む

タイルが揃ったら消さないとだめですね。

タイル数を全部0にすれば消したことになりますが、消したあとのタイル数が
毎回同じだと面白みがないので、積み数を2~4ぐらいでランダムで置き直します。
かつ、最初から揃っているという状態を防ぐため、1箇所だけ積み数を1にします。

shot.onUpdate.add(function () {  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score++;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});


g.game.random.get(2, 4)がランダムの処理です。2~4の整数のどれかになります。
2も4も可能性があるので、この場合は1/3の確率になります。



【改造内容】積み数を得点に反映させる

得点が1点ずつだと寂しいので、積み数を反映させます。

似たようなスコアが出ないように細かく分かれるのがいいのですが、このゲームだと積み数は9種類しかなく、残り時間と絡めるのも難しそうです。

積み数に比例して得点があがる
ようにして、バリエーションが出るようにします。
消す数が多ければ得点がどんどんインフレしていくゲームもよくありますが、
ちょっとやってみると加速しすぎたので比例にとどめておきます。

shot.onUpdate.add(function () {  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score+= stack[0]*100;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});


【改造内容】 積み数の超過をチェックする

最大まで積むと高得点ですが、1個でも超過すると無に帰すリスク要素を追加します。
stackが12を超えたら、得点無しでリセットをかけます。

shot.onUpdate.add(function () {  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score+= stack[0]**2*10;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
    if (stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] > 12 ){
      if (time > 0) {
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});

 



【終了】

これでとりあえずゲームっぽくはなったと思います。

すごく面白いかと言われると、最初の感触では虚無ゲーかなと思われてしまいそうなレベルです。
が、一応は動きと弾数が制限されている中で、素早く積み上げるための最適解は多少複雑になっているはずです。

最大まで積み上げるときは、適当に積み上げるシーンと、気をつけながらならすシーンの両方があることになります。

最終コードは こちらです。

では次は見た目と音を変えて、サンプルゲームの印象を払拭しましょう。