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

プチコン

プチコン メッセージ

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

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

前回「【プチコン講座】イベントを動かすためのシーンを作ってみよう」でイベントごとに処理を分岐出来ました。

次に進みたい所なのですが、その前に文字を出すためのメッセージウィンドウを作ろうと思います。

ウィンドウはスプライトでもいいのですが、特殊な装飾はしないシンプルな描写でいいので
テキストも表示できるグラフィック命令を使ってメッセージウィンドウを作っていきましょう。

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

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

文字を表示する土台となる矩形を表示しよう

メッセージウィンドウの土台となる部分を矩形を使って作ります。

矩形には枠だけと塗りつぶしの2種類がありますが、ウィンドウの中身の部分になるので塗りつぶしですね。

プチコンを始めた時にグラフィック表示って何に使うんだろうと思ったこと、ありませんか?

実はこういうウィンドウを表示したいときに使えたりもするんです。

それではメッセージウィンドウの背景を出してみましょう。

コードを書く場所はBGやSPが表示した後にしましょう。


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

これで実行してみてください。

WiiUであればたぶんいい感じになっていると思います。

3DSの場合は調整してみてください。

グラフィックの表示順番

プチコンにはグラフィックの表示順番があります。

BGにレイヤーがあるように、コンソール・BG・SP・グラフィックと背景色の5つがあります。

プチコンの画面構成については公式ページを参照してください。

話戻すと、そのうちグラフィックはZ軸が初期状態では-1024になっていてBGの後ろに表示されてしまいます。

そうなるとせっかく描写したものが見えなくなるので「GPRIO」を0にすることでBGより前に持ってきます。

しかしGPRIOが0でもスプライトより下に表示されてしまうので場所によってはウィンドウがでてもスプライトがウィンドウの上に乗っかってしまってブサイクな状態になります。

なのでスプライトのZ軸をグラフィックより下にしてしまおうと考えましたが、
スプライトのZ軸を変更する命令がみあたらないなぁと朝ツイートしていたら@ryumagoさんができるとのことで教えていただいたので調べたら「SPOFS」の3つ目の引数がZ軸の指定になるそうです。

@ryumagoさん、ありがとうございます。

スプライト生成関数のところで最後にSPOFS命令をしているので、そこでZ軸を指定してしまいましょう。

変更してしまったら、一度ちゃんとスプライトの上にグラフィックが来るか確認しておくとよいです。

そしてBGもZ軸を設定しなければいけないので注意しましょう。

z軸としては下記が理想です。

  • 0 => グラフィック
  • 1 => スプライト
  • 2~4 => バックグラウンド

グラフィックはGPRIOで0にしてSPOFSで1にしました。

BGもBGOFSでやるのですが、現在マップはマップデータからループで生成しています。

マップは下から上に重ねるように描写しているので、ループの値をそのまま使って2・3・4とやってしまうとおかしくなります。

以下のように逆から減らしていってください。


# マップデータ描写
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

これで理想の順番になりました。

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

ウィンドウベタ塗りだけだとなんか味気ないですよね。

なのでウィンドウの周りにフレームを作ってみましょう。

イメージ的にはファミコン時代のFF風にします。

ここはお好みで大丈夫です。

ドラクエだと黒でベタ塗りでベベルのないフレームを使っています。

FFの場合はフレームが角丸で少しべベルがかかってる感じですね。

これもウィンドウ背景同様、グラフィックで表示します。

枠も背景も本来はスプライトで作ったほうが見栄えは良くなりますが、
そこはプログラミングというよりもグラフィックデザインの分野になるので酢省略ですね。

コーディングする前に少し言っておくと、現在メッセージウィンドウはリテラルで記述しているので、できれば変数化しておくのがベストです。

もしやり方がよくわからない場合は記事最下部に乗せているここまでの全ソースコードを参照してください。

それではフレームを作ってみましょう。


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)

位置はウィンドウ背景の基準にしておけば周りを囲むだけでのすので指定が楽ですね。

数値をよく見たら一定の法則性があるので関数化して値を入れるだけで、
好きな所に好きな大きさのウィンドウを作れそうな感じがします!

実際、はい・いいえウィンドウやメニュー画面表示にも使うので後に関数化します。

今はこのままでいきましょう。

実行してみて、下記のようになればOKです。

プチコン 結果

メッセージウィンドウぽくなりましたね。

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

せっかくメッセージウィンドウを作ったのですから文字を表示したいですね。

しかし文字の表示はPRINTを使って何度かやっていますが、PRINTだと順番にしか表示できないので、ウィンドウ枠に表示できません。

なので使うのは「GPUTCHR」という命令です。

「GPUTCHR16」という2倍のサイズの文字表示があるのですが、画面の見た目がファミコン風に対して文字だけがスーパーファミコンちっくになってしまうので今回は「GPUTCHR」でいいでしょう。

それではウィンドウに文字を出してみましょう。


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

これだけです。

簡単ですね。

ウィンドウの位置が決まっていれば左上を取得できるのでそこから少し内側にずらしてやればOKです。

次回はこれを宝箱を開いたときに表示するようにしたいですね。

プチコン 結果

──平日はあまりがっつりと時間が取れないので前編後編になってしまうことをお許しください。

私自身がプチコンの命令に慣れていないのもありたまに戸惑ってツイッターで助言をいただくことが多々あります。

思うように記事が進まないこともありますが、ご了承を。

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

さっき気付いたのですが、スプライトセットアップ関数がSPVARと書くところがSPSETになっていたので直しました。

今週の土日でも過去の記事を修正しておきます。

それでは。

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
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, "たからばこ には ライフポーション が はいっていた!"



# ゲームループ
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

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
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
    PRINT "メッセージイベント:" + STR$(EVENT) + " | ほそく:" + STR$(RESERVE)
  ELSEIF TYPE == 2 THEN
    PRINT "スプライトへんこうイベント:" + STR$(EVENT) + " | ほそく:" + STR$(RESERVE)
  ELSEIF TYPE == 3 THEN
    PRINT "アイテムぞうげんイベント:" + STR$(EVENT) + " | ほそく:" + STR$(RESERVE)
  ELSEIF TYPE == 10 THEN
    PRINT "SEえんそうイベント:" + STR$(EVENT) + " | ほそく:" + STR$(RESERVE)
  ENDIF

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

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