【プチコン4講座】RPGを作るためのグリッド移動の実装
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は168日目になります。
前回「【プチコン4講座】RPG作成講座第1回~第3回のコードの最適化」でコードを最適化しました。
現状はドット移動のままなので、あまりRPGぽくありません。
マス目移動が理想なので、今回はグリッド移動を実装することにしましょう。
グリッド移動にすると厄介な衝突判定がかなり楽になりますので、最初はグリッド式のRPG作成をオススメします。
基本的にプチコンBIGでやったこととほぼ同じなので、グリッド移動とはなんぞや?
って方は「【プチコン講座】プレイヤーをRPGぽく操作してみよう:グリッド移動編」を参照してください。
基本的に過去記事をみなくてもこの記事で完結できるようにしていますので、
どうしてもわからなかった場合や知識を深めたい人は上記のアドレスからぜひご閲覧ください。
それではプチコン4でRPG作りその4回目始めましょう。
コントローラー関数からキャラクターの移動処理を切り放そう
前回のコード最適化でやればよかったのですが混乱しそうなので、
今回からキャラクターの操作をコントローラー関数から切り離そうと思います。
プチコンBIGのときはそのままコントローラー関数を改造していたのですが、
コントローラー関数は基本的にキーの操作だけを管理するものとして扱いたいです。
キーは押したか離されたか押され続けてるかのどれかを監視するだけですね。
その状態を元にシーン別に処理を変化させるというのがゲームの作り方の基本になります。
それでは一旦プレイヤーの移動処理をコントローラーから切り離してみましょう。
'──────────────────────────
' ┠─ コントローラー関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' コントローラー関数
def D_Controller
var B = BUTTON(0)
# #B_RRIGHTは右コントローラーの右ボタンつまりAボタンの意
var BTN_UP = 1 << #B_LUP
var BTN_DOWN = 1 << #B_LDOWN
var BTN_LEFT = 1 << #B_LLEFT
var BTN_RIGHT = 1 << #B_LRIGHT
var direct = -1
if (B and BTN_UP) > O then
direct = #B_RUP
endif
if (B AND BTN_DOWN) > O then
direct = #B_RDOWN
endif
if (B AND BTN_LEFT) > O then
direct = #B_RLEFT
endif
if (B AND BTN_RIGHT) > O then
direct = #B_RRIGHT
endif
D_SpriteGridMove direct
end
'──────────────────────────
' ┠─ ▼ スプライト関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' スプライト初期化
def D_SpInitialize
~~ 省略 ~~
end
' スプライトの移動
def D_SpriteGridMove A_Direct
case A_Direct
when #B_RUP
SPVAR 0, "Y", SPVAR(0, "Y") - 1
when #B_RDOWN
SPVAR 0, "Y", SPVAR(0, "Y") + 1
when #B_RLEFT
SPVAR 0, "X", SPVAR(0, "X") - 1
when #B_RRIGHT
SPVAR 0, "X", SPVAR(0, "X") + 1
endcase
# キャラクターの移動
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
end
まだグリッド移動ではありませんが、関数の名前はスプライトグリッドムーブにしています。
これでコントローラー関数からスプライトの移動を切り離しました。
しかし結局コントローラーの中で値を設定して関数に渡しているのでスタイリッシュではありません。
ここは少しずつ順を追って綺麗にしていきましょう。
一度問題なくドット移動できているか試しましょう。
グリッド移動を実装しよう
それではドット移動からグリッド移動に変更しましょう。
やることはキー操作をした時にdirect変数に1〜4の値を渡したと思います。
その値によって移動をきりわけていましたね。
ドット移動の場合は毎フレーム移動判定をしていましたが、
グリッド移動の場合は一回移動が発動したら次のマスまで動き続けるという仕組みになっています。
なのでなので以下のフラグが必要になります。
- 移動中かどうか
- どっちの方向を向いているか
グローバル変数でもいいですが、せっかくなのでスプライト変数を使いましょう。
それでは早速作っていきます。
'──────────────────────────
' ┠─ ▼ スプライト関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' スプライト初期化
def D_SpInitialize
' プレイヤーキャラクターの描写と初期化【追加部分】
SPSET 0, 500
SPVAR 0, "X", 0
SPVAR 0, "Y", 0
SPVAR 0, "Direct", 0
SPVAR 0, "MoveFlag", 0
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
end
' スプライトの移動
def D_SpriteGridMove A_Direct
if SPVAR(0,"MoveFlag") == 1 then
case SPVAR(0,"Direct")
when #B_RUP
SPVAR 0, "Y", SPVAR(0, "Y") - 1
when #B_RDOWN
SPVAR 0, "Y", SPVAR(0, "Y") + 1
when #B_RLEFT
SPVAR 0, "X", SPVAR(0, "X") - 1
when #B_RRIGHT
SPVAR 0, "X", SPVAR(0, "X") + 1
endcase
# キャラクターの移動
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
// 割った余りが0になったら動きを止める
if SPVAR(0, "X") MOD #CSZ == 0 && SPVAR(0, "Y") MOD #CSZ == 0 then
SPVAR 0, "MoveFlag", 0
endif
else
if A_Direct > -1 then
SPVAR 0, "Direct", A_ Direct
SPVAR 0, "MoveFlag", 1
endif
endif
end
1回でも上下左右どれか押されたらその方向にグリッド間の移動が終わるまで動き続けます。
その中でも割った余りという処理がとても大事になります。
チップサイズで割り切れるところのみが余りが0になるので、
XとYがどちらも割った余りが0であれば移動フラグを0にするという風にすればグリッド移動が完成します。
移動フラグが0のときのみプレイヤーの方向を保持するようにすれば、
移動中は何を押しても方向変換はできないので安心ですね。
━━流石に同じことを3回もやっていると理解度が半端なくあがりますね。
昔はグリッド移動の割った余りという概念がよくわからなくて苦戦していましたが、
今では問題なく理解できるようになっています。
割った余りというのは小学生でもできますが、
ゲームを作る上でこれを使うという発想がでてこなかったですね。
最後に今回の完成版ソースコードを置いておきます。
それでは
'──────────────────────────
' ▼ 動作モード設定:変数宣言は必須
'──────────────────────────
OPTION STRICT
'──────────────────────────
' ▼ 画面のクリア
'──────────────────────────
ACLS
'──────────────────────────
' ▼ 定数・構造体の定義
'──────────────────────────
CONST #CSZ = 16
'──────────────────────────
' ▼ 変数・配列の定義
'──────────────────────────
' ┠─ 汎用変数
'━━━━━━━━━━━━━━━━━━━━━━━━━━
var G_I = 0, G_J = 0, ¥
G_BGW = 0, G_BGH = 0
' ゲームループを制御するフラグ
var G_GameLoopFlag = #TRUE
'──────────────────────────
' ┠─ マップシーン用配列
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' 空配列宣言
DIM G_MapData[]
'──────────────────────────
' ▼ プログラム初期化処理
'──────────────────────────
D_GAME_INITIALIZE
'──────────────────────────
' ゲームループ開始
'──────────────────────────
loop
' コントローラー関数呼び出し
D_Controller
' ゲームループフラグ監視
if !G_GameLoopFlag then BREAK
' フレームレート安定
VSYNC 1
endloop
'──────────────────────────
' ▼ 関数の定義
'──────────────────────────
' ┠─ ▼ 初期化関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
DEF D_GameInitialize
' マップデータのロード
D_MapInitialize "MAP001.DAT"
' マップデータの描写
'D_MapDraw
' スプライトの初期化
D_SpInitialize
END
'──────────────────────────
' ┗━┓ ▼ マップ関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
DEF D_MAP_INITIALIZE 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)
# #B_RRIGHTは右コントローラーの右ボタンつまりAボタンの意
var BTN_UP = 1 << #B_LUP
var BTN_DOWN = 1 << #B_LDOWN
var BTN_LEFT = 1 << #B_LLEFT
var BTN_RIGHT = 1 << #B_LRIGHT
var direct = -1
if (B and BTN_UP) > O then
direct = #B_RUP
endif
if (B AND BTN_DOWN) > O then
direct = #B_RDOWN
endif
if (B AND BTN_LEFT) > O then
direct = #B_RLEFT
endif
if (B AND BTN_RIGHT) > O then
direct = #B_RRIGHT
endif
D_SpriteGridMove direct
end
'──────────────────────────
' ┠─ ▼ スプライト関連
'━━━━━━━━━━━━━━━━━━━━━━━━━━
' スプライト初期化
def D_SpInitialize
' プレイヤーキャラクターの描写と初期化【追加部分】
SPSET 0, 500
SPVAR 0, "X", 0
SPVAR 0, "Y", 0
SPVAR 0, "Direct", 0
SPVAR 0, "MoveFlag", 0
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
end
' スプライトの移動
def D_SpriteGridMove A_Direct
if SPVAR(0,"MoveFlag") == 1 then
case SPVAR(0,"Direct")
when #B_RUP
SPVAR 0, "Y", SPVAR(0, "Y") - 1
when #B_RDOWN
SPVAR 0, "Y", SPVAR(0, "Y") + 1
when #B_RLEFT
SPVAR 0, "X", SPVAR(0, "X") - 1
when #B_RRIGHT
SPVAR 0, "X", SPVAR(0, "X") + 1
endcase
# キャラクターの移動
SPOFS 0, SPVAR(0,"X"), SPVAR(0,"Y")
// 割った余りが0になったら動きを止める
if SPVAR(0, "X") MOD #CSZ == 0 && SPVAR(0, "Y") MOD #CSZ == 0 then
SPVAR 0, "MoveFlag", 0
endif
else
if A_Direct > -1 then
SPVAR 0, "Direct", A_ Direct
SPVAR 0, "MoveFlag", 1
endif
endif
end