【プチコン講座】メッセージウィンドウを出してみよう:後編

プチコン

プチコン メッセージウィンドウ

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

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

前回「【プチコン講座】メッセージウィンドウを出してみよう:前編」でメッセージウィンドウを出して文字を表示するところまでやりました。

メッセージウィンドウを出してみよう後編ではウィンドウの開閉を出来るようにすることと、
メッセージイベント発動したときにウィンドウを表示するようにしてみましょう。

メッセージ表示は他のジャンルのゲームにも使えるので仕組みを覚えておくと便利です。

それではプチコンでRPG作り第15回目を始めましょう。

  1. Xボタンでウィンドウを閉じたり開いたりしてみよう
  2. メッセージイベントが呼ばれたらウィンドウを開いてみよう
  3. メッセージ送りという処理についてい考えてみよう

Xボタンでウィンドウを閉じたり開いたりしてみよう

メニュー画面でもウィンドウを使うので、Xキーを押したときにウィンドウを開いたり閉じたりできるようにしておきましょう。

まずはAボタンと同じようにXボタンを操作できるようにして連続で押されないようにカウントを設けます。


  ~~~ 省略 ~~~

  # 移動中もボタンを押せるようにしておく
  IF G_BTN_PRESS_COUNT[4] == 1 THEN
    PRINT "Aボタンが押されたよ!"
    D_EV_CHECK G_SP_PLAYER SPVAR(G_SP_PLAYER, 3)
  ENDIF
  IF G_BTN_PRESS_COUNT[6] == 1 THEN
    PRINT "Xボタンが押されたよ!"
  ENDIF

  ~~~ 省略 ~~~


# ボタンカウント関数
DEF D_BTN_PRESS_COUNT
  VAR B = BUTTON()

  ~~~ 省略 ~~~

  # Xボタンカウント
  IF (B AND #X) > 0 THEN
    IF G_BTN_PRESS_COUNT[6] < 255 THEN 
      INC G_BTN_PRESS_COUNT[6]
    ENDIF
  ELSE
    # 1回でも離されたらカウントリセット
    G_BTN_PRESS_COUNT[6] = 0
  ENDIF

END

これでXボタンを押せるようになりました。

現状では最初からメッセージが表示されてしまっていますね。

一旦GPRIO以外のグラフィック命令を関数に退避させておきましょう。

DEF D_WINDOW_DRAW
  GFILL 80, 150, 320, 208 RGB(0, 21, 151)

  ~~~ 省略 ~~~
  # ウィンドウに文字を載せる
  GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!"
  GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!"

END

関数化出来たのでXボタンでこの関数を呼び出します。

一旦テストでAボタンを押したら消えるようにしたいのですが、
グラフィックを消すにはいくつか方法があります。

グラフィックは0,1,2と3ページ分使えます(4はスプライト用で5はバックグラウンド用)

0に描写すると表示され、表示を1にすると0に描写したものが隠れます。

これは「GPAGE」命令を使うことで表示するページと操作するページを選択できます。

他にも、描写したグラフィックを消す「GCLS」命令で消せます。

「GPRIO」で重なりを変更してもいいのですが、表示ページを変更すればいいだけなので却下ですね。

どれを使えばいいか。

まずメッセージイベントで考えてみましょう。

メッセージイベントが発動してウィンドウとメッセージが表示されたとして、次にAボタンを押したら

  • イベントが終了する
  • 続けて次のメッセージイベントが発動する

かのどちらかになると思います。

前者であればイベント終了になるのでGCLSを使って消してしまっても構いません。

後者の場合はどうするかを判断しなければいけません。

メッセージが連続して続く場合にも大きく分けて2パターンあります。

  • 同じウィンドウでメッセージが増えるタイプ
  • ウィンドウが一度消え、もう一度表示されるタイプ

前者であれば消す必要もなくそのまま文字追加でいけますね。

後者の場合は別ページに裏で表示しておいて順番が来たらページを切り替えるといった感じでいいでしょう。

しかし現代のゲーム機の場合処理が早いので別ページに切り替える必要もないくらい高速で処理してくれるので
一度GCLSで消してすぐに次のメッセージを表示しても問題ないように思えます。

この辺は実際にやってみて明らかに不自然な部分が見えて来たら修正でもいいかもしれませんね。

プチコン4になったらさらに処理速度はあがるので余計に気にしなくてもいいかもしれません。

話は戻りましてとりあえずXボタンで先ほど作った関数でウィンドウを開いて、
AボタンでGCLSを使ってウィンドウを消してみましょう。


  # 移動中もボタンを押せるようにしておく
  IF G_BTN_PRESS_COUNT[4] == 1 THEN
    PRINT "Aボタンが押されたよ!"
    D_EV_CHECK G_SP_PLAYER SPVAR(G_SP_PLAYER, 3)

    GCLS

  ENDIF
  IF G_BTN_PRESS_COUNT[6] == 1 THEN
    PRINT "Xボタンが押されたよ!"
    D_EV_CHECK G_SP_PLAYER SPVAR(G_SP_PLAYER, 3)

    D_WINDOW_DRAW

  ENDIF

これでウィンドウの出し入れができるようになりました。

メニュー表示なんかはこういう感じでやります。

実際には

  • Xボタンを押す
  • メニューシーンに切り替える
  • メニューが表示される
  • メニュー操作をする
  • Bボタンを押す
  • メニューを閉じる
  • シーンをマップに切り替える

といった感じです。

この辺はメニュー実装時にまたお話ししましょう。

メッセージイベントが呼ばれたらウィンドウを開いてみよう

それではメニューイベントが発動した時に、先ほど作った関数を呼び出してみましょう。

まだイベントで用意した文字を表示しなくても大丈夫です。

段階を踏んで改良していくのも、プログラミング成長の一環ですからね。

もし自信がある人は自力で実装してみても勉強になりますよ。

何度も言いますが、プログラミングの正解は1つじゃなく人の考えだけあります。

脳死で講座に沿ってやるのではなく、ある程度自分で考えられるようになったら自力でやった方が成長がぐんと早まります。

それではさっきAボタンとXボタンでウィンドウの開閉コードを書きましたが、宝箱を調べるために邪魔になるので一旦消してください。

そしてイベントキューをシフトする関数を下記のように書き換えます。

DEF D_SHIFT_EV_DATA
  VAR TYPE = SHIFT(G_EV_QUEUE)
  VAR EVENT = SHIFT(G_EV_QUEUE)
  VAR RESERVE = SHIFT(G_EV_QUEUE)

  PRINT "[イベント かいし]"
  PRINT "===================="

  IF TYPE == 1 THEN
    D_WINDOW_DRAW
  ELSEIF TYPE == 2 THEN
  ELSEIF TYPE == 3 THEN
  ELSEIF TYPE == 10 THEN
  ENDIF

  PRINT "-------------------"

  IF LEN(G_EV_QUEUE) <= 0 THEN
    G_SCENE_FLAG = 0
  ENDIF
END

今までは宝箱を調べたら処理の内容をPRINTで出していましたが、一旦消します。

そしてタイプ1、つまりメッセージイベントの時に「D_WINDOW_DRAW」関数を呼び出してください。

一旦実行して宝箱を調べてみてください。

ウィンドウが表示されますね。

ここで疑問に出てくるのが以下の通りです。

  • 結局イベントが全部取り出されるので一瞬でイベントが終了する
  • 指定した文しか表示されない

ですね。

それも当然で、まだメッセージ送りという処理を作っていないからです。

普通、村人との会話で話しかけた瞬間にイベントが終了するということはありません。

必ずメッセージウィンドウが出て閉じるまでプレイヤーは操作できなくなるはずです。

(近づいたら自動で喋るタイプはおいといて……)

今回のコーディングはここまでです。

メッセージ送りという処理についてい考えてみよう

最後にメッセージ送りという処理について考えていきます。

結論だけ先に言っておくと、シーンと同じようにフラグを作ってAボタンが押されるまでは次に進まないというループを作るのが正解です。

もう少し詳しく言うと、メッセージイベントが発動したときにフラグをONにします。

そしてそのフラグがONになっている時はイベントキューからは取り出さないようにしておき
Aボタンカウントが1になった時にフラグを解除するだけです。

意外と簡単ですね。

SEを鳴らしたりグラフィックを変えたりするときは止める必要はないので、
宝箱のイベントの場合以下のような流れになります。

    1. 宝箱を調べる
    1. SEが鳴る→開けたグラフィックにする→アイテムを増減しておく
    1. メッセージがでる
    1. Aボタンが押されるまで待つ

といった感じで1~3までは連続で実行され、4で止まるといった感じです。

フラグは流用できるように数値型にしておいたほうが良いでしょう。

例えば変数が

  • 0だとイベントキューを取り出せる状態。
  • 1だとメッセージ待機中(Aボタン押し待ち)
  • 2だとMEが鳴り終わるまで待機
  • 3だとバトル突入

みたいな感じで切り分けられます。

──明日はプチコン4の発売日ですね。

プチコン4までにRPG作りチュートリアルを終わらせたかったけど思った以上に発売が早くて厳しかったです。

少しずつプチコン4に対応した記事にリライトしていかなきゃいけませんね。

一旦できているところまで打ち込んで、廃止された命令などがあると思うのでそれの修正もしなければいけませんし。

当RPGの作り方講座に関してはまだもうちょっと続く感じです。

次回はメッセージ送りの実装をしてみましょう。

最後に恒例のここまでのソースコードを張り付けておきます。

それでは。

ACLS

# 変数定義
VAR G_STOP_FLAG = TRUE # ゲームループフラグ
VAR G_SCENE_FLAG = 0 # シーンフラグ
VAR G_I # 汎用変数
VAR G_OX = 0, G_OY = 0 # BG読み込みのオフセット
VAR G_SZ=16, G_MW, G_MH # チップサイズ、マップ幅、マップ高さ
VAR G_BGW = CEIL(400/G_SZ), G_BGH = CEIL(240/G_SZ) # BG読み込み準備
DIM G_MAP[0] # マップレイヤー4枚+イベントレイヤー分

# 変数定義
VAR G_SP_PLAYER = 0 # PLAYERスプライトNo.
DIM G_SP_ANIM_NO[9] # スプライトアニメーション初期値変数 添え字=ID
DIM G_EV_ID[32] # イベント用情報の配列
VAR G_EV_STEP = 4 # 1つのイベントデータ数
DIM G_EV_QUEUE[0] # イベントキュー配列

# ボタン分の配列を用意
DIM G_BTN_PRESS_COUNT[13]
# 一応定義したボタン配列を0で初期化しておく
FILL G_BTN_PRESS_COUNT,0


# データ定義場所
DATA 500,1040,920,1000,980,1020,269,269,269
DATA 1,5,5,2,  2,4,11,2,  3,23,13,2,  4,13,3,2,  5,22,4,2
DATA 6,6,7,1,  7,3,11,1,  8,22,3,1

@EVENT006
DATA 5,  10,10,0  3,1,1  2,1,0  1,1,0  1,2,1


# アニメーション配列初期値代入
D_FIRST_DATA_READ G_SP_ANIM_NO
# イベントID,X座標,Y座標代入
D_FIRST_DATA_READ G_EV_ID

# マップデータ準備
LOAD "DAT:TEST", G_MAP, 0 # マップデータのロード
G_MW = SHIFT(G_MAP) # 読み込んだデータ配列の0個目を配列から切り離して取得(1638415という数字が入ってる)
G_MH = G_MW AND &HFFFF : G_MW = G_MW >> 16 AND &HFFFF # 取り出したデータからシフト演算やらビット演算をする

# マップデータ描写
FOR G_I=0 TO 3
  BGSCREEN G_I, G_BGW, G_BGH, G_SZ # 1画面分のBG
  BGOFS 0,0,4-G_I
  BGLOAD I, -G_OX, ( -G_OY - ( G_I * G_MH )), G_MW, G_MH * ( G_I + 1 ), G_MAP # マップ描写
NEXT

# プレイヤー配置
D_SP_SETUP G_SP_PLAYER, 1, 0

# イベント配置
FOR G_I=0 TO LEN(G_EV_ID)-1 STEP G_EV_STEP
  D_SP_SETUP G_EV_ID[G_I], G_EV_ID[G_I+1], EV_ID[G_I+2]
NEXT

# グラフィックでウィンドウを定義
GPRIO 0

# ゲームループ
WHILE G_STOP_FLAG
  IF G_SCENE_FLAG == 0 THEN
    D_CONTROLLER
    D_BTN_PRESS
  ELSEIF G_SCENE_FLAG == 1 THEN
    D_SHIFT_EV_DATA
  ENDIF
WEND

# コントローラー関数
DEF D_CONTROLLER
  IF SPVAR(SP_PLAYER, 2) != 1 THEN
    VAR B = BUTTON()
    IF (B AND #UP) > 0 THEN
      IF D_CHECK_COLLISION(G_SP_PLAYER, #UP) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #UP
      ENDIF
    ELSEIF (B AND #DOWN) > 0 THEN
      IF D_CHECK_COLLISION(G_SP_PLAYER, #DOWN) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #DOWN
      ENDIF
    ELSEIF (B AND #LEFT) > 0 THEN
      IF D_CHECK_COLLISION(G_SP_PLAYER, #LEFT) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #LEFT
      ENDIF
    ELSEIF (B AND #RIGHT) > 0 THEN
      IF D_CHECK_COLLISION(G_SP_PLAYER, #RIGHT) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #RIGHT
      ENDIF
    ENDIF
  ELSE
    D_GRID_MOVE SP_PLAYER, (SPVAR SP_PLAYER, 3)
  ENDIF

  # 移動中もボタンを押せるようにしておく
  IF G_BTN_PRESS_COUNT[4] == 1 THEN
    PRINT "Aボタンが押されたよ!"
    D_EV_CHECK G_SP_PLAYER SPVAR(G_SP_PLAYER, 3)
  ENDIF

  IF G_BTN_PRESS_COUNT[6] == 1 THEN
    PRINT "Xボタンが押されたよ!"
  ENDIF
END

# 向きと移動フラグを設定
DEF D_DIRECTION_MOVE A_SPRITE_NO, A_SPRITE_DIRECTION
  SPVAR A_SPRITE_NO, 2, 1
  SPVAR A_SPRITE_NO, 3, A_DIRECTION
END

# 向いている方向に止まるまで歩き続ける(グリッド移動)
DEF D_GRID_MOVE SPRITE_NO, SPRITE_DIRECTION
  # 渡されたスプライトと向きによって移動方向を決定
  IF SPRITE_DIRECTION == #UP THEN
    SPVAR SPRITE_NO, 1, (SPVAR SPRITE_NO, 1) - 1
  ELSEIF SPRITE_DIRECTION == #DOWN THEN
    SPVAR SPRITE_NO, 1, (SPVAR SPRITE_NO, 1) + 1
  ELSEIF SPRITE_DIRECTION == #LEFT THEN
    SPVAR SPRITE_NO, 0, (SPVAR SPRITE_NO, 0) - 1
  ELSEIF SPRITE_DIRECTION == #RIGHT THEN
    SPVAR SPRITE_NO, 0, (SPVAR SPRITE_NO, 0) + 1
  ENDIF

  # 移動させる
  SPOFS SPRITE_NO,(SPVAR SPRITE_NO, 0),(SPVAR SPRITE_NO, 1)

  # 割った余りが0になったら動きを止める
  IF (SPVAR SPRITE_NO, 0) MOD G_SZ == 0 && (SPVAR G_SPRITE_NO, 1) MOD G_SZ == 0 THEN
    SPVAR G_SPRITE_NO, 2, 0
  ENDIF

END

# ドット座標からグリッド座標に変換
DEF D_GET_GRID_XY(A_X_OR_Y)
  RETURN A_X_OR_Y / G_SZ
END

# MAP配列のどこにいるかチェック
DEF D_GET_MAP_POSITION(A_X, A_Y, A_LAYER)
  VAR RETURN_POS = 0
  VAR GRID_X = D_GET_GRID_XY(A_X)
  VAR GRID_Y = D_GET_GRID_XY(A_Y)

  VAR PULS_LAYER = (G_MW * G_MH) * A_LAYER

  IF A_Y <= 0 THEN
    RETURN_POS = GRID_X + PLUS_LAYER
  ELSE
    RETURN_POS = (GRID_X + (GRID_Y * G_MW)) + PLUS_LAYER
  ENDIF

  RETURN RETURN_POS
END

# 当たり判定チェック
DEF D_CHECK_COLLISION(A_NO, A_DIRECTION)
  VAR MP1_SUM_POS = 0
  VAR EV_SUM_POS = 0

  VAR DIRECT = D_GET_DIRECT_POINT(A_DIRECTION)

  VAR MAP_POS = D_GET_MAP_POSITION(SPVAR(A_NO,0), SPVAR(A_NO,1), 1)
  VAR EVENT_POS = D_GET_MAP_POSITION(SPVAR(A_NO,0), SPVAR(A_NO,1), 3)

  MP1_SUM_POS = MAP_POS + DIRECT
  EV_SUM_POS = EVENT_POS + DIRECT

  IF G_MAP[MP1_SUM_POS] == 0 THEN
    IF G_MAP[EV_SUM_POS] == 0 THEN
      D_EV_LAYER_REWRITE A_NO, A_DIRECTION, 1
      RETURN 1
    ELSE
      RETURN
    ENDIF
  ELSE
    RETURN 0
  ENDIF
END

# スプライトセットアップ
DEF D_SP_SETUP A_NO, A_X, A_Y
  VAR F = 20
  VAR ANIM = G_SP_ANIM_NO[A_NO]

  SPSET A_NO ANIM
  SPVAR A_NO 0, G_SZ * A_X
  SPVAR A_NO 1, G_SZ * A_Y
  SPVAR A_NO 2, 0
  SPVAR A_NO 3, #DOWN
  SPOFS A_NO SPVAR(A_NO,0), SPVAR(A_NO,1),1

  IF A_NO == 0 || G_EV_ID[(G_EV_STEP * A_NO)-1] == 2 THEN
    SPANIM A_NO, "I", F,ANIM, F,ANIM+1, F,ANIM+2, F,ANIM+3, 0 
  ENDIF
END

# イベントレイヤー書き換え
DEF D_EV_LAYER_REWRITE A_NO, A_DIRECT, A_FLAG

  VAR POS = D_GET_MAP_POSITION(SPVAR(A_NO,0), SPVAR(A_NO,1), 3)

  IF A_FLAG > 0 THEN
    IF A_DIRECT > 0 THEN
      VAR AFTER_POS = POS + D_GET_DIRECT_POINT(A_DIRECT)
      G_MAP[AFTER_POS] = A_NO
    ELSE
      G_MAP[POS] = A_NO
    ENDIF
  ELSE
    VAR BEFORE_POS = POS - D_GET_DIRECT_POINT(A_DIRECT)
    G_MAP[BEFORE_POS] = 0
  ENDIF

END

# 方向定数から1歩前のグリッド座標を得るための値を得る
DEF D_GET_DIRECT_POINT(A_DIRECTION)
  IF A_DIRECTION == #UP THEN
    RETURN DIRECT = -MW
  ELSEIF A_DIRECTION == #DOWN THEN
    RETURN DIRECT = MW
  ELSEIF A_DIRECTION == #LEFT THEN
    RETURN DIRECT = -1
  ELSEIF A_DIRECTION == #RIGHT THEN
    RETURN DIRECT = 1
  ENDIF
END

# DATA初期読み込み
DEF DD_FIRST_DATA_READ A_ARRAY
  FOR G_I=0 TO LEN(G_EV_ID)-1
    VAR N=0: READ N
    G_EV_ID[G_I] = N
  NEXT
END

# ボタンカウント関数
DEF D_BTN_PRESS_COUNT
  VAR B = BUTTON()

  # Aボタンカウント
  IF (B AND #A) > 0 THEN
    IF G_BTN_PRESS_COUNT[4] < 255 THEN 
      INC G_BTN_PRESS_COUNT[4]
    ENDIF
  ELSE
    # 1回でも離されたらカウントリセット
    G_BTN_PRESS_COUNT[4] = 0
  ENDIF

  # Xボタンカウント
  IF (B AND #X) > 0 THEN
    IF G_BTN_PRESS_COUNT[6] < 255 THEN 
      INC G_BTN_PRESS_COUNT[6]
    ENDIF
  ELSE
    # 1回でも離されたらカウントリセット
    G_BTN_PRESS_COUNT[6] = 0
  ENDIF

END

# 前方のイベントをチェックする
DEF D_EV_CHECK A_NO A_DIRECT

  VAR POS = D_GET_MAP_POSITION(SPVAR(A_NO,0), SPVAR(A_NO,1), 3)
  VAR AFTER_POS = POS + D_GET_DIRECT_POINT(A_DIRECT)

  VAR EV_ID = G_MAP[AFTER_POS]

 D_GET_EV_DATA EV_ID
END

# イベントリストからイベントキューへ挿入
DEF D_GET_EV_DATA A_ID
  VAR EV_COUNT = 0
  VAR EV_DATA = 0

  IF A_ID == 6 THEN
    RESTORE @EVENT006
    READ EV_COUNT
    FOR G_I=1 TO (EV_COUNT * 3)-1
      READ EV_DATA
      PUSH G_EV_QUEUE, EV_DATA
    NEXT
  ENDIF

  IF LEN(G_EV_QUEUE) > 0 THEN
    G_SCENE_FLAG = 1
  ENDIF
END

DEF D_SHIFT_EV_DATA
  VAR TYPE = SHIFT(G_EV_QUEUE)
  VAR EVENT = SHIFT(G_EV_QUEUE)
  VAR RESERVE = SHIFT(G_EV_QUEUE)

  PRINT "[イベント かいし]"
  PRINT "===================="

  IF TYPE == 1 THEN
    D_WINDOW_DRAW
  ELSEIF TYPE == 2 THEN
  ELSEIF TYPE == 3 THEN
  ELSEIF TYPE == 10 THEN
  ENDIF

  PRINT "-------------------"

  IF LEN(G_EV_QUEUE) <= 0 THEN
    G_SCENE_FLAG = 0
  ENDIF
END


DEF D_WINDOW_DRAW
  GFILL 80, 150, 320, 208 RGB(0, 21, 151)

  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, "プレイヤー は たからばこ を あけた!"
  GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!"

END