【プチコン講座】メッセージ送りの実装をしてみよう
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は144日目になります。
前回「メッセージウィンドウを出してみよう:後編」でメッセージウィンドウを表示したり非表示にしたりできるようにしてメッセージイベントが発動したら開くようにしました。
前回の状態だとメッセージが出てすぐに次のイベントが呼び出されてしまいます。
これでは困りますので、メッセージ送り……つまりメッセージが表示されたらAボタンが押されるまで待機する処理を作ります。
メッセージ送りは今までやってきたことの組み合わせで実現できます。
RPGだけでなく文章を表示するタイプのゲームでは必須機能ですのでしっかりと覚えましょう。
それではプチコンでRPG作り第16回目を始めましょう。
メッセージが表示されたらフラグを切り替えよう
現状、イベントキューからイベントリストを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