【プチコン4講座】イベントシステム構築:メッセージウィンドウ前編
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は175日目になります。
前回「【プチコン4講座】イベントシステム構築:イベント駆動処理」でイベントを駆動させる仕組みを作りました。
これでイベントを動かせるようになったので、宝箱イベント完成を目指しましょう。
宝箱イベントで画面に影響があるものは主にメッセージイベントのみです。
今回はそのメッセージを表示するためのウィンドウを作っていきます。
それではプチコン4でRPG作りその11回目始めましょう。
ウィンドウの土台となる背景を矩形で表示してみよう
マップ画面の上に文字を表示しても見にくいので、文字を表示するための土台をまず作りましょう。
まずは凝ったウィンドウではなくシンプルなものから作りたいのでとりあえずGRAPHIC命令でベタ塗りをします。
グラフィックを描画する対象ページを決めてそこに描写していきます。
何も指定していないと対象ページが0にっているのですが、一応明示的に指定しておきましょう。
それからベタ塗り命令を行います。
GTARGET 0
GFILL 80, 150, 320, 208 RGB(0, 0, 0)
設置場所はゲーム初期化の後にしてみてください。
その後実行すると、ウィンドウが表示され……ませんね。
しかしグラフィック描画命令は行っています。
確認するために「D_GameInitialize」をコメントアウトして実行してみましょう
ちゃんと描画されています。
ということはマップチップの下に行ってしまっているということですね。
プチコン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で作ったものをそのまま使えました。
これでウィンドウぽくなりましたね。
メッセージウィンドウに文字を表示してみよう
ウィンドウの準備が出来たので、その上に文字を表示してみましょう。
画面に文字を出すのはPRINTや?を使いますが、基本的にこれらはデバッグ用に使うもので
ゲームを作るときにこれらは使いません。
ゲームに使うのは「GPUTCHR」という命令になります。
試しにプチコンBIGでやったものをそのまま打ち込んでみましょう。
GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!"
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!"
おお……もう……
ウィンドウはかき消されてるし、文字サイズが大きすぎますね。
プチコン4では文字サイズが16になっているので8にしてやる必要があります。
GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!",8
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!",8
サイズはいい感じになりましたが、背景が消えてしまっているままです。
同じレイヤーに表示しているので消えてしまうのでしょう
どうやら描画方法を変更すればいい感じでした。
GPUTCHR 85, 155, "プレイヤー は たからばこ を あけた!",8,RGB(255,255,255),1
GPUTCHR 85, 165, "たからばこ には ライフポーション が はいっていた!",8,RGB(255,255,255),1
これで背景をかき消されずに描画することが出来ました。
──全部詰め込むと長くなるので前編後編にしました。
後編はウィンドウの開閉とイベントによるメッセージの呼び出しを実装します。
現状、初期化のところにコードを書きなぐっているのもブサイクなので関数化したいですね。
最後に今回の完成版ソースコードを置いておきます。
それでは
'──────────────────────────
' ▼ 動作モード設定:変数宣言は必須
'──────────────────────────
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