【プチコン講座】イベントを動かすためのシーンを作ってみよう

プチコン

プチコン イベント 宝箱

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

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

前回「【プチコン講座】Aボタンでイベントを調べられるようにしよう」でイベントを調べられるようにしました。

今回はイベントを動かすためのイベントシーンの実装をしていきます。

ここで言うイベントシーンというのは主にメッセージ処理に使われます。

普通のRPGであればプレイヤーやイベントを動かしたりエフェクトを出したりしますが、
今回はそこまで複雑なことをしませんが今後やっていくための土台になりますのでしっかり組みましょう。

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

  1. ゲームループの中で条件分岐をしてシーンを切り替えよう
  2. イベントキューを作ろう

ゲームループの中で条件分岐をしてシーンを切り替えよう

まずはシーンを切り替えるためのフラグを作りましょう。

TRUE or FALSE でもよいのですがバトルシーンやメニューシーンを作るとなると2つじゃ足りませんね。

なので数値で切り替えられるようにしましょう。


G_SCENE_FLAG = 0

フラグを作ったのでゲームループでシーン0は今までのマップ移動シーンに設定してください。


WHILE G_STOP_FLAG
  IF G_SCENE_FLAG == 0 THEN
    D_CONTROLLER
    D_BTN_PRESS
  ENDIF
WEND

これでマップシーンが確立しましたね。

ここを数値で切り替えていけば好きなようにシーンを作ることが出来ます。

グローバル変数なのでどこからでもシーンを変更出来るので便利ですね。

ちょっと早いですが、もうここでは説明することが無いので次に進みましょう。

イベントキューを作ろう

キュー(Queue)というのは簡単に言うと筒状の入れ物に筒より少し小さい径ボールを入れていきます。

そして、取り出すのは最初に入れたボールから取り出していくようなイメージです。

キューについて詳しく知りたい人はググってみてください。

イベントもこれと同じで、イベントリストをキューに入れていき取り出して実行します。

イベントキューが無くなったらフラグを切り替えてシーンを戻すといった感じですね。

この辺はやってみないとイメージが掴みづらいと思うのでまずはキューを作ってみましょう。

DIM G_EV_QUEUE[0]

とりあえずテストなので文字列の配列にしようと思います。

マップシーンではこのイベントキューを毎ループ監視する必要があります。

例えば宝箱を調べたら宝箱のIDを見て該当するイベントリストをイベントキューにコピーします。

そうすると次のループでイベントキューの配列の長さが0以上になりますね。

条件分岐でイベントキューの長さが0より大きければシーンフラグを1に変更するといった具合です。

この時、1つ以上イベントがあれば先頭のイベントキューのタイプを見て切り分けるといった感じが便利そうですね。

ボスイベントであればメッセージイベントが終わった後にバトルに突入とかする時に便利なので、
何か問題が出てくるまでは一旦これで行きましょう。

イベントリストを作ってみよう

キューにイベントを入れるためにはイベントリストが必要ですね。

これもイベントをマップに配置したように、04は1つ目のイベント、59はイベント2とスペースを設けてやるのがよさそうです。

ですが今回の作るゲームは構成的にはこんな感じでしょう。

  • Queue[0] => イベントタイプ
  • Queue[1] => イベント内容
  • Queue[2] => 予備

3つと少ないですね。

解説していきましょう。

イベントタイプについて

イベントタイプが分かれば何をするかがはっきりとわかります。

例えばイベントタイプ1が宝箱だった場合

  • 宝箱を開けるメッセージ
  • 宝箱の中身「イベント内容」を知らせるメッセージ
  • アイテム「イベント内容」増減処理
  • 宝箱グラフィックを開けたものに変更

と決まっています。

この定型処理を呼び出すためにイベントタイプを利用します。

「イベント内容」となっているところは次に解説するイベント内容によって変更されます。

イベントタイプは数値で管理することが出来ますね。

イベント内容について

イベントタイプが分かったらどこの配列から情報を取ってくるのかが判別できます。

そして、判別した配列の中からイベント内容と合致する内容を取ってきてイベント関数に渡すだけですね。

イベント内容は配列の添え字にアクセスするのでこれも同じくして数値でいけますね。

予備について

例えば宝箱の場合、中身をメッセージで表示する必要があります。

予備にアイテム名を入れておけばメッセージ表示の一部に置き換えるように設定すれば
「アイテム名」を手に入れた!と表示することができます。

バトルの場合はここに戦わせたいモンスターの名前や能力値のようなデータをいれてもいいですね。

今回作っているゲームは単純なものなので予備1つでいけますが、できれば事前に設計しておくべきです。

宝箱のイベントリストを仮で作ってみる

イベントタイプとイベント内容があれば何のイベントを発動させるかわかるので、
宝箱イベントリストを作ってみましょう。


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

やっていることを解説すると、まずはイベント006、つまり宝箱のデータを用意してラベルで飛べるようにしています。

そしてイベント1つにつき3つのデータが必要になるので、
3つ1ブロックとして空白を2つ入れて見やすくしています

一番先頭に5とありますが、これはイベントの数(3つで1つだから5つになる)を明示しています。

COPYで一括代入しようと思ったのですが、配列の要素数を決めておかないとエラーがでてしまいますので、
先頭だけ取り出してループの数を取得してからREADで一つずつ代入することにしました。

もっといい方法があるかもしれませんが、ちょっと思いつかなかったので一旦このやり方でいきます。

後日いい方法を思いついたら機を計らって一気に変更します。

現時点でこれはなにをやっているかというと1つ目のブロックは「10, 10, 0」となっていますね。

1つ目は10イベントタイプで「SEを鳴らす」にしてみました。

2つ目の10は「SE何個目」かという意味ですね。てきとうにやったのであとでふさわしい音に変えます。

3つ目は特に何もすることがないので0にして何もしないという意味にします。

次のブロックは「3,1,1」ですが、イベントタイプの3はアイテムの増減にします。

その後の1はアイテムの番号です。

そして最後の予備の1は増減値ですね。

増やすなら1以上、減らすなら-1以下ということでやります。

全部説明していると冗長になるのでイベントタイプだけ言いますね

残りのイベントタイプ2は「スプライト変更」宝箱を開けた状態にするためですね。

そしてイベントタイプ1はメッセージの表示。

SEを10にしたのはその間に何かを入れるためのスペースです。

思いついた順にやってもよかったのですが、こうやって最初から予備スペースを空けておくと言うのは
後から何かを追加したいときとかに便利です。

ちょっとこの辺が行き当たりばったりで考えたことなのでそのうち修正するかもしれません。

こうやっていい方法が思いつかなくてもゴリ押しで作れたりできるのもプログラミングの楽しい所です。

イベントリストをキューに入れてみよう

イベントIDは調べた時に取得できたと思うので、そのIDを基準に分岐させるようにします。

まずはPRINTだったところをイベントを判定する関数に置き換えたいので下記のように変更してください。

# 前方のイベントをチェックする
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

イベントIDで分岐していたところを「D_GET_EV_DATA」に置き換えました。

それでは「D_GET_EV_DATA」を作りましょう。

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

まずイベントカウントとイベントデータ変数を定義します。

そしてイベントIDは一旦宝箱だけでテストするのでIFの条件は6にしてください。

RESTOREで指定ラベルに飛んで、DATAの先頭にある5をとるために一度READします。

その後、ループ回数が分かるのでFORのTOの後に3倍して1引いたものを条件にしてください。

この場合5*3-1となるので0~14までG_Iがループしますね。

そしてその中でREADでDATAからデータを一旦EV_DATAにSHIFTで先頭を1つ取り出してから、PUSHで配列に挿入してやりましょう。

ループしていけばイベントキューにDATAの1つ目以外は全部入るので宝箱イベントのリストが作成されました。

次にイベントキューの配列が要素数0よりおおきければシーンフラグを1にします。

次のループにいくときシーンフラグが1になっているので、そこでイベントキューを取り出す関数を呼び出します。

ゲームループを以下のように変更しましょう。

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

シーンフラグが1の時、「D_SHIFT_EV_DATA」が追加されてますね。

それでは「D_SHIFT_EV_DATA」を作りましょう。

イベントキューから取り出して処理するだけなので返り値も引数もありません。


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

現状まだイベントそのものを作っていないので一旦PRINTでイベントキューから、
何のイベントが呼ばれたのかわかるようにしてみましょう。

本来はメッセージ送りとかでAボタンを押して進めないといけませんが、
今回はイベントが一気に呼び出されて一瞬で終わります。

しかし画面に出ていれば何が呼び出されたのかわかると思うので今回は一旦これで行きましょう。

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

プチコン 結果

このようになっていたらOKです。

──段々と1回の記事の難易度が上がってきています。

javascriptだったら簡単だったことがSmileBasicだと結構回り道をしたり、独自の書き方をしなければならないところが大きいですね。

しかしながらパズルみたいで面白いですね。

私は正解が何か分からないパズルより最初から正解が分かっていて、
それにたどり着く為にどうするかを考えることがどうやら結構好きなようです。

古畑任三郎のドラマみたいですね。

犯人が分かっていてそれを推理していく。

最近ちょっとした古畑任三郎パロ動画にハマってしまったのでたぶんそういうことなんでしょう。

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

それでは。

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
  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

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

  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