【プチコン4講座】イベントシステム構築:Aボタンで調べる実装

プチコン4

プチコン4 Aボタン処理

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

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

前回「【プチコン4講座】イベントシステム構築:配置と衝突判定の実装」でイベントを指定位置に並べて宝箱も配置しました。

このままではただの置物なので、Aボタンで調べられるようにしましょう。

手始めにAボタンを押すとプレイヤーの向いている方向のイベントレイヤーにアクセス出来るようにして、
それからボタンの制御方法を学んでいきます。

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

  1. Aボタンを押せる機能をコントローラー関数に追加
  2. Aボタンを押してイベントレイヤーのグリッドにアクセス
  3. 1回だけ反応するボタンの制御方法を学ぼう

Aボタンを押せる機能をコントローラー関数に追加

現在は十字キー(SWITCHだと十字ボタンになるのかな?)の操作しかないので、
ゲームらしくAボタンを押せるようにしてみましょう。

1つ実装すれば後はBだろうがXだろうがYだろうがどのボタンでも実装できるようになります。

早速コントローラー関数を改良しましょう。

' コントローラー関数
def D_Controller()
  var B = BUTTON(0)

  ' #B_RRIGHTは右コントローラーの右ボタンつまりAボタンの意
  var BTN_UP = 1 << #B_LUP
  var BTN_DOWN = 1 << #B_LDOWN
  var BTN_LEFT = 1 << #B_LLEFT
  var BTN_RIGHT = 1 << #B_LRIGHT
  var BTN_A = 1 << #B_RRIGHT

  var direct = -1

  if (B and BTN_UP) > O then
    direct = #B_RUP
  endif
  if (B AND BTN_DOWN) > O then
    direct = #B_RDOWN
  endif
  if (B AND BTN_LEFT) > O then
    direct = #B_RLEFT
  endif
  if (B AND BTN_RIGHT) > O then
    direct = #B_RRIGHT
  endif

  if (B AND BTN_A) > O then
    print "Aボタンが押されたよ"
  endif

  return direct

end

これでAボタンを認識できるようになりました。

実際に動かして試してみましょう。

プチコン4表示結果その1

1回押しただけでログがいっぱいでてきたのではないでしょうか?

移動中の十字キーならいいですが、Aボタンがこれだと色々と誤作動を引き起こしそうです。

これを制御する必要があるのですが、後ほど書きます。

とりあえずAボタンが動いたというところに喜びましょう!

Aボタンを押してイベントレイヤーのグリッドにアクセス

既に前方のグリッドにアクセスする処理は作ったことがありますね。

それを流用してAボタンを押したときに前方グリッドの配列にアクセスをして値を取ってくるというものです。

イベントレイヤーにはイベントIDが書き込まれているはずなので、
コウモリを調べたら1、近くの宝箱を調べたら7がかえってきます。

とりあえずアクセスしてみましょう。

' 前方のイベントをチェックする
def D_EvCheck A_Id, A_Direct

  var pos = D_GetMapPosition(SPVAR(A_Id,"X"), SPVAR(A_Id,"Y"), #LAYER_EV)
  var afterPos = pos + D_GetGridDirectPoint(A_Direct)

  var evId = G_MapData[afterPos]

  case evId
    when 0
      print "なにも ない よ!"
    when 1
      print "モンスター コウモリ が いるよ!"
    when 7
      print "たからばこ が あるよ!"
  endcase
end

このイベントチェック関数にIDと方向を指定すればそのIDの前方の配列の値を取得して、
その値に応じて分岐させるという仕組みです。

これをAボタンが押された時に呼び出すようにしてみましょう。

' コントローラー関数
def D_Controller()

  ~~~ 省略 ~~~

  if (B AND BTN_A) > O then
    D_EvCheck 0, SPVAR(0,"Direct")
  endif

  return direct

end

これで実行して

  • なにもないところ
  • 宝箱の前
  • コウモリの前

それぞれでAボタンを押してみてください。

プチコン4表示結果その2

対応したログが画面に表示されたら調べる処理は完成です。

しかしこのままだとボタンが連続で押されてしまっている状態です。

1回押したときに1回発動のみに制限しないと色々と不便です。

1回だけ反応するボタンの制御方法を学ぼう

ボタンを制すものはゲームを制す。

と言っても過言ではない程入力による制御はゲームにとって重要です。

今回学ぶ制御方法は1回しか動かない処理を作ります。

ネタバレするとボタンカウントを実装してカウントが1の時に処理をするという方法ですね。

これを実装しておかないとメッセージが出た時に連打状態になって1回押しただけでメッセージ送りが発動し
読むのが困難になりゲームとして欠陥品となってしまいます。

なので今後のためにもボタンカウントを実装しておきましょう。

’ ボタンカウント配列
G_BtnPressCount[4]


' コントローラー関数
def D_Controller()
  ~~~ 省略 ~~~

  if (B AND BTN_A) > O then
    D_BtnPressCount #B_RRIGHT
    if G_BtnPressCount[3] == 1 then
      D_EvCheck 0, SPVAR(0,"Direct")
    endif
  else
    if G_BtnPressCount[3] >= 1 then
       G_BtnPressCount[3] = 0
    endif
  endif

  return direct

end

' ボタンカウント関数
def D_BtnPressCount A_Button
  ' ボタンカウント分岐
  case A_Button
    when #B_RRIGHT
      if G_BtnPressCount[3] < 256 then 
        INC G_BtnPressCount[3]
      endif
  endcase
end

プチコン4表示結果その3

ボタンを押されている時ににカウント関数を呼びます。
そして、離された時にカウントをリセットします。

後は制御したいボタン毎に処理を増やしていきます。

これはもうちょっとスマートに書けるような気がしますね。

増えて来たら検討してみましょう。

これで実行してみると調べても1回だけしかログが出ません。

ボタンカウントはメッセージイベントを作るときにも使うので覚えておいてください。

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

それでは

'──────────────────────────
' ▼ 動作モード設定:変数宣言は必須
'──────────────────────────
OPTION STRICT
'──────────────────────────
' ▼ 画面のクリア
'──────────────────────────
ACLS
'──────────────────────────
' ▼ 定数・構造体の定義
'──────────────────────────
CONST #CSZ = 16
ENUM #LAYER_MAP1=0,#LAYER_MAP2,#LAYER_EV

'──────────────────────────
' ▼ 変数・配列の定義
'──────────────────────────
' ┠─ 汎用変数
'━━━━━━━━━━━━━━━━━━━━━━━━━━
var G_I = 0, G_J = 0, ¥
G_BGW = 0, G_BGH = 0
' ゲームループを制御するフラグ
var G_GameLoopFlag = #TRUE
'──────────────────────────
' ┠─ マップシーン用配列
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' 空配列宣言
DIM G_MapData[]
'──────────────────────────
' ┠─ スプライト用配列
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' アニメーション開始スプライト定義No配列
var G_SpEvStep = 4
DIM G_SpAnimNo[] = [500,1040,920,1000,980,1080,1020,269,269,269,269]
DIM G_EvId[] = [0,1,0,0,\
  1,5,5,0,\
  2,4,11,0,\
  3,23,13,0,\
  4,13,3,0,\
  5,8,2,0,\
  6,22,4,0,\
  7,6,7,1,\
  8,21,9,1,\
  9,3,11,1,\
  10,22,2,1\
  ]
'──────────────────────────
' ┠─ コントローラー用配列
'━━━━━━━━━━━━━━━━━━━━━━━━━━
’ ボタンカウント配列
G_BtnPressCount[4]

'──────────────────────────
' ▼ プログラム初期化処理
'──────────────────────────
D_GAME_INITIALIZE
'──────────────────────────
' ゲームループ開始
'──────────────────────────
loop
  ' コントローラー関数呼び出し
  D_SpriteGridMove 0, D_Controller()

  ' ゲームループフラグ監視
  if !G_GameLoopFlag then BREAK
  ' フレームレート安定
  VSYNC 1
endloop

'──────────────────────────
' ▼ 関数の定義
'──────────────────────────
' ┠─ ▼ 初期化関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
DEF D_GameInitialize
  ' マップデータのロード
  D_MapInitialize "MAP001.DAT"
  ' マップデータの描写
  'D_MapDraw
  ' スプライトの初期化
  for G_I=0 to LEN(G_EvId)-1 step G_SpEvStep
    D_SpInitialize G_EvId[G_I]
  next
END

'──────────────────────────
' ┗━┓ ▼ マップ関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
DEF D_MAP_INITIALIZE A_FILENAME$
  ' マップデータファイル読み込み
  G_MAPDATA = LOADV(A_FILENAME$)

  ' マップデータ配列の先頭2つを取り出す
  G_BGW = SHIFT(G_MapData)
  G_BGH = SHIFT(G_MapData)
END
'──────────────────────────
'   ┗━┳━ ▼ マップデータ描写
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' マップデータの描写関数
DEF D_MapDraw
  ' マップチップ配列を描写
  for G_J = 0 to G_BGH-1
    for G_I = 0 to G_BGW-1
      ' ローカル変数CPに計算した値を入れる 
      var cp = G_I + ( G_J * G_BGW )
      ' CASE文でMAPDATA[CP]の内容に応じて処理を切り替える
      case G_MapDat[cp]
        ' 0のとき
        when 0:
          ' 地面を表示
          TPUT 0, G_I, G_J, CHR(&HE8C9)
        ' 1のとき
        when 1:
          ' 木を表示
          TPUT 0, G_I, G_J, CHR(&HE8CA)
      endcase
    next
  next
end

'──────────────────────────
' ┠─ コントローラー関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' コントローラー関数
def D_Controller()
  var B = BUTTON(0)

  ' #B_RRIGHTは右コントローラーの右ボタンつまりAボタンの意
  var BTN_UP = 1 << #B_LUP
  var BTN_DOWN = 1 << #B_LDOWN
  var BTN_LEFT = 1 << #B_LLEFT
  var BTN_RIGHT = 1 << #B_LRIGHT
  var BTN_A = 1 << #B_RRIGHT

  var direct = -1

  if (B and BTN_UP) > O then
    direct = #B_RUP
  endif
  if  (B AND BTN_DOWN) > O then
    direct = #B_RDOWN
  endif
  if  (B AND BTN_LEFT) > O then
    direct = #B_RLEFT
  endif
  if  (B AND BTN_RIGHT) > O then
    direct = #B_RRIGHT
  endif

  if (B AND BTN_A) > O then
    D_BtnPressCount #B_RRIGHT
    if G_BtnPressCount[3] == 1 then
      D_EvCheck 0, SPVAR(0,"Direct")
    endif
  else
    if G_BtnPressCount[3] >= 1 then
       G_BtnPressCount[3] = 0
    endif
  endif

  return direct

end


' ボタンカウント関数
def D_BtnPressCount A_Button
  ' ボタンカウント分岐
  case A_Button
    when #B_RRIGHT
      if G_BtnPressCount[3] < 256 then 
        INC G_BtnPressCount[3]
      endif
  endcase
end

'──────────────────────────
' ┠─ ▼ スプライト関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' スプライト初期化
def D_SpInitialize A_Id
  SPSET A_Id, 500
  SPVAR A_Id, "X", G_EvId[G_SpEvStep * A_Id + 1] * #CSZ
  SPVAR A_Id, "Y", G_EvId[G_SpEvStep * A_Id + 2] * #CSZ
  SPVAR A_Id, "Direct", -1
  SPVAR A_Id, "MoveFlag", 0
  SPOFS A_Id, SPVAR(A_Id,"X"), SPVAR(A_Id,"Y")

  D_MapArrayReWrite A_Id, SPVAR(A_Id, "Direct"), 1, #LAYER_EV

  if G_EvId[G_SpEvStep * A_Id + 3] != -1 then
    SPANIM A_Id, "I",\
    20,spAnimNo[A_Id],\
    20,spAnimNo[A_Id]+1,\
    20,spAnimNo[A_Id]+2,\
    20,spAnimNo[A_Id]+3,\
    G_EvId[G_SpEvStep * A_Id + 3]
  endif
end

' スプライトの移動
def D_SpriteGridMove A_Id, A_Direct

  if SPVAR(A_Id,"MoveFlag") == 1 then 
    case SPVAR(A_Id,"Direct")
    when #B_RUP
      SPVAR A_Id, "Y", SPVAR(A_Id, "Y") - 1
    when #B_RDOWN
      SPVAR A_Id, "Y", SPVAR(A_Id, "Y") + 1
    when #B_RLEFT
      SPVAR A_Id, "X", SPVAR(A_Id, "X") - 1
    when #B_RRIGHT
      SPVAR A_Id, "X", SPVAR(A_Id, "X") + 1
    endcase

    ' キャラクターの移動
    SPOFS A_Id, SPVAR(A_Id,"X"), SPVAR(A_Id,"Y")

    ' 割った余りが0になったら動きを止める
    if SPVAR(A_Id, "X") MOD #CSZ == 0 && SPVAR(A_Id, "Y") MOD #CSZ == 0 then
      SPVAR A_Id, "MoveFlag", 0
    endif
  else
    if A_Direct >= 0 then
      ' 現在のグリッド座標にアクセスするマップ配列添え字を取得
      var pos = D_GetMapPosition(SPVAR(A_Id, "X"), SPVAR(A_Id, "Y"), 0)
      ' 移動の可否関係なく向きは変更しておく
      SPVAR 0, "Direct", A_ Direct
      ' 向かう先が移動可能かどうかを調べる(MAPレイヤー&イベントレイヤー)
      if D_CheckCollision(pos, SPVAR(A_Id, "Direct")) == #TRUE then
        SPVAR A_Id, "MoveFlag", 1
      endif
    endif
  endif
end


'──────────────────────────
' ┠─ ▼ 衝突判定関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' ドット座標をグリッド座標に変換
def D_GetGridXY(A_XY)
  return A_XY / #CSZ
end

' グリッド座標とレイヤーを与えてマップ配列の値を取得する
def D_GetMapPosition(A_X, A_Y, A_LAYER)
  var gridX = D_GetGridXY(A_X)
  var gridX = D_GetGridXY(A_Y)

  VAR layerNo = (G_BGW * G_BGH) * A_LAYER

  IF A_Y <= 0 then
    return gridX + layerNo
  ELSE
    return gridX +  (GRIX_Y * G_BGW)) + layerNo
  ENDIF

END

' 進む先のグリッドが移動可能かどうかを調べる
DEF D_CheckCollision(A_Pos, A_Direct)
  var sumPos = 0
  var evPos = 0

  sumPos = D_GetMapArrayLayerNo(A_Pos, #LAYER_MAP1) + D_GetGridDirectPoint(A_Direct)
  evPos  = D_GetMapArrayLayerNo(A_Pos, #LAYER_EV) + D_GetGridDirectPoint(A_Direct)

  if G_MapData[sumPos] == 0 then
    if G_MapData[evPos] == 0 then
      return #TRUE
    endif
  endif
  return #FALSE 
END

' 方向定数から1歩前のグリッド座標を得るための値を得る
def D_GetGridDirectPoint(A_Direct)
  var direct = 0
  case A_Direct
    when #B_RUP
      direct = -G_BGW
    when #B_RDOWN
      direct = G_BGW
    when #B_RLEFT
      direct = -1
    when #B_RRIGHT
      direct = 1
  endcase
  return direct
end

' マップ配列添字生成
def D_GetMapArrayLayerNo(A_Pos,A_Layer)
  ' 与えられた位置に対してレイヤーを考慮した配列添字を返す
  return A_Pos + ((G_BGW * G_BGH) * A_Layer)
end

' マップ配列書き換え(レイヤー指定
def D_MapArrayReWrite A_Id, A_Direct, A_Flag, A_Layer
  var pos = D_GetMapPosition(SPVAR(A_Id,"X"), SPVAR(A_Id,"Y), A_Layer)

  if A_Flag > 0 then
    if A_Direct >= 0 then
      var afterPos = pos + D_GetGridDirectPoint(A_Direct)
      G_MapData[afterPos] = A_Id
    else
      G_MapData[pos] = A_Id
    endif
  else
    var beforePos = pos - D_GetGridDirectPoint(A_Direct)
    G_MapData[beforePos] = 0
  endif
end

'──────────────────────────
' ┠─ ▼ イベント判定関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' 前方のイベントをチェックする
def D_EvCheck A_Id, A_Direct

  var pos = D_GetMapPosition(SPVAR(A_Id,"X"), SPVAR(A_Id,"Y"), #LAYER_EV)
  var afterPos = pos + D_GetGridDirectPoint(A_Direct)

  var evId = G_MapData[afterPos]

  case evId
    when 0
      print "なにも ない よ!"
    when 1
      print "モンスター コウモリ が いるよ!"
    when 7
      print "たからばこ が あるよ!"
  endcase
end