【プチコン4講座】イベントシステム構築:メッセージウィンドウ前編

プチコン4

プチコン4 イベント メッセージウィンドウ

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

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

前回「【プチコン4講座】イベントシステム構築:イベント駆動処理」でイベントを駆動させる仕組みを作りました。

これでイベントを動かせるようになったので、宝箱イベント完成を目指しましょう。

宝箱イベントで画面に影響があるものは主にメッセージイベントのみです。

今回はそのメッセージを表示するためのウィンドウを作っていきます。

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

  1. ウィンドウの土台となる背景を矩形で表示してみよう
  2. ウィンドウフレームを作ってみよう
  3. メッセージウィンドウに文字を表示してみよう

ウィンドウの土台となる背景を矩形で表示してみよう

マップ画面の上に文字を表示しても見にくいので、文字を表示するための土台をまず作りましょう。

まずは凝ったウィンドウではなくシンプルなものから作りたいのでとりあえずGRAPHIC命令でベタ塗りをします。

グラフィックを描画する対象ページを決めてそこに描写していきます。

何も指定していないと対象ページが0にっているのですが、一応明示的に指定しておきましょう。

それからベタ塗り命令を行います。

GTARGET 0
GFILL 80, 150, 320, 208 RGB(0, 0, 0)

設置場所はゲーム初期化の後にしてみてください。

その後実行すると、ウィンドウが表示され……ませんね。

しかしグラフィック描画命令は行っています。

確認するために「D_GameInitialize」をコメントアウトして実行してみましょう

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

ちゃんと描画されています。

ということはマップチップの下に行ってしまっているということですね。

プチコンBIGの時はGPRIOで表示順番を変更出来たのですがプチコン4になってかなり仕様が変更されたみたいです。

解決法としてはテキストスクリーンのレイヤー優先度を下げてやればいけました。

ダイレクトモードで所属レイヤーをそれぞれ確認したら全部0が返ってきたので、
もしやと思ってテキストスクリーンのレイヤーを試しに4にしてみたら描画したグラフィックが前面にきてくれました。

しかしこのままだとスプライトよりも下に来てしまっているのでスプライト側も下に来てもらわないとダメです。

テキストスクリーンを4にしたのでスプライトは一旦3にしておきましょう。

スプライトに関してはスプライト毎に設定するので、SPSETをした後じゃないとエラーがでますので、
レイヤー設定は初期化の最後にしておきましょう。

グラフィックに関してはレイヤー設定が出来ずに描画ページの指定しかできません。

ページを切り替えちゃうと裏側に描写されているので表示されなくなります。

なのでとりあえず何か問題が出るまで0にしておきましょう。

'──────────────────────────
' ▼ プログラム初期化処理
'──────────────────────────
D_GAME_INITIALIZE
GFILL 80, 150, 320, 208 RGB(0, 0, 0)

'──────────────────────────
' ┠─ ▼ 初期化関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
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
  ' レイヤーのセッティング
  D_ScreenLayerSettings
end

' ▼ スクリーンレイヤー変更
'──────────────────────────
def D_ScreenLayerSettings
  TLAYER 0, 4
  for G_I = 0 to LEN(G_SpAnimNo)-1
    SPLAYER G_I, 3
  next
  GTARGET 0
end

これで実行すると理想の並びになったと思います。

スプライトより前面にグラフィックが表示されているかチェックするために表示位置をずらしてみましょう。

グラフィックの方が全面に来ていたら大丈夫です。

ウィンドウフレームを作ってみよう

ウィンドウベタ塗りだけでもいいですが、少し寂しいのでフレームを付けてあげましょう。

これも本来ならスプライトの方がオシャレなフレームが作れますが、
FF1風のフレームを作ってみましょう。

背景の上に乗せる感じなのでフレームは背景の後に指定してください。

GBOX 79, 149, 321, 209, RGB(128,126,129)
GBOX 78, 148, 322, 210, RGB(128,126,129)
GBOX 77, 147, 323, 211, RGB(128,126,129)

GPSET 78, 148,  RGB(255,255,255)
GPSET 78, 210,  RGB(255,255,255)
GPSET 322, 148,  RGB(255,255,255)
GPSET 322, 210,  RGB(255,255,255)

GLINE 77, 149, 77, 209, RGB(255,255,255)
GLINE 76, 149, 76, 209,  RGB(128,126,129)

GLINE 323, 149, 323, 209, RGB(255,255,255)
GLINE 324, 149, 324, 209,  RGB(128,126,129)

GLINE 79, 147, 321, 147, RGB(255,255,255)
GLINE 79, 146, 321, 146, RGB(128,126,129)

GLINE 79, 211, 321, 211, RGB(255,255,255)
GLINE 79, 212, 321, 212, RGB(128,126,129)

これはプチコンBIGで作ったものをそのまま使えました。

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

これでウィンドウぽくなりましたね。

メッセージウィンドウに文字を表示してみよう

ウィンドウの準備が出来たので、その上に文字を表示してみましょう。

画面に文字を出すのはPRINTや?を使いますが、基本的にこれらはデバッグ用に使うもので
ゲームを作るときにこれらは使いません。

ゲームに使うのは「GPUTCHR」という命令になります。

試しにプチコンBIGでやったものをそのまま打ち込んでみましょう。

GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!"
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!"

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

おお……もう……

ウィンドウはかき消されてるし、文字サイズが大きすぎますね。

プチコン4では文字サイズが16になっているので8にしてやる必要があります。

GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!",8
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!",8

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

サイズはいい感じになりましたが、背景が消えてしまっているままです。

同じレイヤーに表示しているので消えてしまうのでしょう

どうやら描画方法を変更すればいい感じでした。

GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!",8,RGB(255,255,255),1
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!",8,RGB(255,255,255),1

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

これで背景をかき消されずに描画することが出来ました。

──全部詰め込むと長くなるので前編後編にしました。

後編はウィンドウの開閉とイベントによるメッセージの呼び出しを実装します。

現状、初期化のところにコードを書きなぐっているのもブサイクなので関数化したいですね。

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

それでは

'──────────────────────────
' ▼ 動作モード設定:変数宣言は必須
'──────────────────────────
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\
  ]

' イベントキュー
'──────────────────────────
DIM G_EvQueue[]

' 各イベントデータ(ラベル管理
'──────────────────────────
@Event007
DATA 5,  10,10,0  3,0,1  2,1,0  1,1,0  1,2,1  
'──────────────────────────
' ┠─ コントローラー用配列
'━━━━━━━━━━━━━━━━━━━━━━━━━━
’ ボタンカウント配列
G_BtnPressCount[4]

'──────────────────────────
' ▼ プログラム初期化処理
'──────────────────────────
D_GAME_INITIALIZE
GFILL 80, 150, 320, 208 RGB(0, 0, 0)

GBOX 79, 149, 321, 209, RGB(128,126,129)
GBOX 78, 148, 322, 210, RGB(128,126,129)
GBOX 77, 147, 323, 211, RGB(128,126,129)

GPSET 78, 148,  RGB(255,255,255)
GPSET 78, 210,  RGB(255,255,255)
GPSET 322, 148,  RGB(255,255,255)
GPSET 322, 210,  RGB(255,255,255)

GLINE 77, 149, 77, 209, RGB(255,255,255)
GLINE 76, 149, 76, 209,  RGB(128,126,129)

GLINE 323, 149, 323, 209, RGB(255,255,255)
GLINE 324, 149, 324, 209,  RGB(128,126,129)

GLINE 79, 147, 321, 147, RGB(255,255,255)
GLINE 79, 146, 321, 146, RGB(128,126,129)

GLINE 79, 211, 321, 211, RGB(255,255,255)
GLINE 79, 212, 321, 212, RGB(128,126,129)

GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!",8,RGB(255,255,255),1
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!",8,RGB(255,255,255),1

'──────────────────────────
' ゲームループ開始
'──────────────────────────
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
      ' イベントキュー監視
      if LEN(G_EvQueue) > 0 then
        G_SceneFlag = 1
    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
  ' レイヤーのセッティング
  D_ScreenLayerSettings
end

' ▼ スクリーンレイヤー変更
'──────────────────────────
def D_ScreenLayerSettings
  TLAYER 0, 4
  for G_I = 0 to LEN(G_SpAnimNo)-1
    SPLAYER G_I, 3
  next
  GTARGET 0
end

'──────────────────────────
' ┗━┓ ▼ マップ関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
def D_MapInitialize 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
        when #B_RDOWN
        when #B_RLEFT
        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_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]

  D_GetEvData evId

end

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

' イベントキュー処理
'──────────────────────────
def D_ShiftEvData
  var type = SHIFT(G_EvQueue)
  var event = SHIFT(G_EvQueue)
  var reserve = SHIFT(G_EvQueue)

  print "[イベント かいし]"
  print "===================="
  case type
    when 1
      print "メッセージイベント:" + STR$(event) +", " + "詳細" + STR$(reserve) 
    when 2
      print "スプライトへんこうイベント:" + STR$(event) +", " + "詳細" + STR$(reserve) 
    when 3
      print "アイテムぞうげんイベント:" + STR$(event) +", " + "詳細" + STR$(reserve) 
    when 10
      print "SEえんそうイベント:" + STR$(event) +", " + "詳細" + STR$(reserve) 
  endcase

  print "-------------------"

  if LEN(G_EvQueue) <= 0 then
    G_SceneFlag = 0
  endif
end

' イベントデータ取り出してキューに格納
'──────────────────────────
def D_GetEvData A_Id
  var evCount = 0
  var evData = 0

  case A_Id
    when 7
      RESTORE @Event007
      READ evCount
      for G_I=1 to (evCount * 3)-1
        READ evData
        PUSH G_EvQueue, evData
      next
  endcase

  if LEN(G_EvQueue) > 0 then
    G_SceneFlag = 1
  endif
end