【プチコン4講座】スプライトに衝突判定を実装しよう

プチコン4

プチコン4 スプライト 衝突判定

こんにちは。なおキーヌです。

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

スプライトを出して動かすのを2回連続でやったのでしっかりと覚えてもらったと思います。

今回はスプライトに衝突判定(当たり判定)を付けてみましょう。

1から衝突判定を自作することも出来ますがプチコン4には簡単に衝突判定を設定できるように準備されているので、
今回は用意されたモノを使ってパパっと当たり判定を付けてみましょう。

それではプチコンミニゲーム講座第7回目を始めようと思います。

  1. SPCOLで衝突判定を付ける準備をしよう
  2. ココまでのコードをまとめてみよう
  3. SPHITを使って敵にぶつかったらGAMEOVERにしてみよう
  4. 衝突判定は奥が深い

SPCOL衝突判定を付ける準備をしよう

衝突判定の機能を使うには、最初に準備が必要です。

また準備か!準備ばっかりだな!

っておもうかもしれませんがプログラミングなんてほとんどが準備で観念しましょう(笑)

それでは早速スプライトの衝突判定を有効にするコードを書きます。

もちろんスプライトは宣言済みでなければなりません。


' スプライト準備
SPSET 0, 500
SPSET 1, 1000

' スプライトの衝突判定の準備
SPCOL 0
SPCOL 1

これは例ですが、プレイヤーと敵に衝突判定のをしています。

SPCOLは衝突判定を設定するための命令で、スプライトの番号を与えると衝突判定の準備が完了します。

調べてみるとどうやらココからココまでのスプライトNoにSPCOLを設定するという命令は無いようなので、
まとめてSPCOLを設定したい場合はFORなどのループを使って対処しましょう。

例えば今回の地面にSPCOLを設定する場合は以下のようになります。

' スプライト番号200-224までSPCOLの設定
for G_I=0 to 24
    SPCOL 200+G_I
next

ココで気を付けてほしいのはSPSETで準備をしていないエラーがでるので、
スプライト番号をSPCOLしないようにしましょう。

衝突判定は衝突判定としか分かり合えない

これですべてのスプライトにSPCOLを設定することが出来るようになりました。

なぜ全部に衝突判定の準備をしなければいけないのかというと、
衝突判定は衝突判定としか判定が出来ないので各自衝突判定を持っておかなければいけません。

どちらかだけ持っていても意味がないということですね。

ココまでのコードをまとめてみよう

現状、プレイヤーは16ドットずつ動くのでワープして見えますね。

まずはプレイヤーも敵と同じように滑らかに動けるようにしてみましょう。

ついでにJoyConのコントローラーの右側の操作になっているので左側(十字ボタン)に変更します。

ABXYのボタン処理は後で使うので残しておきましょう。(コードでは省略しています)

更に1回ボタンを押したら動かなくなるので十字ボタンは押してる間ずっと動かしたいですね。


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

  ' 上ボタン処理
  if (B AND 1 << #B_LUP) != 0 then
    DEC Y, 2
  ' 下ボタン処理
  elseif (B AND 1 << #B_LDOWN) != 0 then
    INC Y, 2
  ' 左ボタン処理
  elseif (B AND 1 << #B_LLEFT) != 0 then
    DEC X, 2
  ' 右ボタン処理
  elseif (B AND 1 << #B_LRIGHT) != 0 then
    INC X, 2
  endif

  'ABXYボタンコード省略

end

現状移動処理はボタン関数を呼び出して、その後のカウント数を見て
プレイヤーを移動処理させていましたが、面倒なのでD_Controller関数に直接移動処理を書きました。

なのでLOOPの部分に書いてたものは消せますね。

ちょっとコードが複雑になってきたので、
前回の処理も加えて一回ここまでのコードを1つにまとめてみましょう。

' 画面クリア
ACLS

' 座標用変数
VAR X=96, Y=96, EX=128, EY=96
' 向き変数 
VAR E_DIRECT = 0
' ボタン用変数
X_BTN = 0, B_BTN = 0, Y_BTN = 0, A_BTN = 0 
' ループ用変数
VAR G_I = 0

' スプライト準備
SPSET 0, 500
SPSET 1, 1000

for G_I=0 to 24
    SPSET 200+G_I 1244
    SPOFS 200+G_I 0+(16*G_I),116
next

' スプライトの衝突判定の準備
SPCOL 0
SPCOL 1
for G_I=0 to 24
    SPCOL 200+G_I
next

'----------------------------------
' ループ開始
LOOP

  ' コントローラー関数
  D_CONTROLLER

  ' スプライトを移動
  SPOFS 0, X,Y

  ' 敵の座標を調べて端っこに行ってたら向きを変える
  IF EX <= 0 THEN
      E_DIRECT = 1
  ELSEIF EX > 400-16 THEN
      E_DIRECT = 0
  ENDIF

  ' 敵の向きの方向へ進ませる
  IF E_DIRECT == 0 THEN
      DEC EX,1
  ELSE
      INC EX,1
  ENDIF

  ' プレイヤーを指定座標へ動かす
  SPOFS 0, X,Y
  ' 敵を指定座標へ動かす
  SPOFS 1, EX,EY

  ' 垂直同期
  VSYNC

ENDLOOP

' ループ終了
'----------------------------------


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


  ' 上ボタン処理
  if (B AND 1 << #B_LUP) != 0 then
    DEC Y, 2
  ' 下ボタン処理
  elseif (B AND 1 << #B_LDOWN) != 0 then
    INC Y, 2
  ' 左ボタン処理
  elseif (B AND 1 << #B_LLEFT) != 0 then
    DEC X, 2
  ' 右ボタン処理
  elseif (B AND 1 << #B_LRIGHT) != 0 then
    INC X, 2
  endif


  ' Xボタン処理
  if (B AND 1 << #B_RUP) != 0 then
    ' ボタンカウント命令に#B_RUPを渡す
    D_BtnPressCount #B_RUP
  else
    ' ボタンが離されたらカウントが1以上の時
    if X_BTN >= 1 then
      ' ボタンカウントを0にする
      X_BTN = 0
    endif
  endif

  ' Bボタン処理
  if (B AND 1 << #B_RDOWN) != 0 then
    ' ボタンカウント命令に#B_RDOWNを渡す
    D_BtnPressCount #B_RDOWN
  else
    ' ボタンが離されたらカウントが1以上の時
    if B_BTN >= 1 then
      ' ボタンカウントを0にする
      B_BTN = 0
    endif
  endif

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


  ' Aボタン処理
  if (B AND 1 << #B_RRIGHT) != 0 then
    ' ボタンカウント命令に#B_RRIGHTを渡す
    D_BtnPressCount #B_RRIGHT
  else
    ' ボタンが離されたらカウントが1以上の時
    if A_BTN >= 1 then
      ' ボタンカウントを0にする
      A_BTN = 0
    endif
  endif

end


' ボタンカウント関数
'───────────────────────────────
def D_BtnPressCount A_Button

  ' ボタンカウント分岐
  case A_Button
    ' 渡されたボタンがXだったら
    when #B_RUP
      ' ボタンカウント配列の数が256未満かどうか調べる
      if X_BTN < 256 then 
        ' 条件に一致したら+1する
        INC X_BTN
      endif

    ' 渡されたボタンがBだったら
    when #B_RDOWN
      ' ボタンカウント配列の数が256未満かどうか調べる
      if B_BTN < 256 then 
        ' 条件に一致したら+1する
        INC B_BTN
      endif

    ' 渡されたボタンがYだったら
    when #B_RLEFT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if Y_BTN < 256 then 
        ' 条件に一致したら+1する
        INC Y_BTN
      endif

    ' 渡されたボタンがAだったら
    when #B_RRIGHT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if A_BTN < 256 then 
        ' 条件に一致したら+1する
        INC A_BTN
      endif
  endcase
end

コントローラー関数だけで結構長いコードになってしまってますね。

しかしコントローラー関数は1回作っておけば今後の講座も使いまわせるので便利です(記事書く私も便利

SPHITを使って敵にぶつかったらGAMEOVERにしてみよう

衝突判定したいスプライトにSPCOLで衝突判定の準備をしたら実際にぶつからせてみましょう。

実際に衝突判定を行うには「SPHITSP」という関数を使います。

使い方は簡単で引数に衝突したいスプライト番号を2つ指定します。

例えばプレイヤーと敵を衝突判定させたい場合。

' 衝突したかどうかを格納する変数
VAR IS_HIT = 0

' 衝突判定をして当たっていれば1、当たってなければ0を返す
IS_HIT = SPHITSP(0,1)

これで衝突判定同士がぶつかっていればIS_HIT変数に1が入ります。

この時条件式にIS_HIT変数を利用すればいいですね。

それではプレイヤーが敵(がいこつ)にぶつかったらゲームループを抜けてゲームを強制終了してやりましょう。

先ほどの完成版コードに

  • IS_HIT変数の宣言
  • LOOP処理の記述

をしてください。

' 衝突したかどうかを格納する変数
VAR IS_HIT = 0

'----------------------------------
' ループ開始
LOOP

  ' コントローラー関数
  D_CONTROLLER

  ' スプライトを移動
  SPOFS 0, X,Y

  ' 敵の座標を調べて端っこに行ってたら向きを変える
  IF EX <= 0 THEN
      E_DIRECT = 1
  ELSEIF EX > 400-16 THEN
      E_DIRECT = 0
  ENDIF

  ' 敵の向きの方向へ進ませる
  IF E_DIRECT == 0 THEN
      DEC EX,1
  ELSE
      INC EX,1
  ENDIF

  ' 敵を指定座標へ動かす
  SPOFS 1, EX,EY

  ' 衝突判定チェック
  IS_HIT = SPHITSP(0,1)

  ' 衝突していたらループを抜ける
  IF IS_HIT == #TRUE THEN
    BREAK
  ENDIF

  ' 垂直同期
  VSYNC

ENDLOOP

' ループ終了
'----------------------------------

これで歩いているガイコツにぶつかったらゲームが止まるはずです。

プチコン4 出力結果その1

衝突判定は奥が深い」

衝突判定というのはゲーム制作においてとても重要度の高い仕組みです。

プチコン4はゲームを作るためのプログラミング用ソフトなので
簡単に衝突判定を実装できますが、普通のプログラミング言語だとこうも簡単にいきません。

ライブラリを使えば簡単だったりしますが自力で実装するとなると結構めんどうくさいのです。

今回のプチコン4の衝突判定は矩形(四角形)で判定を行っているので
画像の右上の空白部分に当たっても衝突したとみなされるので

あたってないだろそこ!ってところで衝突判定を取れてしまいます。

もうちょっと精度を上げたい場合はキャラクターの形に添った
衝突判定を作ったりするのですが、処理負荷が大きくなってしまうのでオススメできません。

弾幕系のシューティングゲームの場合、これ完全に当たってんだろって状態で
弾を避けられるのは画像よりも小さい丸型の判定にしていることが多いです。

逆に四角で判定を取っていると弾幕ゲーの場合は無理ゲーに近くなります。

今回作っているアクションゲームはそこまでシビアな判定は必要ないので、
このままデフォルトの衝突判定を使っていきます。

次回は地面にも判定を付けて動ける場所の制限をしてみましょう。

それでは。