【プチコン4講座】ショットに衝突判定を付けて岩を壊してみよう

プチコン4

プチコン4 シューティングゲーム ショット

こんにちは。継続の錬金術士なおキーヌです。

ブログ毎日更新は217日目になります。

第8回「【プチコン4講座】障害物がある方がゲームは燃える!」にてプチコン4における衝突判定の基礎をやりました。

障害物の実装と衝突判定の軽い実装は前回で行いました。

今回は折角ショットが撃てるのですから意味を持たせてみましょう。

ひとまずは衝突判定が完成している岩に当たったら岩が消えるという処理を作りましょう。

それではプチコン4でSTG作りその第9回目始めましょう。

  1. 弾と障害物の衝突判定を取る
  2. シューティングゲーム作り講座第9回まとめ

弾と障害物の衝突判定を取る

折角弾を撃てるんだから岩を壊せるようにしてみようか。

よっしゃ!シューティング要素やっと来たな!

プレイしてみて確かにこのままだと撃てた方が面白くなりそうね。

今回は特に新しいことはしないからサクサクいくよ。過去に使ってきた処理だからわからなかったら過去記事を読んでみてね。そのうちリンク張るから今は許して。

' 弾衝突チェック
'───────────────────────────────
def D_ShotColCheck(A_I)

  ' 受け取ったA_Iが障害物に当たったかチェック
  var isHit = SPHITSP(A_I, G_SpvManageNo[3], G_SpvManageNo[4])

  ' 障害物が既に衝突判定を受けていない状態で衝突した場合  
  if isHit != -1 && SPVAR(isHit,"IS_HIT") == 0 then
    ' 障害物破壊音を鳴らす
    BEEP 120
    ' 障害物の衝突フラグをON
    SPVAR isHit, "IS_HIT", 1
    ' 弾も初期化してその場から消す
    D_ShotPosSettings A_I
    ' 障害物を透明にする
    SPHIDE isHit
    ' 呼び出し元に0を返してヒットしたことを伝える
    return 0
  else
    ' 何も当たってない場合は-1を返してヒットしてないことを伝える
    return -1
  endif
end

' 弾リセット処理
'───────────────────────────────
def D_ShotPosSettings A_I
  SPOFS A_I, -16, -16
  SPVAR A_I, "X", 0
  SPVAR A_I, "Y", 0
  SPVAR A_I, "SHOT_FLAG", 0
end

殆ど弾の衝突処理のコピーだね。弾リセット命令は初期化の部分の一部からとってきたからスプライト初期化も変更しておこうね。障害物も弾にヒットしたらHIDEしてるから再配置関数の最後にSHOWを入れて表示してあげよう。

' スプライトの初期化
'───────────────────────────────
def D_SpriteInitialize
  ' スプライトを定義
  var PID = G_SpvManageNo[0]
  SPSET PID, 352 ' プレイヤー
  SPVAR PID, "X", 0
  SPVAR PID, "Y", 0
  SPCOL PID

  ' ループさせて弾を生成
  for G_I = G_SpvManageNo[1] to G_SpvManageNo[2]
    SPSET G_I, 1322,0 ' 弾は最初からHIDE状態
    D_ShotPosSettings G_I
    SPCOL G_I
  next

  ' ループさせて障害物を生成
  for G_I = G_SpvManageNo[3] to G_SpvManageNo[4]
    SPSET G_I, 262 ' 岩
    D_ObstacleRndPosSettings G_I
    SPCOL G_I, 1
  next
end

' 障害物再配置
'───────────────────────────────
def D_ObstacleRndPosSettings A_I
  VAR X = 400 + RND(400)
  VAR Y = RND(225)
  SPVAR A_I, "X", X
  SPVAR A_I, "Y", Y
  SPVAR A_I, "IS_HIT", 0
  SPOFS A_I, SPVAR(A_I,"X"), SPVAR(A_I,"Y")
  SPSHOW A_I
end

これで準備は整ったね。最初に作った衝突判定の関数を弾の移動処理に組み込んでみよう。

' プレイヤーの弾の移動処理
'───────────────────────────────
def D_PlayerShotMove
  for G_I=G_SpvManageNo[1], to G_SpvManageNo[2]
    ' 弾にショットフラグが立っていれば
    if SPVAR(G_I,"SHOT_FLAG") == 1 then
      ' 弾のX座標を加算
      SPVAR G_I, "X", SPVAR(G_I,"X") + 8
      ' 弾を移動させる
      SPOFS G_I, SPVAR(G_I,"X"), SPVAR(G_I,"Y")
      ' 移動した後に衝突判定
      if D_ShotColCheck(G_I) == -1 then
        ' 弾の座標が400を越えたら
        if G_I, SPVAR(G_I,"X") > 400 then
          ' 画面外に行くのでショットフラグを0にして非表示にする
          SPVAR G_I,"SHOT_FLAG",0
          SPHIDE G_I
        endif
      endif
    endif
  next
end

弾を移動した後に関数を使って条件分岐を作ったよ。-1が返ってきたらそのままいつも通りの処理になって、0が返ってきたらヒットしたって証拠だから関数が命令を実行した後に弾は初期化されるからそれ以降何も処理をしないよ。

関数って便利ね。処理も出来るし条件分岐用の役割にもなるなんて。

正直プログラミング初心者にはあんまりオススメしないやり方なんだけどね……処理を一緒くたにしちゃうと確かに楽だけどコードを見る側は混乱すると思うんだ。

確かにここまでの流れを見ているから理解できているが何も言われないとよくわからんな……

関数の名前もチェックしてるぐらいしかわからないからね。まぁその関数の中身を見ればわかるんだけど今回は1つのプログラミングテクニックとして使ってみたよ。色々応用は効くから他でプログラミングした時に使ってみると良いね。

それじゃあ実行してみようか。弾が当たって音がなって弾も障害物も消えたらOKだよ。

わぁ、一気にシューティングゲームぽくなったね!

プレイヤーにライフを付けてぶつかったときに減るようにして岩を壊したときにスコアが増えればもう立派なゲームになるね。

確かにここまでの流れを見ているから理解できているが何も言われないとよくわからんな……

シューティングゲーム作り講座第9回まとめ

ちょっと短いですが今回はココで終了です。

やったことは第8回目と同じくしてスプライトとスプライトの衝突判定の復習でしたね。

プチコン4だから衝突判定はシンプルに書けますが、自力で衝突判定を作るとそれだけで結構長いコードになるので
ゲームを作るのに集中が出来て助かりますね。

ぶっちゃけちゃうと前回の記事にまとめて置けばよかったと思いました。

実はシューティングゲームの入門の入門としてはもうここである意味完成です。

後はトモカズくんの言うようにゲームとして成立させるために必要なゲームオーバーになるための仕組みを作ったり、
スコアを実装していくだけです。

覚える処理としては後は画面に数字を出すという処理さえ覚えてしまえば、
ココまで覚えてきた処理だけでゲームを作ることが出来ます。

残りは

  • スコア and ライフ制 or 1ミス制の実装
  • ゲームオーバーの実装
  • スコア一定数値で難易度上昇
  • 各シーンのBGMとタイトル画面の実装
  • プチコンサーバーにゲームのアップロード手順

全15回の予定で進めます。

STGの後は何を作りましょうかね……

それでは。

' ゲーム初期化
ACLS
' ループ用変数
var G_I
' ボタンカウント配列
DIM G_BtnPressCount[4]

' スプライト管理番号まとめ変数
DIM G_SpvManageNo[] = [0,10,14,20,30]

D_Initialize

' ループ開始
loop
  D_Controller
  D_PlayerMove
  D_PlayerColCheck
  D_PlayerShotMove
  D_ObstacleMove
  ' 垂直同期
  VSYNC
endloop


' ゲーム初期化
'───────────────────────────────
def D_Initialize

  ' マップ描写
  D_MapDraw

  ' スプライト初期化
  D_SpriteInitialize
end

' コントローラー
'───────────────────────────────
def D_Controller
  ' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
  VAR B = BUTTON(0)

  ' 押されているボタンの情報を表示
  if (B AND 1 << #B_LUP) != 0 then
    ' 上を押されたらY座標を減算
    SPVAR 0,"Y", SPVAR(0,"Y")-1
  elseif (B AND 1 << #B_LDOWN) != 0 then
    ' 下を押されたらY座標を加算
    SPVAR 0,"Y", SPVAR(0,"Y")+1
  endif
  if (B AND 1 << #B_LLEFT) != 0 then
    ' 左を押されたらX座標を減算
    SPVAR 0,"X", SPVAR(0,"X")-1
  elseif (B AND 1 << #B_LRIGHT) != 0 then
    ' 右を押されたらX座標を加算
    SPVAR 0,"X", SPVAR(0,"X")+1
  endif

  ' Yボタン処理
  if (B AND 1 << #B_LREFT) != 0 then
    ' ボタンカウント命令に#B_RLEFTを渡す
    D_BtnPressCount #B_RLEFT
    ' Yボタンのカウントが1であれば
    if G_BtnPressCount[2] == 1 then
      D_PlayerShot
    endif
  else
    ' ボタンが離されたらカウントが1以上の時
    if G_BtnPressCount[2] >= 1 then
      ' ボタンカウントを0にする
      G_BtnPressCount[2] = 0
    endif
  endif
end

' ボタンカウント関数
'───────────────────────────────
def D_BtnPressCount A_Button
  ' ボタンカウント分岐
  case A_Button
    ' 渡されたボタンが#B_RLEFTだったら
    when #B_RLEFT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if G_BtnPressCount[2] < 256 then 
        ' 条件に一致したら+1する
        INC G_BtnPressCount[2]
      endif
  endcase
end


' スプライトの初期化
'───────────────────────────────
def D_SpriteInitialize
  ' スプライトを定義
  var PID = G_SpvManageNo[0]
  SPSET PID, 352 ' プレイヤー
  SPVAR PID, "X", 0
  SPVAR PID, "Y", 0
  SPCOL PID

  ' ループさせて弾を生成
  for G_I = G_SpvManageNo[1] to G_SpvManageNo[2]
    SPSET G_I, 1322,0 ' 弾は最初からHIDE状態
    D_ShotPosSettings G_I
    SPCOL G_I
  next

  ' ループさせて障害物を生成
  for G_I = G_SpvManageNo[3] to G_SpvManageNo[4]
    SPSET G_I, 262 ' 岩
    D_ObstacleRndPosSettings G_I
    SPCOL G_I, 1
  next
end

' スプライト(プレイヤー)の移動
'───────────────────────────────
def D_PlayerMove
  ' プレイヤーの移動
  SPOPS 0, SPVAR(0,"X"), SPVAR(0,"Y")
end

' マップの描写
'───────────────────────────────
' マップの塗りつぶし描写
def D_MapDraw
  ' 背景の色味を変更
  TCOLOR 0, &Hdddddd00
  ' 背景の塗りつぶし
  TFILL 0,0,0,24,14,CHR$(&HE8D8)
end

' プレイヤーショット処理
'───────────────────────────────
def D_PlayerShot
  for G_I=G_SpvManageNo[1], to G_SpvManageNo[2]
    ' 弾にショットフラグが立っていなければ
    if SPVAR(G_I,"SHOT_FLAG") == 0 then
      BEEP 10
      ' 表示フラグをONにする
      SPSHOW G_I
      ' 出現フラグをONにする
      SPVAR G_I,"SHOT_FLAG", 1
      ' 弾の座標をプレイヤーの前方に設定する
      SPVAR G_I, "X", SPVAR(0,"X") + 8
      SPVAR G_I, "Y", SPVAR(0,"Y") + 4
      ' ショット命令が1回動いたら無条件でループを抜ける
      break
  next
end

' プレイヤーの弾の移動処理
'───────────────────────────────
def D_PlayerShotMove
  for G_I=G_SpvManageNo[1], to G_SpvManageNo[2]
    ' 弾にショットフラグが立っていれば
    if SPVAR(G_I,"SHOT_FLAG") == 1 then
      ' 弾のX座標を加算
      SPVAR G_I, "X", SPVAR(G_I,"X") + 8
      ' 弾を移動させる
      SPOFS G_I, SPVAR(G_I,"X"), SPVAR(G_I,"Y")
      ' 移動した後に衝突判定
      if D_ShotColCheck(G_I) == -1 then
        ' 弾の座標が400を越えたら
        if G_I, SPVAR(G_I,"X") > 400 then
          ' 画面外に行くのでショットフラグを0にして非表示にする
          SPVAR G_I,"SHOT_FLAG",0
          SPHIDE G_I
        endif
      endif
    endif
  next
end

' 岩の移動処理
'───────────────────────────────
def D_ObstacleMove
  for G_I=G_SpvManageNo[3], to G_SpvManageNo[4]
    ' 完全に画面左外でなければ
    if SPVAR(G_I,"X") > -16 then
      ' 岩のX座標を加算
      SPVAR G_I, "X", SPVAR(G_I,"X") - 5
      ' 岩を移動させる
      SPOFS G_I, SPVAR(G_I,"X"), SPVAR(G_I,"Y")
    else
      ' 画面左外に行ったら右画面外にランダム再配置する
      D_ObstacleRndPosSettings G_I
    endif
  next
end

' 障害物再配置
'───────────────────────────────
def D_ObstacleRndPosSettings A_I
  VAR X = 400 + RND(400)
  VAR Y = RND(225)
  SPVAR A_I, "X", X
  SPVAR A_I, "Y", Y
  SPVAR A_I, "IS_HIT", 0
  SPOFS A_I, SPVAR(A_I,"X"), SPVAR(A_I,"Y")
  SPSHOW A_I
end

' プレイヤー衝突チェック
'───────────────────────────────
def D_PlayerColCheck
  var isHit = SPHITSP(G_SpvManageNo[0], G_SpvManageNo[3], G_SpvManageNo[4])

  if isHit != -1 && SPVAR(isHit,"IS_HIT") == 0 then
    BEEP 91, -300
    SPVAR isHit, "IS_HIT", 1
  endif
end

' 弾衝突チェック
'───────────────────────────────
def D_ShotColCheck(A_I)

  var isHit = SPHITSP(A_I, G_SpvManageNo[3], G_SpvManageNo[4])

  if isHit != -1 && SPVAR(isHit,"IS_HIT") == 0 then
    BEEP 120
    SPVAR isHit, "IS_HIT", 1
    D_ShotPosSettings A_I
    SPHIDE isHit
    return 0
  else
    return -1
  endif
end

' 弾リセット処理
'───────────────────────────────
def D_ShotPosSettings A_I
  SPOFS A_I, -16, -16
  SPVAR A_I, "X", 0
  SPVAR A_I, "Y", 0
  SPVAR A_I, "SHOT_FLAG", 0
end