【プチコン4講座】シーンという概念を学ぶ

プチコン4

プチコン4 シーン管理

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

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

前回「【プチコン4講座】イベントシステム構築:Aボタンで調べる実装」でイベントを調べてIDを取得することが出来ました

現状では調べたものが何かわかるようになったので、IDによってイベントを呼び出したいです。

そのためにはシーン(画面)という概念を学ぶ必要があります。

RPGをやったことのある人ならメニュー画面やバトル画面、メッセージ表示画面と言えばわかりやすいでしょうか。

今はマップ上でキャラを動かせる「マップ移動画面」というシーンだけ存在しています。

イベントを発動させるには「イベントシーン」を用意しなければいけません。

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

  1. シーンを切り替える準備をしよう
  2. シーンを切り替えてみよう

シーンを切り替える準備をしよう

シーンを切り替えるためには切り替えるためのフラグが必要になります。

しかしTRUEかFALSEだけでは2つのシーンしか作れません。

なので1つ変数を作成して数値でシーン管理する必要があります。

といっても特に難しいことをするわけではなく単純に値を保持する変数を作り、
case文を使って数値によって毎フレーム呼び出す関数を変更するだけです。

現状、ゲームループではスプライト移動関数にコントローラー関数の戻り値を渡して毎ループ動いています。

さらにイベントが動いているときはキャラクターを動かさないようにしたいので、
現状だと常に移動ができてしまうのでフラグによって制御する必要がありますね。

それではシーンフラグを作ってゲームループの中をフラグで分岐させてみましょう

G_SceneFlag = 0

'──────────────────────────
' ゲームループ開始
'──────────────────────────
loop
  'シーン管理呼び出し
  D_GameSceneManager
  ' ゲームループフラグ監視
  if !G_GameLoopFlag then BREAK
  ' フレームレート安定
  VSYNC 1
endloop

'──────────────────────────
' シーン管理
'──────────────────────────
def D_GameSceneManager
  var direct = D_Controller()
  case G_SceneFlag
    when 0
      ' マップシーン
      D_SpriteGridMove 0, direct
    when 1
      ' イベントシーン
      D_EventScene direct
  endcase
end

今後シーンが増えていくと想定してシーン管理関数を作成し、
こっちにシーンの分岐をわけることにしました。

これでG_SceneFlagの数値を切り替えればシーン変更することが可能になりました。

現状では「D_EventScene」関数を用意していないので作ってシーンを切り替えてみましょう。

シーンを切り替えてみよう

シーンを切り替える準備が出来たので、一旦ボタンで切り替えられるようにします。

Xボタンを押したらイベントシーンに。

Yボタンを押したらマップシーンに。

という感じでやればわかりやすいでしょう。

もっとわかりやすくするのであればシーンにprintを付けておけば現状どのシーンになっているかわかりやすいです。

' シーン管理
'──────────────────────────
def D_GameSceneManager
  var direct = D_Controller()
  case G_SceneFlag
    when 0
      ' マップシーン
      D_SpriteGridMove 0, direct
      print "マップシーン"
    when 1
      ' イベントシーン
      D_EventScene direct
      print "イベントシーン"
  endcase
end
'──────────────────────────
' ┠─ コントローラー関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' コントローラー関数
def D_Controller()
  var B = BUTTON(0)
  var direct = -1

  ' #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_X = 1 << #B_RUP
  var BTN_B = 1 << #B_RDOWN
  var BTN_Y = 1 << #B_RLEFT
  var BTN_A = 1 << #B_RRIGHT


  ' 方向を押されていたらその保持
  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

  ' Aボタン処理
  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

  ' Bボタン処理
  if (B AND BTN_B) > O then
    D_BtnPressCount #B_RDOWN
    if G_BtnPressCount[1] == 1 then
      print "Bが押されたよ"
    endif
  else
    if G_BtnPressCount[1] >= 1 then
       G_BtnPressCount[1] = 0
    endif
  endif

  ' Xボタン処理
  if (B AND BTN_X) > O then
    D_BtnPressCount #B_RUP
    if G_BtnPressCount[0] == 1 then
      print "Xが押されたよ"
      G_SceneFlag = 0
    endif
  else
    if G_BtnPressCount[0] >= 1 then
       G_BtnPressCount[0] = 0
    endif
  endif

  ' Yボタン処理
  if (B AND BTN_Y) > O then
    D_BtnPressCount #B_RLEFT
    if G_BtnPressCount[2] == 1 then
      print "Yが押されたよ"
      G_SceneFlag = 1
    endif
  else
    if G_BtnPressCount[2] >= 1 then
       G_BtnPressCount[2] = 0
    endif
  endif

  return direct
end

' ボタンカウント関数
def D_BtnPressCount A_Button
  if G_BtnPressCount[A_Button] < 256 then 
    INC G_BtnPressCount[A_Button]
  endif
end

ついでにBボタンも実装してみました。

ボタンカウントも修正です。

よく見たら定数渡せばメチャクチャシンプルに書けましたね。

Switchの場合見た目はABXYですが、プチコンだと右ボタンも左ボタンも
左の上下左右、右の上下左右という扱いになっています。

ここで少し混乱してしまいそうですが、慣れてしまえばどうということはありません。

順番を上下左右と覚えておけばいいです。

なので順番的にX→B→Y→Aとなりますね。

私は古い人間なのでABXYの順でコードを書きましたが(笑)

ちゃんと意図したとおりに動いてくれれば記述する順番は問題ないです。

なんか見ただけで冗長と分かるコードですよねこれ。

上下左右はこれ以上まとめようがないとしてABXYはかなり省略できそうですので、
関数として切り分けてみましょう。

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

  ' 十字キー監視
  direct = D_LeftButtonProcess(B, #B_LUP, direct)
  direct = D_LeftButtonProcess(B, #B_LDOWN, direct)
  direct = D_LeftButtonProcess(B, #B_LLEFT, direct)
  direct = D_LeftButtonProcess(B, #B_LRIGHT, direct)

  ' ABXY監視
  D_RightButtonProcess B, #B_RUP
  D_RightButtonProcess B, #B_RDOWN
  D_RightButtonProcess B, #B_RLEFT
  D_RightButtonProcess B, #B_RRIGHT

  return direct
end

' 十字キー処理
def D_LeftButtonProcess(A_B, A_Type, A_NowDirect)

  if A_NowDirect > -1 then
   return A_NowDirect
  endif

  var BtnPush = 0
  var direct = -1

  ' 方向を保持
  case A_Type
    when #B_LUP
      BtnPush = 1 << #B_LUP
      direct = #B_RUP
    when #B_LDOWN
      BtnPush = 1 << #B_LDOWN
      direct = #B_RDOWN
    when #B_LLEFT
      BtnPush = 1 << #B_LLEFT
      direct = #B_RLEFT
    when #B_LRIGHT
      BtnPush = 1 << #B_LRIGHT
      direct = #B_RRIGHT
  endcase

  ' 押されていたら方向を返し押されていなければ-1を返す
  if (B and BtnPush) > O then
    return direct
  else
    direct = -1
    return direct
  endif
end

' ABXYボタン処理
def D_RightButtonProcess A_B, A_Type
  var BtnPush = 0

  case A_Type
    when #B_RUP
      BtnPush = 1 << #B_RUP
    when #B_RDOWN
      BtnPush = 1 << #B_RDOWN
    when #B_RLEFT
      BtnPush = 1 << #B_RLEFT
    when #B_RRIGHT
      BtnPush = 1 << #B_RRIGHT
  endcase

  if (A_B AND BtnPush ) > O then
    D_BtnPressCount A_Type
    if G_BtnPressCount[A_Type] == 1 then
      case A_Type
        when #B_RUP
          G_SceneFlag = 1
        when #B_RDOWN
        when #B_RLEFT
          G_SceneFlag = 0
        when #B_RRIGHT
          D_EvCheck 0, SPVAR(0, "Direct")
      endcase
    endif
  else
    if G_BtnPressCount[A_Type] >= 1 then
       G_BtnPressCount[A_Type] = 0
    endif
  endif
end

def D_EventScene A_Direct
end

これでXを押せばイベントシーンに。

Yを押せばマップシーンに切り替えることが出来ます。

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

後はそれぞれのシーンに対して何をするかという処理を書けばよいですね。

バトルシーンやタイトルシーンを用意する時に分岐を増やすだけです。

──次回はイベントキューを作ります。

キューとはデータ構造です。

イメージとしては筒の中に次々と玉を入れて、
先に入れたものから取り出していく方法です。

キューの逆としてスタックというものがあります。

スタックは後から入れたものを先に取り出します。

イベントは基本的に先に登録したモノから実行したいのでキュー方式がいいですね。

RPGツクールをやったことがある人はイメージが付きやすいんじゃないでしょうか

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

それでは

'──────────────────────────
' ▼ 動作モード設定:変数宣言は必須
'──────────────────────────
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_GameSceneManager
  ' ゲームループフラグ監視
  if !G_GameLoopFlag then BREAK
  ' フレームレート安定
  VSYNC 1
endloop

'──────────────────────────
' シーン管理
'──────────────────────────
def D_GameSceneManager
  var direct = D_Controller()
  case G_SceneFlag
    when 0
      ' マップシーン
      D_SpriteGridMove 0, direct
      print "マップシーン"
    when 1
      ' イベントシーン
      D_EventScene direct
      print "イベントシーン"
  endcase
end


'──────────────────────────
' ▼ 関数の定義
'──────────────────────────
' ┠─ ▼ 初期化関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
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)
  var direct = -1

  ' 十字キー監視
  direct = D_LeftButtonProcess(B, #B_LUP, direct)
  direct = D_LeftButtonProcess(B, #B_LDOWN, direct)
  direct = D_LeftButtonProcess(B, #B_LLEFT, direct)
  direct = D_LeftButtonProcess(B, #B_LRIGHT, direct)

  ' ABXY監視
  D_RightButtonProcess B, #B_RUP
  D_RightButtonProcess B, #B_RDOWN
  D_RightButtonProcess B, #B_RLEFT
  D_RightButtonProcess B, #B_RRIGHT

  return direct
end

' 十字キー処理
def D_LeftButtonProcess(A_B, A_Type, A_NowDirect)

  if A_NowDirect > -1 then
   return A_NowDirect
  endif

  var BtnPush = 0
  var direct = -1

  ' 方向を保持
  case A_Type
    when #B_LUP
      BtnPush = 1 << #B_LUP
      direct = #B_RUP
    when #B_LDOWN
      BtnPush = 1 << #B_LDOWN
      direct = #B_RDOWN
    when #B_LLEFT
      BtnPush = 1 << #B_LLEFT
      direct = #B_RLEFT
    when #B_LRIGHT
      BtnPush = 1 << #B_LRIGHT
      direct = #B_RRIGHT
  endcase

  ' 押されていたら方向を返し押されていなければ-1を返す
  if (B and BtnPush) > O then
    return direct
  else
    direct = -1
    return direct
  endif
end

' ABXYボタン処理
def D_RightButtonProcess A_B, A_Type
  var BtnPush = 0

  case A_Type
    when #B_RUP
      BtnPush = 1 << #B_RUP
    when #B_RDOWN
      BtnPush = 1 << #B_RDOWN
    when #B_RLEFT
      BtnPush = 1 << #B_RLEFT
    when #B_RRIGHT
      BtnPush = 1 << #B_RRIGHT
  endcase

  if (A_B AND BtnPush ) > O then
    D_BtnPressCount A_Type
    if G_BtnPressCount[A_Type] == 1 then
      case A_Type
        when #B_RUP
          G_SceneFlag = 1
        when #B_RDOWN
          D_EvCheck 0, SPVAR(0, "Direct")
        when #B_RLEFT
          G_SceneFlag = 0
        when #B_RRIGHT
      endcase
    endif
  else
    if G_BtnPressCount[A_Type] >= 1 then
       G_BtnPressCount[A_Type] = 0
    endif
  endif
end

' ボタンカウント関数
def D_BtnPressCount A_Button
  if G_BtnPressCount[A_Button] < 256 then 
    INC G_BtnPressCount[A_Button]
  endif
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

'──────────────────────────
' ┠─ ▼ イベント処理関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
def D_EventScene A_Direct
end