【プチコン講座】メッセージ送りの実装をしてみよう

プチコン

プチコン メッセージ送り

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

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

前回「メッセージウィンドウを出してみよう:後編」でメッセージウィンドウを表示したり非表示にしたりできるようにしてメッセージイベントが発動したら開くようにしました。

前回の状態だとメッセージが出てすぐに次のイベントが呼び出されてしまいます。

これでは困りますので、メッセージ送り……つまりメッセージが表示されたらAボタンが押されるまで待機する処理を作ります。

メッセージ送りは今までやってきたことの組み合わせで実現できます。

RPGだけでなく文章を表示するタイプのゲームでは必須機能ですのでしっかりと覚えましょう。

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

  1. メッセージが表示されたらフラグを切り替えよう
  2. Aボタンが押されるまで待機ループを抜けないようにする
  3. コントローラーとシーンの処理を整理しよう

メッセージが表示されたらフラグを切り替えよう

現状、イベントキューからイベントリストを1つずつ取り出して実行していますよね。

その取り出したときにイベントタイプの判定をしていました。

メッセージイベントは「1」なのでそのイベントが取り出されたらフラグを1にしてみます。

フラグを用意して、そのフラグを切り替えてみましょう。


VAR G_EV_Q_FLAG = 0

~~~省略~~~

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
    G_EV_Q_FLAG = 1
  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

これでメッセージ送りのフラグを絶たせることができました。

次にイベント中はゲームループのシーンが切り替わっていますね。

メッセージ送りがない時はキューを取り出す関数が連続で実行されていたので一瞬でイベントが終わっていました。

今回用意したフラグを使って分岐させてみましょう。

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

イベントシーンが1の時の中でさらにイベントキューフラグを使って分岐させています。

0の時はイベントキュー取り出しモードで、1の時はAボタンが押されるまで待機する関数が動きます。

Aボタンが押されるまで待機ループを抜けないようにする

Aボタンを押されるまでフラグが切り替わらないようにしたいので

「D_EV_STOP_A_BTN」関数を作りましょう。


DEF D_EV_STOP_A_BTN
  IF G_BTN_PRESS_COUNT[4] == 1 THEN
    PRINT "メッセージ待機解除"
    G_EV_Q_FLAG = 0
  ENDIF
END

マップ画面のAボタン処理の流用ですね。

本当はコントローラー関連の処理は別ファイルにして切り分けて、
特定のフラグの時に1回目押されたらフラグ解除という処理にしておいた方が無駄なコードを書かずに済みます。

今回はいったんわかりやすいように作っていますが、最適化チャレンジをしてみるのも良いかもしれませんね。

ここまでのを実行してみるとメッセージのところで処理がストップすると思いますが、
今のところ文章が固定なのでどのイベントが発動しているのかわかりづらいですね。

グラフィック処理をまとめた関数のところで直接描写しているので、
メッセージ内容に応じて変化させたいところです。

コントローラーとシーンの処理を整理しよう

メインループとコントローラーの関数がごちゃごちゃしてきましたね。

イベントシーンのキー操作とマップシーンキー操作は切り分けたいです。

なので以下のように大幅に改良しました。

# ゲームループ
WHILE G_STOP_FLAG
  D_SCENE_PARENT
  VSYNC 1
WEND

# シーンの大元
DEF SCENE_PARENT

  IF G_SCENE_FLAG == 0 THEN
    D_CTRL_MAP_MODE
  ELSEIF G_SCENE_FLAG == 1 THEN
    IF G_EV_Q_FLAG == 0 THEN
      D_SHIFT_EV_DATA
    ELSEIF EV_Q_FLAG == 1 THEN
      D_EV_STOP_A_BTN
    ENDIF
  ENDIF

  D_BTN_PRESS

END

# マップ用コントローラー関数
DEF D_CTRL_MAP_MODE
  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

ゲームループがかなりシンプルになりましたね。

シーンの大元を呼び出しているだけです。

そして、その呼び出しているシーンの関数を作りました。

ゲームループにあった分岐を「D_SCENE_PARENT」に切り分けました。

シーン変数によって変化させているのは変わらずで、ボタンカウント関数である「D_BTN_PRESS_COUNT」は、
シーンにかかわらずカウントするようにしました。

こうすることでどのシーンに居ても決定ボタンの監視が出来ますね。

そして、長らくあった「D_CONTROLLER」は「D_CTRL_MAP_MODE」に変更しています。

内容は変わっていません。

これでだいぶすっきりしましたね。

一度実行してみて宝箱を調べて、2回分メッセージが表示される状態になっています。

話しかけるところから合計して3回Aボタンを押せば動けるようになるはずです。

──プチコン4発売されましたね。

朝起きて速攻ダウンロードしてみましたが、ワクワクが止まりません。

土日にコード移植してプチコン4にも対応させてみます。

背景表示がBGから変わってるので、それ以外は意外と問題なさそうな感じがします。

効率化できそうなところは追記していこうと思います。

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

それでは。

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
  D_SCENE_PARENT
  VSYNC 1
WEND

# シーンの大元
DEF SCENE_PARENT

  IF G_SCENE_FLAG == 0 THEN
    D_CTRL_MAP_MODE
  ELSEIF G_SCENE_FLAG == 1 THEN
    IF G_EV_Q_FLAG == 0 THEN
      D_SHIFT_EV_DATA
    ELSEIF EV_Q_FLAG == 1 THEN
      D_EV_STOP_A_BTN
    ENDIF
  ENDIF

  D_BTN_PRESS

END

# マップ用コントローラー関数
DEF D_CTRL_MAP_MODE
  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
    G_EV_Q_FLAG = 1
  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

DEF D_EV_STOP_A_BTN
  IF G_BTN_PRESS_COUNT[4] == 1 THEN
    PRINT "メッセージ待機解除"
    G_EV_Q_FLAG = 0
  ENDIF
END