【プチコン講座】ATB風バトルシステムを構築しよう:エネミー編

プチコン

プチコン バトルコマンド

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

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

前回「【プチコン講座】ATB風バトルシステムを構築しよう:コマンド選択編」でプレイヤーの攻撃防御回復ができるようになりました。

防御と回復はエネミーの攻撃を設定してからでないと効果が分からないので、今回はエネミーの行動処理を追加します。

チュートリアルなのでエネミーの行動は攻撃と防御のみに絞るので、プレイヤーの時に作った関数を流用するだけですね。

バトルシステム作りもいよいよ大詰めとなってきました。踏ん張って作り上げましょう!

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

  1. エネミーに攻撃・防御をさせてみよう
  2. 防御の効果を実装しよう
  3. 相手の行動中は行動しないようにするには

エネミーに攻撃をさせてみよう

エネミーのゲージが溜まる処理はもうすでに完成してあります。

プレイヤーと同じように行動できるようになったら攻撃させてみましょう。

# バトルシーンループ
DEF BATTLE_MODE
  D_BATTLE_GAUGE_LOOP
  D_BATTLE_GUAGE_GRAPH
  D_BATTLE_ENEMY_ACTION
  D_BATTLE_SELECT_COMMAND
  D_BATTLE_ATTACK
END

# バトルゲージリセット
DEF D_BATTLE_GUAGE_RESET A_ACTOR
  IF A_ACTOR == 0 THEN
    G_BATTLE_STATUS[0] = 0
    G_BATTLE_STATUS[2] = 0
  ELSE
    G_BATTLE_STATUS[1] = 0
    G_BATTLE_STATUS[3] = 0
  ENDIF
END

# エネミーの行動選択
DEF D_BATTLE_ENEMY_ACTION
  IF G_BATTLE_STATUS[3] == 1 THEN
    PRINT "エネミーのこうげき"
    BEEP 40, 200
    D_BATTLE_DAMAGE 1
    D_BATTLE_GUAGE_RESET 1
  ENDIF
END

これでエネミーもゲージが溜まったら即攻撃してくるようになりました。

プチコン出力結果その1

しかしこのままでは攻撃するだけの機械みたいになってしまうので、
ランダムで防御させるようにしてみましょう。


# エネミーの行動選択
DEF D_BATTLE_ENEMY_ACTION
  IF G_BATTLE_STATUS[3] == 1 THEN
    VAR ACTION_NUM = RND(10)

    IF ACTION_NUM < 8 THEN
      PRINT "エネミーのこうげき!"
      BEEP 40, 200
      D_BATTLE_DAMAGE 1
    ELSE
      PRINT "エネミーのぼうぎょ!"
      D_BATTLE_GUARD 1
    ENDIF
    D_BATTLE_GUAGE_RESET 1
  ENDIF
END

RND関数を使って乱数を発生させました。

10個の乱数を発生させるという意味になりますね。

0~9の数値が返ってきます。

8未満、つまり0~7は攻撃で8~9は防御にしています。

防御率が高くてもつまらないので、これぐらいでいいでしょう。

エネミーに合わせてバランス調整してください。

プレイヤーがダメージを受けるようにする

現時点ではダメージ処理がプレイヤーからエネミーに向けてしか作っていないので、
攻撃の引数に1を渡したときにエネミー側の処理に切り替えるという関数に修正します。

ぼうぎょは渡した値によって該当する配列の防御フラグを変えているだけなので特に何もしなくてOKです。


# ダメージ計算
DEF D_BATTLE_DAMAGE A_ACTOR

  VAR DAMAGE = 0

  IF A_ACTOR == 0 THEN
    PRINT "エネミーHP @ダメージまえ:" + STR$(G_ENEMY_BATTLE_PARAMS[1])
    DAMAGE = G_PLAYER_STATUS[3] - G_ENEMY_BATTLE_PARAMS[3]
  ELSE
    PRINT "プレイヤーHP @ダメージまえ:" + STR$(G_PLAYER_STATUS[1])
    DAMAGE = G_ENEMY_BATTLE_PARAMS[2] - G_PLAYER_STATUS[4]
  ENDIF

  # 最低でもダメージは1入るようにする
  IF DAMAGE <= 0 THEN
    DAMAGE = 1
  ENDIF

  IF A_ACTOR == 0 THEN
    DEC G_ENEMY_BATTLE_PARAMS[1], DAMAGE
    IF G_ENEMY_BATTLE_PARAMS[1] <= 0
      PRINT "エネミーをたおしたぞ!"
    ENDIF
    PRINT "エネミーHP @ダメージあと:" + STR$(G_ENEMY_BATTLE_PARAMS[1])
  ELSE
    DEC G_PLAYER_STATUS[1], DAMAGE
    IF G_PLAYER_STATUS[1] <= 0
      PRINT "GAME OVER!"
    ENDIF
    PRINT "プレイヤーHP @ダメージあと:" + STR$(G_PLAYER_STATUS[1])
  ENDIF
END

これでバトルの根本部分が完成しました!

一度コウモリと戦ってみてください。

プチコン出力結果その2

今のところダメージが2固定なので2発こうげきすれば倒せますが、
エネミー側は20回攻撃しないとだめですね……

ためしにプレイヤーの初期HPをさげてしまってゲームオーバー処理を確認します。

データベースにプレイヤーの現在HPと最大HPを設定している部分の現在HPの部分を4とかにすればわかりやすいでしょう。

防御の効果を実装しよう

お互いに攻撃防御(プレイヤーだけ回復)ができるようになったので、
ぼうぎょの効果を実装していきましょう。

現在、ぼうぎょを選んだ時に防御フラグ配列をONにしています。

ぼうぎょの効果は次に行動できるようになるまでにするか、
次の行動を選択したときに解除するかは好きなようにして大丈夫です。

後者の方がバトル難易度は下がりますね。

一応私は行動が選択できるようになったら解除するようにします。


# バトルゲージリセット
DEF D_BATTLE_GUAGE_RESET A_ACTOR
  IF A_ACTOR == 0 THEN
    G_BATTLE_STATUS[0] = 0
    G_BATTLE_STATUS[2] = 0
  ELSE
    G_BATTLE_STATUS[1] = 0
    G_BATTLE_STATUS[3] = 0
  ENDIF
  G_BATTLE_GUARD_FLAG[A_ACTOR] = 0
END

リセット時に渡された値によってリセットすれば簡単ですね。

次に攻撃時のダメージ計算時にフラグがONになっているときに、
防御を2倍にする処理を組み込んでみましょう。


# ダメージ計算
DEF D_BATTLE_DAMAGE A_ACTOR

  VAR DAMAGE = 0

  IF A_ACTOR == 0 THEN
    PRINT "エネミーHP @ダメージまえ:" + STR$(G_ENEMY_BATTLE_PARAMS[1])
    DAMAGE = G_PLAYER_STATUS[3] - ( G_ENEMY_BATTLE_PARAMS[3] + ( G_ENEMY_BATTLE_PARAMS[3] * G_BATTLE_GUARD_FLAG[A_ACTOR] ))
  ELSE
    PRINT "プレイヤーHP @ダメージまえ:" + STR$(G_PLAYER_STATUS[1])
    DAMAGE = G_ENEMY_BATTLE_PARAMS[2] - ( G_PLAYER_STATUS[4] + ( G_PLAYER_STATUS[4] * G_BATTLE_GUARD_FLAG[A_ACTOR] ))
  ENDIF

  # 最低でもダメージは1入るようにする
  IF DAMAGE <= 0 THEN
    DAMAGE = 1
  ENDIF

  IF A_ACTOR == 0 THEN
    DEC G_ENEMY_BATTLE_PARAMS[1], DAMAGE
    IF G_ENEMY_BATTLE_PARAMS[1] <= 0
      PRINT "エネミーをたおしたぞ!"
    ENDIF
    PRINT "エネミーHP @ダメージあと:" + STR$(G_ENEMY_BATTLE_PARAMS[1])
  ELSE
    DEC G_PLAYER_STATUS[1], DAMAGE
    IF G_PLAYER_STATUS[1] <= 0
      PRINT "GAME OVER!"
    ENDIF
    PRINT "プレイヤーHP @ダメージあと:" + STR$(G_PLAYER_STATUS[1])
  ENDIF
END

ぼうぎょフラグがONの時は1になるので、
元の防御力を掛け算すれば0か元の防御力をプラスしてからダメージ計算を行います。

一度コウモリの攻撃パターンをつねに防御にするように条件を変えてみて防御の効果を確認してみましょう。

私は面倒なのでコウモリが防御するまで待ちました(笑)

プチコン出力結果その3

こうもりに攻撃したときにダメージが1になっていればぼうぎょ効果は反映されています。

相手の行動中は行動しないようにするには

これ以降はコードは書きません。

バトルの行動順番や行動キューについてちょっと考えてみましょう。

現状ではATB風バトルではありますが、攻撃しかないので選択した瞬間攻撃しますね。

FFで考えてみると攻撃を選択した時に少しだけ間があると思います。

バトルシステムは内部では行動キュー制となっているので、
選択した順番でなおかつ行動までのディレイが少ないキャラクターが優先されます。

魔法で例えるとよくわかりやすいです。

魔法は詠唱時間というものがあるので、
基本的に魔法を選択したすぐ後に別キャラが選んだ通常攻撃よりも順番が遅くなります。

しかし現状のシステムでは攻撃と防御と回復しかありません。

どれも詠唱要らずで選んだものが行動を起こします。

戦闘中ならスピーディでいい戦闘なのですが、ゲームオーバーになる瞬間がちょっと面倒です。

ほぼ同時に攻撃した場合の判定はどうするかですね。

相打ちというのはアクションRPGじゃなければ殆どありえません。

プログラムの性質上、上から下に処理が流れていくので最初に処理された方が勝ちになります。

一応、バトルループを見てもらうとエネミーのアタックの方が処理が上になっていると思います。

この時ダメージを与えた時にHPチェッカー的な関数を置いておくと、
いい感じにプレイヤーの攻撃を実行する前にゲームオーバー処理にすることが出来ます。

行動キュー的な仕組みにしていると安全にこの処理を行えるのですが、
現状のままでは何かしらのタイミングで変な処理になるのではないかと心配です。

しかしテコ入れは簡単なので一旦このままで進めましょう。

もしおかしな動きになったらその時修正したいと思います。

──少し短いですがキリがよいので今日はここまでにしましょう。

次回はプレイヤーのHPを表示したり、誰が何の行動を起こしたのかわかるようにUIを作りたいと思います。

現状では染みすぎるので、攻撃時やダメージ時にエフェクトや効果をつけたりもしたいですね。

最後に恒例のソースコードを置いておきます。

それでは。

ACLS

#------------------
# 変数定義
#------------------
VAR G_STOP_FLAG = TRUE # ゲームループフラグ
VAR G_I # 汎用変数

# シーンフラグ
VAR G_SCENE_FLAG = 0
VAR G_STATUS_FLAG = FALSE

# イベント用
VAR G_EV_Q_FLAG = 0 # イベントキューがあるかどうか
DIM G_EV_ID[32] # イベント用情報の配列(初回DATA読み込み用)
VAR G_EV_ID_NOW = 0 # 現在のイベントナンバー
VAR G_EV_STEP = 4 # 1つのイベントデータ数
DIM G_EV_QUEUE[0] # イベントキュー配列

# プレイヤー用
VAR G_PLAYER_NAME$ = ""
VAR G_PLAYER_STATUS_LEN = 8
DIM G_PLAYER_STATUS[0]

# ウィンドウサイズ用
VAR G_WINDOW_SIZES_LEN = 12
DIM G_WINDOW_SIZES[0]

# カーソル用
VAR G_CURSOR_X = 0
VAR G_CURSOR_Y = 0
VAR G_CURSOR_N = 0
VAR G_CURSOR_POS_LEN = 3
DIM G_CURSOR_POS[0]

# アイテム名用
VAR G_ITEMS_NAMES_LEN = 2
DIM G_ITEMS_NAMES[0]

# システムワード
VAR G_SYSTEM_WORDS_LEN = 7
DIM G_SYSTEM_WORDS[0]

# エネミー用
VAR G_ENEMY_NAMES_LEN = 5
DIM G_ENEMY_NAMES[0]
VAR G_ENEMY_PARAMS_LEN = 35
DIM G_ENEMY_PARAMS[0]
DIM G_ENEMY_BATTLE_PARAMS[7]

# メッセージ用
VAR G_MSG_DATAS_LEN = 4
DIM G_MSG_DATAS[0]

# ボタン分の配列を用意
DIM G_BTN_PRESS_COUNT[13] # 全ボタンカウント配列
FILL G_BTN_PRESS_COUNT,0 # 一応定義したボタン配列を0で初期化しておく

# BG関連
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_BATTLE_STATUS[6]
DIM G_BATTLE_GUARD_FLAG[2]



#------------------
# データベース定義
#------------------
# 開始時に読み込むDATA軍(ラベル付けたほうが安全かもしれない
DATA 500,1040,920,1000,980,1020,269,269,269
DATA 1,5,5,2,  2,23,13,2,  3,4,11,2,  4,13,3,2,  5,22,4,2
DATA 6,6,7,1,  7,3,11,1,  8,22,3,1

# コウモリイベントデータ
@EVENT001
DATA 3,  4,1,0, 3,1,0, 0,1,1
@EVENT001_1
DATA 1,  0,0,0


# 宝箱イベントデータ
@EVENT006
DATA 7,  10,95,400  3,1,268  2,0,1  1,1,0, 10,12,0  1,2,0,  0,1,1
@EVENT006_1
DATA 2,  1,3,0,  0,0,0

# プレイヤー初期ステータス
@PLAYER_INIT_STATUS
DATA 1,20,20,3,2,3,0,1

# ウィンドウサイズ用
@WINDOWSIZE
DATA 80,150,240,58,  10,10,112,77,  10,10,80,50

# カーソル用
@SELECTDATA
DATA 3,5,15

# エネミーパラメータ用
@ENEMYPARAMS
DATA 1,4,3,1,3,2,20
DATA 2,8,3,1,3,2,20
DATA 3,12,3,1,3,2,20
DATA 4,16,3,1,3,2,20
DATA 5,30,3,1,3,2,20

# ステータスウィンドウ用ワード
@SYSTEMWORD
DATA "Lv", "HP", "こうげき", "ぼうぎょ", "すばやさ", "けいけんち", "ポーション"
# アイテム名リスト
@ITEMDATA
DATA "ライフポーション", "せいすい"

# モンスター名リスト
@ENEMYNAME
DATA "リトルバット", "ゴブリン", "スケルトン", "マミー", "ゴースト"

# 定型文リスト
@MESSAGEDATA
DATA "は たからばこを あけた", "たからばこ には", "が はいっていた!", "からっぽ!"

# アニメーション配列初期値代入
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

# 配列初期化
FILL G_ENEMY_BATTLE_PARAMS, 0
FILL G_BATTLE_GUARD_FLAG, 0



# 各種データの読み込み
D_DB_LOAD 0, G_PLAYER_STATUS_LEN, G_PLAYER_STATUS
D_DB_LOAD,1, G_WINDOW_SIZES_LEN, G_WINDOW_SIZES
D_DB_LOAD 2, G_CURSOR_POS_LEN, G_CURSOR_POS
D_DB_LOAD 3, G_ENEMY_PARANS_LEN, G_ENEMY_PARANS
D_DB_LOAD 4, G_SYSTEM_WORDS_LEN, G_SYSTEM_WORDS
D_DB_LOAD 5, G_ITEM_NAMES_LEN, G_ITEM_NAMES
D_DB_LOAD 6, G_ENEMY_NAMES_LEN, G_ENEMY_NAMES
D_DB_LOAD 7, G_MSG_DATAS_LEN, G_MSD_DATAS

# グラフィック画面を一番手前に
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
  ELSEIF G_SCENE_FLAG == 2 THEN
    D_STATUS_MODE
  ELSEIF G_SCENE_FLAG == 3 THEN
    D_BATTLE_SCENE_LOOP
  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ボタンが押されたよ!"
    G_SCENE_FLAG = 2
  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 0
    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

  D_EV_LAYER_REWRITE A_NO, 0, 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 D_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

  # Yボタンカウント
  IF (B AND #Y) > 0 THEN
    IF G_BTN_PRESS_COUNT[7] < 255 THEN 
      INC G_BTN_PRESS_COUNT[7]
    ENDIF
  ELSE
    # 1回でも離されたらカウントリセット
    G_BTN_PRESS_COUNT[7] = 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

  D_EV_SELECT_ A_ID

  IF LEN(G_EV_QUEUE) > 0 THEN
    G_EV_ID_NOW = A_ID
    G_SCENE_FLAG = 1
  ENDIF
END

# イベントID毎のイベントキュー作成
DEF D_EV_SELECT A_ID

  IF A_ID != 0 THEN
    # IDラベル作成
    VAR LABEL$ = "@EVENT" + FORMAT$("%03D", A_ID)

    VAR EV_COUNT = 0
    VAR EV_DATA = 0

    IF SPVAR(A_ID, 4) == 0 THEN
      RESTORE LABEL$
    ELSEIF SPVAR(A_ID, 4) == 1 THEN
      RESTORE LABEL$ + "_1"
    ENDIF

    READ EV_COUNT
    FOR G_I=1 TO (EV_COUNT * 3)-1
      READ EV_DATA
      PUSH G_EV_QUEUE, EV_DATA
    NEXT

  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)

  IF TYPE== 0 THEN
    IF EVENT == 1 THEN
      SPVAR G_EV_ID_NOW, 4, RESERVE
    ENDIF
  ELSEIF TYPE == 1 THEN
    GCLS
    D_WINDOW_DRAW G_WINDOW_SIZES[0], G_WINDOW_SIZES[1], G_WINDOW_SIZES[2], G_WINDOW_SIZES[3]
    D_MSG_DRAW G_WINDOW_SIZES[0], G_WINDOW_SIZES[1], EVENT RESERVE
    G_EV_Q_FLAG = 1
  ELSEIF TYPE == 2 THEN
    IF EVENT == 1 THEN
      INC G_PLAYER_STATUS[7], RESERVE
    ENDIF
  ELSEIF TYPE == 3 THEN
    IF EVENT == 1 THEN
      SPSET G_EV_ID_NOW, RESERVE
    ENDIF
  ELSEIF TYPE == 4 THEN
    D_BATTLE_BEFORE_SETTINGS EVENT
    G_SCENE_FLAG = 3
  ELSEIF TYPE == 10 THEN
    BEEP EVENT, RESERVE
  ENDIF

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

# ウィンドウクリエイター
DEF D_WINDOW_DRAW A_X, A_Y, A_W, A_H

  # カラー配列を定義
  VAR WC_BACK = RGB(0, 21, 151) # 背景色
  VAR WC_FRAME = RGB(128,126,129) # フレーム色
  VAR WC_HIGHLITE = RGB(255,255,255) # ハイライト色

  # 右下を計算
  VAR EX = A_W + A_X
  VAR EY = A_H + A_Y

  # 背景描写(塗りつぶし有り矩形)
  GFILL A_X, A_Y, EX, EY, WC_BACK

  # 矩形フレーム描写(塗りつぶし無し矩形)
  GBOX A_X-1, A_Y-1, EX+1, EY+1, WC_FRAME
  GBOX A_X-2, A_Y-2, EX+2, EY+2, WC_FRAME
  GBOX A_X-3, A_Y-3, EX+3, EY+3, WC_FRAME

  # ハイライトの角の部分(点)
  GPSET A_X-2, A_Y-2, WC_HIGHLITE
  GPSET A_X-2, EY+2, WC_HIGHLITE
  GPSET EX+2, A_Y-2, WC_HIGHLITE
  GPSET EX+2, EY+2, WC_HIGHLITE

  # ハイライトとフレームの直線部分
  GLINE A_X-3, A_Y-1, A_X-3, EY+1, WC_HIGHLITE
  GLINE A_X-4, A_Y-1, A_X-4, EY+1, WC_FRAME

  GLINE EX+3, A_Y-1, EX+3, EY+1, WC_HIGHLITE
  GLINE EX+4, A_Y-1, EX+4, EY+1, WC_FRAME

  GLINE A_X-1, A_Y-3, EX+1, A_Y-3, WC_HIGHLITE
  GLINE A_X-1, A_Y-4, EX+1, A_Y-4, WC_FRAME

  GLINE A_X-1, EY+3, EX+1, EY+3, WC_HIGHLITE
  GLINE A_X-1, EY+4, EX+1, EY+4, WC_FRAME

END

# メッセージ送り用
DEF D_EV_STOP_A_BTN
  IF G_BTN_PRESS_COUNT[4] == 1 THEN
    G_EV_Q_FLAG = 0
  ENDIF
END

# メッセージイベント
DEF D_MSG_DRAW A_X, A_Y, A_EVENT A_RESERVE
  IF A_EVENT == 1 THEN
    GPUTCHR A_X+5, A_Y+5, "プレイヤー" + G_MSG_DATAS[0]
  ELSEIF A_EVENT == 2 THEN
    GPUTCHR A_X+5, A_Y+5, G_MSG_DATAS[1] + G_ITEMS_NAMES[A_RESERVE] + G_MSG_DATAS[2]
  ELSEIF A_EVENT == 3 THEN
    GPUTCHR A_X+5, A_Y+5, G_MSG_DATAS[3]
  ENDIF
END

# データベースローダー
DEF D_DB_LOAD A_ID, A_LEN, A_ARY

  IF A_ID < 4 THEN
    VAR ARY_WORD = 0
  ELSE
    VAR ARY_WORD$ = ""
  ENDIF

  IF A_ID == 0 THEN
    RESTORE @PLAYER_INIT_STATUS
  ELSEIF A_ID == 1 THEN
    RESTORE @WINDOWSIZE
  ELSEIF A_ID == 2 THEN
    RESTORE @SYSTEMWORD
  ELSEIF A_ID == 3 THEN
    RESTORE @ENEMYPARAMS
  ELSEIF A_ID == 4 THEN
    RESTPRE @SELECTDATA
  ELSEIF A_ID == 5 THEN
    RESTORE @ITEMNAME
  ELSEIF A_ID == 6 THEN
    RESTORE @ENEMYNAME
  ELSEIF A_ID == 7 THEN
    RESTPRE @MESSAGEDATA
  ENDIF

  FOR G_I=0 to A_LEN-1
    IF A_ID < 4 THEN
      READ ARY_WORD
      PUSH A_ARY, ARY_WORD
    ELSE
      READ ARY_WORD$
      PUSH A_ARY, ARY_WORD$
    ENDIF
  NEXT
END

# ステータスウィンドウ描写
DEF D_STATUS_WINDOW_DRAW
  # ローカル配列定義
  VAR STATUS = ""
  DIM WS[0]
  # グローバル配列の参照を格納
  WS = G_WINDOW_SIZES

  # ウィンドウの描写
  D_WINDOW_DRAW WS[4], WS[5], WS[6], WS[7]

  # ステータス文字の描写
  FOR G_I = 0 TO G_SYSTEM_WORDS_LEN-1
    # ステータス文字の描写準備
    IF G_I == 0 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[0])
    ELSEIF G_I == 1 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[1])+"/"+STR$(G_PLAYER_STATUS[2])
    ELSEIF G_I == 2 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[3])
    ELSEIF G_I == 3 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[4])
    ELSEIF G_I == 4 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[5])
    ELSEIF G_I == 5 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[6])+"/999"
    ELSEIF G_I == 6 THEN
      STATUS = G_SYSTEM_WORDS[G_I]+":"+ STR$(G_PLAYER_STATUS[7])
    ENDIF

    # 実際のステータスの描写
    GPUCHR  WS[4]+5, WS[5]+5 + (G_I*10), STATUS
  NEXT
END

# ウィンドウトグル
DEF D_STATUS_WINDOW_TOGGLE
  # フラグを反転させる
  G_STATUS_FLAG = NOT G_STATUS_FLAG

  # フラグによって開くか閉じるかを決める
  IF STATUS_FLAG THEN
    D_STATUS_WINDOW_DRAW
  ELSE
    GCLS
  ENDIF
END

# ステータスモード
DEF D_STATUS_MODE
  # 初回にステータスウィンドウフラグをONにしておく
  IF NOT G_STATUS_FLAG THEN
    D_STATUS_WINDOW_TOGGLE
  ENDIF

  # Xボタン監視
  IF G_BTN_PRESS_COUNT[6] == 1 THEN
    D_STATUS_WINDOW_TOGGLE
  ENDIF

END


# バトルシーンループ
DEF BATTLE_MODE
  D_BATTLE_GAUGE_LOOP
  D_BATTLE_GUAGE_GRAPH
  D_BATTLE_ENEMY_ACTION
  D_BATTLE_SELECT_COMMAND
  D_BATTLE_ATTACK
END

# ゲージループ
DEF D_BATTLE_GAUGE_LOOP
  # プレイヤーゲージ
  IF G_BATTLE_STATUS[0] < 180 THEN
    INC G_BATTLE_STATUS[0]
  ELSEIF G_BATTLE_STATUS[2] == 0 THEN
    BEEP 7, 600
    INC G_BATTLE_STATUS[2]
  ENDIF

  # エネミーゲージ
  IF G_BATTLE_STATUS[1] < 360 THEN
    INC G_BATTLE_STATUS[1]
  ELSEIF G_BATTLE_STATUS[3] != 0 THEN
    BEEP 7, 600
    INC G_BATTLE_STATUS[3]
  ENDIF
END


# ゲージグラフィック表示
DEF D_BATTLE_GUAGE_GRAPH
  # 背景として下地を黒く塗りつぶす
  GFILL SPVAR(0,0)-2, SPVAR(0,1)+G_SZ+2, SPVAR(0,0)+G_SZ+2, SPVAR(0,1)+G_SZ+5, RGB(255,255,255)
  GFILL SPVAR(0,0)-1, SPVAR(0,1)+G_SZ+1, SPVAR(0,0)+G_SZ+1, SPVAR(0,1)+G_SZ+4, RGB(0,0,0)
  VAR INC_GUAGE = FLOOR( G_BATTLE_STATUS[0] / 180 * G_SZ )
  GLINE SPVAR(0,0) , SPVAR(0,1)+G_SZ, SPVAR(0,0) + INC_GUAGE , SPVAR(0,1)+G_SZ+3, 
END

# バトルゲージリセット
DEF D_BATTLE_GUAGE_RESET A_ACTOR
  IF A_ACTOR == 0 THEN
    G_BATTLE_STATUS[0] = 0
    G_BATTLE_STATUS[2] = 0
  ELSE
    G_BATTLE_STATUS[1] = 0
    G_BATTLE_STATUS[3] = 0
  ENDIF
  G_BATTLE_GUARD_FLAG[A_ACTOR] 
END

# プレイヤーの行動選択
DEF D_BATTLE_ATTACK
  VAR SELECT_SUCCESS = FALSE

  IF G_BATTLE_STATUS[2] == 1 THEN
    IF G_BTN_PRESS_COUNT[4] == 1 THEN

      IF G_CURSOR_N == 0 THEN
        BEEP 103, 300
        D_BATTLE_DAMAGE 0
        SELECT_SUCSESS = TRUE
      ELSE IF G_CURSOR_N == 1 THEN
        PRINT "ぼうぎょ!"
        BEEP 86, 600, 127
        D_BATTLE_GUARD 0
        SELECT_SUCSESS = TRUE
      ENDIF
      ELSE IF G_CURSOR_N == 2 THEN
        IF G_PLAYER_STATUS[7] >= 1 THEN
          PRINT "かいふく!"
          BEEP 106, 200
          D_BATTLE_CURE
          SELECT_SUCSESS = TRUE
        ENDIF
      ENDIF

      IF SELECT_SUCCESS THEN
        GCLS
        D_BATTLE_GUAGE_RESET 0
        G_CURSOR_N = 0
      ELSE
        BEEP 0, -500, 127
      ENDIF

    ENDIF
  ENDIF  
END

# プレイヤーのコマンドウィンドウ表示
DEF D_BATTLE_SELECT_COMMAND
  DIM WS = G_WINDOW_SIZES
  IF G_BATTLE_STATUS[2] == 1 THEN
    G_BATTLE_STATUS[2] = 2
    G_CURSOR_X = WS[8] + G_CURSOR_POS[0]
    G_CURSOR_X = WS[9] + G_CURSOR_POS[1]
    D_WINDOW_DRAW WS[8], WS[9], WS[10], WS[11]
    GPUTCHR WS[8] + G_CURSOR_POS[1] + (G_SZ/2), WS[9] + G_CURSOR_POS[0], "こうげき"
    GPUTCHR WS[8] + G_CURSOR_POS[1] + (G_SZ/2), WS[9] + (G_CURSOR_POS[1]*2) + (G_SZ/2), "ぼうぎょ"
    GPUTCHR WS[8] + G_CURSOR_POS[1] + (G_SZ/2), WS[9] + (G_CURSOR_POS[1]*3) + G_SZ, "かいふく"
    BREPEAT #UP, 30, 5
    BREPEAT #DOWN, 30, 5
  ELSEIF G_BATTLE_STATUS[2] == 2 THEN
    GPUTCHR G_CURSOR_X, G_CURSOR_Y, "|>",RGB(0,21,151)
    VAR B = BUTTON(1)
    IF (B AND #UP) > 0 THEN
      DEC G_CURSOR_Y, G_CURSOR_POS[2]
      DEC G_CURSOR_N
      IF G_CURSOR_Y < WS[9] + G_CURSOR_POS[1] THEN
        G_CURSOR_Y = WS[9] + G_CURSOR_POS[1] + (G_CURSOR_POS[2]*2)
        G_CURSOR_N = 2
      ENDIF
      BEEP 9, 400
    ELSEIF (B AND #DOWN) > 0 THEN
      INC G_CURSOR_Y, G_CURSOR_POS[2]
      INC G_CURSOR_N
      IF G_CURSOR_Y > WS[9] + G_CURSOR_POS[1] + (G_CURSOR_POS[2]*2) THEN
        G_CURSOR_Y = WS[9] + G_CURSOR_POS[1]
        G_CURSOR_N = 0
      ENDIF
      BEEP 9, 400
    ENDIF
    GPUTCHR G_CURSOR_X, G_CURSOR_Y, "|>",RGB(255,255,255)
  ENDIF  
END

# 戦闘準備関数
DEF D_BATTLE_BEFORE_SETTINGS A_ID
  # エネミーステータス初期化
  VAR ST_POS = (A_ID-1) * LEN(G_ENEMY_BATTLE_PARAMS)
  FOR G_I=0 TO LEN(G_ENEMY_BATTLE_PARAMS)-1
    G_ENEMY_BATTLE_PARAMS[G_I] = G_ENEMY_PARAMS[ G_I + ST_POS] 
  NEXT
END

# ダメージ計算
DEF D_BATTLE_DAMAGE A_ACTOR

  VAR DAMAGE = 0

  IF A_ACTOR == 0 THEN
    PRINT "エネミーHP @ダメージまえ:" + STR$(G_ENEMY_BATTLE_PARAMS[1])
    DAMAGE = G_PLAYER_STATUS[3] - ( G_ENEMY_BATTLE_PARAMS[3] + ( G_ENEMY_BATTLE_PARAMS[3] * G_BATTLE_GUARD_FLAG[A_ACTOR] ))
  ELSE
    PRINT "プレイヤーHP @ダメージまえ:" + STR$(G_PLAYER_STATUS[1])
    DAMAGE = G_ENEMY_BATTLE_PARAMS[2] - ( G_PLAYER_STATUS[4] + ( G_PLAYER_STATUS[4] * G_BATTLE_GUARD_FLAG[A_ACTOR] ))
  ENDIF

  # 最低でもダメージは1入るようにする
  IF DAMAGE <= 0 THEN
    DAMAGE = 1
  ENDIF

  IF A_ACTOR == 0 THEN
    DEC G_ENEMY_BATTLE_PARAMS[1], DAMAGE
    IF G_ENEMY_BATTLE_PARAMS[1] <= 0
      PRINT "エネミーをたおしたぞ!"
    ENDIF
    PRINT "エネミーHP @ダメージあと:" + STR$(G_ENEMY_BATTLE_PARAMS[1])
  ELSE
    DEC G_PLAYER_STATUS[1], DAMAGE
    IF G_PLAYER_STATUS[1] <= 0
      PRINT "GAME OVER!"
    ENDIF
    PRINT "プレイヤーHP @ダメージあと:" + STR$(G_PLAYER_STATUS[1])
  ENDIF
END


# ぼうぎょ関数
DEF  D_BATTLE_GURAD A_ID
  G_BATTLE_GUARD_FLAG[A_ID] = 1
END

# かいふく関数
DEF  D_BATTLE_CURE
  DEC G_PLAYER_STATUS[7]
  G_PLAYER_STATUS[1] = G_PLAYER_STATUS[2]
END

# エネミーの行動選択
DEF D_BATTLE_ENEMY_ACTION
  IF G_BATTLE_STATUS[3] == 1 THEN
    VAR ACTION_NUM = RND(10)

    IF ACTION_NUM < 8 THEN
      PRINT "エネミーのこうげき!"
      BEEP 40, 200
      D_BATTLE_DAMAGE 1
    ELSE
      PRINT "エネミーのぼうぎょ!"
      D_BATTLE_GUARD 1
    ENDIF
    D_BATTLE_GUAGE_RESET 1
  ENDIF
END