【プチコン4講座】モノを押して動かせるようにしよう

プチコン4

プチコン4 パズルゲーム モノを押して動かす

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

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

倉庫番ライクゲームを実際に作っていきましょう。

一番のメイン処理となるのはやはりモノを押して動かすという処理ですね。

今回作っていくのは壺を指定位置に動かすゲームなので壺を押せるようにしましょう。

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

  1. プレイヤーと壺の衝突判定を取ろう
  2. プレイヤーが上下左右どこから壺にぶつかったかを調べる
  3. 壺を動かせるかを調べて可能なら動かす
  4. 壺が壺にぶつかって動けない場合はプレイヤーも動かさない

プレイヤーと壺の衝突判定を取ろう

前回でSPCOLは既に実行済みなので、SPHITSPで衝突判定を取りましょう。

プレイヤーは管理番号が0で、壺は1-10でしたね。

範囲で衝突判定を取るのはもうお手の物なのではないでしょうか!

衝突判定を利用すればプレイヤーはどの壺に当たったかが分かるので、
動かすのも容易になりますね。

VAR HIT_OBJ = -1

LOOP
  ' スプライト管理番号0が1-10のどれかに当たれば取得
  HIT_OBJ = SPHITSP(0,1,10)

  ' ぶつかっていたらPRINTする
  IF HIT_OBJ != -1 THEN
    PRINT "↓にぶつかったよ"
    PRINT HIT_OBJ
  ENDIF
ENDLOOP

シンプルな書き方をするとこれで判定が取れますね。

一応ぶつかったスプライトの管理番号は保持しておきましょう。

保持していると何か機能を作ろうとしたとき便利になります。

プレイヤーが上下左右どこから壺にぶつかったかを調べる

横からぶつかって上に動いたり下に動いたりすると奇妙ですよね。

ちゃんと押した方向に壺も移動してほしいものです。

上下左右の判定は色んな方法があります。

どれを実装するかはお好みかもしれませんね。

一応パッと思いついた上下左右判定の取り方は下記になります。

  • プレイヤーの向いてる方向を調べて動かす
  • プレイヤーの進む前と進んだ後の座標を調べる
  • プレイヤーと壺の位置を調べてどこから押されたか調べる

一番簡単なのは1番目の「プレイヤーの向きを調べる」でしょうか。

スプライト変数を使えば向きの保持も容易です。

私はこの方法が最もシンプルだと思うので採用しますが、お好きな方法で大丈夫です。

もし迷ってる人は私と同じ方法で進めてください。

それではプレイヤーの向きと方向キーを押したときに向きを設定する処理を書きます。

' 向き情報を設定
SPVAR 0, "DIRECT", 0

' プレイヤー移動関数
DEF D_PLAYER_MOVE

  ' ボタンカウントに応じて向きを変更
  IF UP_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LUP
  ELSEIF DOWN_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LDOWN
  ELSEIF LEFT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LLEFT
  ELSEIF RIGHT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LRIGHT
  ENDIF

  ' ボタンカウントに応じて向きを変更
  CASE SPVAR (0, "DIRECT")
    WHEN #B_LUP:
      SPVAR 0, "Y" , SPVAR(0,"Y") - #CS
    WHEN #B_LDOWN:
      SPVAR 0, "Y" , SPVAR(0,"Y") + #CS
    WHEN #B_LLEFT:
      SPVAR 0, "X" , SPVAR(0,"X") - #CS
    WHEN #B_LRIGHT:
      SPVAR 0, "X" , SPVAR(0,"X") + #CS
  ENDCASE

  ' プレイヤーを移動
  SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
END

コントローラー関数の中にあるプレイヤーを移動させる処理ですが、
どんどん長くなってしまうので切り離してプレイヤー移動処理の関数として分離します。

関数の作り方の説明をしていませんでしたが、軽く説明すると
DEF~ENDで挟んで名前を付けられると言った感じです。

引数を与えられるしくみもあるのですが、新たに作ったときにまた説明します。

そして十字ボタンを押したら座標の加減算を行っていたのですが、
向きを変更するだけにしました。

というのも、ツボが壁に当たってたり自分自身が壁に当たってたりした場合
座標を加算してから衝突判定を行ったらまた減らさないといけないからです。

向きだけは動けなくても変わるので十字ボタンを押したら向きを変更するようにしました。

その後、自分の向きに応じて移動できるかといった感じですね。

今は条件式を組んでいないので向いた方向に移動するようになっています。

これでプレイヤーの向きを得ることが出来るようになったので
どこから壺にぶつかったが判断できるようになりました。

壺を動かせるかを調べて可能なら動かす

次に十字キーを押してプレイヤーの向きを変更した後、
何もなければプレイヤーだけ移動する。

もし壺にヒットしていたら、壺をプレイヤーの進行方向に動かせるかのチェックが必要になります。

判定するタイミングはプレイヤーの向きが決まった直後ですね。

それでは衝突判定を取って壺を動かせるかの処理を書きましょう。

  ' 仮移動用配列
  DIM ADD_POS[] = [0,0]

  ' ボタンカウントに応じて向きを変更
  IF UP_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LUP
    ADD_POS[1] = -#CS
  ELSEIF DOWN_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LDOWN
    ADD_POS[1] = #CS
  ELSEIF LEFT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LLEFT
    ADD_POS[0] = -#CS
  ELSEIF RIGHT_BTN == 1 THEN
    ADD_POS[0] = #CS
    SPVAR 0, "DIRECT", #B_LRIGHT
  ENDIF

  ' プレイヤーを移動させる
  SPVAR 0, "X", SPVAR(0,"X")+ADD_POS[0]
  SPVAR 0, "Y", SPVAR(0,"X")+ADD_POS[1]
  SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")

  ' プレイヤーと壺の衝突判定を取る
  HIT_OBJ = SPHITSP(0,1,10)

  ' プレイヤーが壺に当たっているかチェック
  IF HIT_OBJ != -1 THEN
    ' 当たっていれば壺を仮移動させる
    SPOFS HIT_OBJ, SPVAR(HIT_OBJ,"X")+ADD_POS[0], SPVAR(HIT_OBJ,"Y")+ADD_POS[1]

    ' 壺を仮移動させた先は壺かどうか
    IF SPHITSP(HIT_OBJ,1,10) != -1 THEN
      BEEP 52
      ' ぶつかってたら動かせないはずなので壺の座標を戻す
      SPOFS HIT_OBJ, SPVAR(HIT_OBJ,"X"), SPVAR(HIT_OBJ,"Y")
    ELSE
      BEEP 100
      ' 壺の移動できたので座標変数の変更を確定
      SPVAR HIT_OBJ, "X", SPVAR(HIT_OBJ,"X")+ADD_POS[0]
      SPVAR HIT_OBJ, "Y", SPVAR(HIT_OBJ,"Y")+ADD_POS[1]
    ENDIF
  ENDIF

今のところ壁が無いので壺の先に壺があったら動かせないようにしました。

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

移動させたときとぶつかったときの音を鳴らすようにしてわかりやすくしています。

壺が壺にぶつかって動けない場合はプレイヤーも動かさない

今のままだと壺が壺にぶつかったら動きませんが、プレイヤーはそのまま直進してしまいます。

なので壺が移動できなかった場合、プレイヤーの動きも止めなければいけません。

先ほどの壺判定の処理もまとめてプレイヤーの移動を改良してみましょう。

' プレイヤー移動関数
DEF D_PLAYER_MOVE

  ' 仮移動用配列
  DIM ADD_POS[] = [0,0]

  ' ボタンカウントに応じて向きを変更と仮移動数値を設定
  IF UP_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LUP
    ADD_POS[1] = -#CS
  ELSEIF DOWN_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LDOWN
    ADD_POS[1] = #CS
  ELSEIF LEFT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LLEFT
    ADD_POS[0] = -#CS
  ELSEIF RIGHT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LRIGHT
    ADD_POS[0] = #CS
  ENDIF

  ' プレイヤーを仮移動させる
  SPOFS 0, SPVAR(0,"X")+ADD_POS[0], SPVAR(0,"Y")+ADD_POS[1]

  ' 壺の衝突判定を取る
  HIT_OBJ = SPHITSP(0,1,10)

  ' 壺に当たっていて居るかチェック
  IF HIT_OBJ != -1 THEN
    ' 壺を仮移動させる
    SPOFS HIT_OBJ, SPVAR(HIT_OBJ,"X")+ADD_POS[0], SPVAR(HIT_OBJ,"Y")+ADD_POS[1]

    ' 壺を仮移動させた先は壺かどうか
    IF SPHITSP(HIT_OBJ,1,10) != -1 THEN
      BEEP 52
      ' ぶつかってたら動かせないはずなのでプレイヤーと壺の座標を戻す
      SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
      SPOFS HIT_OBJ, SPVAR(HIT_OBJ,"X"), SPVAR(HIT_OBJ,"Y")
    ELSE
      ' プレイヤーも壺も移動できたので座標変数の変更を確定
      SPVAR 0, "X", SPVAR(0,"X")+ADD_POS[0]
      SPVAR 0, "Y", SPVAR(0,"X")+ADD_POS[1]
      SPVAR HIT_OBJ, "X", SPVAR(HIT_OBJ,"X")+ADD_POS[0]
      SPVAR HIT_OBJ, "Y", SPVAR(HIT_OBJ,"Y")+ADD_POS[1]
    ENDIF
  ELSE
    ' 壺に当たっていなければプレイヤーは普通に移動するので座標変数を確定
    SPVAR 0, "X", SPVAR(0,"X")+ADD_POS[0]
    SPVAR 0, "Y", SPVAR(0,"X")+ADD_POS[1]
  ENDIF
END

プレイヤーも壺と同様仮移動をさせて動かせないなら元に戻して、
プレイヤー単独ないしプレイヤーと壺が両方動かせるならそれぞれの座標を仮移動の位置と同じにする。

といった感じです。

衝突判定を取るときのコツはまず仮移動させてぶつかるかどうか調べる

そしてぶつかって移動できなければ元の位置に戻しましょう。

具体的に言うとSPOFSで現在座標から動かす座標をプラスして、
ダメなら現在座標にSPOFSをすれば動いていないことになりますね。

これで倉庫番ライクゲームの基本的な動きは完成です!

後は背景と壁を作り、ダイヤの上に壺を乗せてクリア判定を作ればゲームとしては完成ですね。

次回は今回の復習も兼ねて壁を実装してみましょう。

単純に壁用スプライトを定義して部屋を作り、衝突判定を取り移動処理の部分に壁の処理も増やしてやればOKです。

次に行く前に自力で壁を実装出来たらあなたのゲームクリエイタースキルはメキメキ上昇しているということです!

もしわからない人は素直に次に行きましょう!

今わからなくても後から分かれば大丈夫です!

最後に今回の完成版ソースコードを置いておきます。

それでは

' 画面クリア
ACLS

' チップサイズ定数
CONST #CS = 16

' ゴールと木箱の個数
VAR OBJ = 5

' ループ用変数
VAR G_I = 0

' ボタンカウント用変数
VAR UP_BTN = 0, DOWN_BTN = 0, LEFT_BTN = 0, RIGHT_BTN = 0

' 衝突判定用変数
VAR HIT_OBJ = -1

' プレイヤースプライトの準備
SPSET 0, 721
' スプライト管理番号0番目に「X」という名前の変数を宣言し、0を代入する
SPVAR 0, "X", 0
' スプライト管理番号0番目に「Y」という名前の変数を宣言し、0を代入する
SPVAR 0, "Y", 0
' スプライトの向き情報を設定
SPVAR 0, "DIRECT", 0
' スプライト変数の呼び出しはカッコを使って呼び出したいスプライト管理番号と変数名を指定する
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
' スプライト衝突判定の準備
SPCOL 0

' 壺スプライトの準備
FOR G_I = 1 TO OBJ
  SPSET G_I, 265
  SPVAR G_I, "X", G_I * #CS
  SPVAR G_I, "Y", 0
  SPCOL G_I
  SPOFS 0, SPVAR(G_I,"X"), SPVAR(G_I,"Y")
NEXT

' 壺を置くための目印スプライト準備
FOR G_I = 11 TO OBJ+10
  SPSET G_I, 224
  SPVAR G_I, "X", G_I * #CS
  SPVAR G_I, "Y", 0
  SPCOL G_I
  SPOFS 0, SPVAR(G_I,"X"), SPVAR(G_I,"Y")
NEXT

' ループ開始
LOOP
  D_Controller
  VSYNC
ENDLOOP


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

  ' 上ボタン処理
  if (B AND 1 << #B_LUP) != 0 then
    D_BtnPressCount #B_LUP
  else
    if UP_BTN >= 1 then
      UP_BTN = 0
    endif
  endif

  ' 下ボタン処理
  if (B AND 1 << #B_LDOWN) != 0 then
    D_BtnPressCount #B_LDOWN
  else
    if DOWN_BTN >= 1 then
      DOWN_BTN = 0
    endif
  endif

  ' 左ボタン処理
  if (B AND 1 << #B_LLEFT) != 0 then
    D_BtnPressCount #B_LLEFT
  else
    if LEFT_BTN >= 1 then
      LEFT_BTN = 0
    endif
  endif

  ' 右ボタン処理
  if (B AND 1 << #B_LRIGHT) != 0 then
    D_BtnPressCount #B_LRIGHT
  else
    if RIGHT_BTN >= 1 then
      RIGHT_BTN = 0
    endif
  endif

  ' プレイヤー移動
  D_PLAYER_MOVE

end

' プレイヤー移動関数
'───────────────────────────────
DEF D_PLAYER_MOVE

  ' 仮移動用配列
  DIM ADD_POS[] = [0,0]

  ' ボタンカウントに応じて向きを変更と仮移動数値を設定
  IF UP_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LUP
    ADD_POS[1] = -#CS
  ELSEIF DOWN_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LDOWN
    ADD_POS[1] = #CS
  ELSEIF LEFT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LLEFT
    ADD_POS[0] = -#CS
  ELSEIF RIGHT_BTN == 1 THEN
    SPVAR 0, "DIRECT", #B_LRIGHT
    ADD_POS[0] = #CS
  ENDIF

  ' プレイヤーを仮移動させる
  SPOFS 0, SPVAR(0,"X")+ADD_POS[0], SPVAR(0,"Y")+ADD_POS[1]

  ' 壺の衝突判定を取る
  HIT_OBJ = SPHITSP(0,1,10)

  ' 壺に当たっていて居るかチェック
  IF HIT_OBJ != -1 THEN
    ' 壺を仮移動させる
    SPOFS HIT_OBJ, SPVAR(HIT_OBJ,"X")+ADD_POS[0], SPVAR(HIT_OBJ,"Y")+ADD_POS[1]

    ' 壺を仮移動させた先は壺かどうか
    IF SPHITSP(HIT_OBJ,1,10) != -1 THEN
      BEEP 52
      ' ぶつかってたら動かせないはずなのでプレイヤーと壺の座標を戻す
      SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
      SPOFS HIT_OBJ, SPVAR(HIT_OBJ,"X"), SPVAR(HIT_OBJ,"Y")
    ELSE
      ' プレイヤーも壺も移動できたので座標変数の変更を確定
      SPVAR 0, "X", SPVAR(0,"X")+ADD_POS[0]
      SPVAR 0, "Y", SPVAR(0,"X")+ADD_POS[1]
      SPVAR HIT_OBJ, "X", SPVAR(HIT_OBJ,"X")+ADD_POS[0]
      SPVAR HIT_OBJ, "Y", SPVAR(HIT_OBJ,"Y")+ADD_POS[1]
    ENDIF
  ELSE
    ' 壺に当たっていなければプレイヤーは普通に移動するので座標変数を確定
    SPVAR 0, "X", SPVAR(0,"X")+ADD_POS[0]
    SPVAR 0, "Y", SPVAR(0,"X")+ADD_POS[1]
  ENDIF
END


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

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

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

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

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