【プチコン講座】プレイヤーをRPGぽく操作してみよう:グリッド移動編
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は134日目になります。
前回「【プチコン講座】プレイヤーを表示して操作してみよう:ドット移動編」でマップをドット移動するところまでやりました。
ドット移動の2DRPGもあるのですが、スプライト同士もしくはオブジェクトで当たり判定をとるので、
ちゃんとつくらないと予期せぬすり抜けやバグが大量発生してしまうので中級者向けです。
最初は大人しくグリッド移動のRPGを作ってみましょう。
グリッド移動の作り方を覚えると、1画面RPGならず1画面アクションや1画面パズル等が作れるようになります。
アクションの場合はドット移動の方がいいかもしれませんが、
倉庫番のようなパズルゲームだとグリッド移動の方がストレスなくできますね。
でも、作りたいのはRPGなのでRPGの作り方を中心にやっていきますね。
それではプチコンでRPG作り第6回目を始めましょう。
グリッド移動についての考え方
実際にコードを書く前に少しグリッド移動についてお勉強をしましょう。
そんなに難しくないので、しっかりみておいてください。
プチコン3号とプチコンBIGはマップチップなどのBGはチップサイズ(3DSなら8×8でBIGなら16×16)で数値を1つずらせばチップサイズ分動きましたが、
スプライトの場合はチップ分ではなく、1ドットずつ動くような仕様になっています。
(プチコン初代とプチコンMK-IIは調べてないのでわからないです。)
予想ではありますが、これはプチコン4になっても変わらない仕様だと思います。
もし発売後仕様が違っていたら変更いたします。
グリッド移動とは
結論から言っておくと、エクセルをイメージしてもらうとわかりやすいです。
Excelを使ったことが無いという人は、読書感想文なんかで使う方眼紙をイメージしてください。
1つのマスがプレイヤーやNPCが居る位置になります。
前回作ったドット移動では、マスの中を移動できるようなイメージです。
一方グリッド移動はマスからマスへ移動するイメージですね。
プレイヤー(スプライト)は1ドットずつしか動かないことを先ほど紹介しました。
前回のコードを実行してくれた人はマップ画面をグリグリと移動出来たと思います。
あれはプレイヤーの座標をキーが押されている間は+1や-1をするというコードになっていました。
では、マップチップのサイズ分座標を加算もしくは減算したらどうでしょうか?
グリッド移動(仮)をしてみる
実際にコントローラー関数を改造してみましょう。
// コントローラー関数
DEF D_CONTROLLER
VAR B = BUTTON()
IF (B AND #UP) > 0 THEN SPVAR SP_PLAYER, 1, (SPVAR SP_PLAYER, 1)-SZ
IF (B AND #DOWN) > 0 THEN SPVAR SP_PLAYER, 1, (SPVAR SP_PLAYER, 1)+SZ
IF (B AND #LEFT) > 0 THEN SPVAR SP_PLAYER, 0, (SPVAR SP_PLAYER, 0)-SZ
IF (B AND #RIGHT) > 0 THEN SPVAR SP_PLAYER, 0, (SPVAR SP_PLAYER, 0)+SZ
SPOFS 0,X,Y
END
一番最後のところを1からSZに変更しています。
直接16と打ってくれてもかまいません。
それでは実行してみましょう。
……どうでしょうか?
前回の16倍速になっているのでシューティングのスピードアップを最大までしたような状態になりましたね。
ちゃんとマス目に移動はしているのでグリッドごとにプレイヤーは止まるのですが、
このままのスピードではまともにRPGとしてプレイできませんね。
速度は前回のままで、グリッドごとに移動するというのが望ましいです。
グリッド移動の考え方
では、速度を維持したままグリッドごとに移動するにはどうしたらいいでしょうか?
ネタバレすると、1回方向キーが押されたら隣のマスまでプレイヤーを移動し続ける。
ということです。
もう少し詳しく書くと
- キーを押すとプレイヤーが移動開始する
- プレイヤーが移動している最中はキー操作を無効にする
- 移動が完了したらキー操作を可能に戻す
このことから、移動中かどうかのフラグ作成が必要になりますね。
あとはどの方向に進んでいるのかも知る必要があります。
移動中はキー受付なし、移動していない時はキー受付OKという風にIFで切り分けます。
キー受付なしの時はプレイヤーが次のグリッドまで動き続けるので
移動処理を加えておきます。
実際に書くとこうなります。
SPVAR SP_PLAYER, 2, 0 // SP変数2は移動フラグ用
SPVAR SP_PLAYER, 3, 10 // SP変数3は向き用
~~~省略~~~
// コントローラー関数
DEF D_CONTROLLER
IF SPVAR(SP_PLAYER, 2) != 1 THEN
VAR B = BUTTON()
IF (B AND #UP) > 0 THEN
D_DIRECTION_MOVE(#UP)
ELSEIF (B AND #DOWN) > 0 THEN
D_DIRECTION_MOVE(#DOWN)
ELSEIF (B AND #LEFT) > 0 THEN
D_DIRECTION_MOVE(#LEFT)
ELSEIF (B AND #RIGHT) > 0 THEN
D_DIRECTION_MOVE(#RIGHT)
ENDIF
ELSE
D_GRID_MOVE SP_PLAYER, (SPVAR 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)%SZ == 0 && (SPVAR SPRITE_NO, 1)%SZ == 0 THEN
SPVAR SPRITE_NO, 2, 0
ENDIF
END
これでグリッド移動ができるようになったはずです!
かなりRPGぽくなりましたね!
しかしすり抜けてしまうのは、衝突判定を全く設定していないからです(笑)
それではコードを解説していきましょう。
移動中フラグと向いている方向について
SPVAR SP_PLAYER, 2, 0
とSP変数2個目に0を入れています。
まずは0をつっこんでゲーム開始時は移動中じゃないということを記しています。
コントローラー関数のところでこの移動中フラグで処理を切り分けていますね。
0が止まっている状態でそれ以外は移動中という風にしています。
止まっている時に上下左右のキーが押されたら「D_DIRECTION_MOVE」関数を読んで、
向いている方向をスプライト変数に入れて移動中フラグを1を入れます。
押されたキーによって向いている方向の数値をスプライト変数3つ目に代入しておきます。
向きは0~3で判断してもいいのですがせっかくプチコン側が上下左右の判断できる数値を作ってくれているので、
活用したほうがいいでしょう。
移動中の処理
移動中になったら移動中が終わるまでは「D_GRID_MOVE」を呼び続けます。
向いている方向に進み続け、X座標もY座標も割った余りが0になっていれば移動完了です。
割った余りを算出するには「MOD」を使います。
現在地をチップの大きさで割った余りが0になれば移動完了という風にすればOKです。
スプライト変数の補足
スプライト変数の0はx座標、1はy座標、2は移動中かどうか、3は向いている方向。
にしています。
変数には自由に値を入れられるのでルールを決めておかないと、
このスプライトは1番目に何が入っていたかなって迷ってしまう無駄が発生します。
自分ルールを決めておけば取り出しやすくていいですね。
──プチコンでのユーザー定義関数の中でユーザー定義関数を呼ぶとエラーが出て、
仕様上関数内では関数を呼べないのかと思いましたが、どうやら定義するときに仮引数部分にカッコをつけていると
戻り値が無いとエラーがでるようでした。
仮引数のところのカッコを取り外したらすんなりと関数から関数を呼び出せました。
公式にちょっと書いててほしかったですねこれ……
Twitterでフォロワーの方(名前出していいのかわからないので伏せておきます)に助言いただけなければ今日のブログが中途半端な状態でアップするところでした。
Web言語の場合は返り値がなくてもカッコをつけても問題なかったので毒されていましたね。
見えないところで勝手に戻り値を設定してくれていたのでしょう。
この場を借りてもう一度お礼申し上げます。
ありがとうございました!
それでは最後に完成版ソースコードを載せておきます。
それでは。
ACLS
# 変数定義
VAR G_STOP_FLAG = TRUE # ゲームループフラグ
VAR I # 汎用変数
VAR OX = 0, OY = 0 # BG読み込みのオフセット
VAR SZ=16, MW, MH # チップサイズ、マップ幅、マップ高さ
VAR BGW = CEIL(400/SZ), BGH = CEIL(240/SZ) # BG読み込み準備
DIM MAP[0] # マップレイヤー4枚+イベントレイヤー分
# 変数定義
VAR SP_PLAYER = 0 # PLAYERスプライトNo.
DIM SP_ANIM_NO[10] # スプライトの初期定義Noを格納する配列
# 配列初期値設定
SP_ANIM_NO[0] = 500 # PLAYERの初期定義Noを代入
# スプライト表示
SPSET SP_PLAYER, SP_ANIM_NO[SP_PLAYER] # プレイヤー用スプライトの設定
SPVAR SP_PLAYER, 0, SZ*1 # プレイヤースプライト用変数0 => X座標
SPVAR SP_PLAYER, 1, SZ*0 # プレイヤースプライト用変数1 => Y座標
SPVAR SP_PLAYER, 2, 0 # 移動中フラグ
SPVAR SP_PLAYER, 3, 10 # 向き設定:最初は下向き
SPOFS SP_PLAYER, SPVAR(SP_PLAYER,0), SPVAR(SP_PLAYER,1) # 初期位置設定
# マップデータ準備
LOAD "DAT:TEST", MAP, 0 # マップデータのロード
MW = SHIFT(MAP) # 読み込んだデータ配列の0個目を配列から切り離して取得(1638415という数字が入ってる)
MH = MW AND &HFFFF : MW = MW >> 16 AND &HFFFF # 取り出したデータからシフト演算やらビット演算をする
# マップデータ描写
FOR I=0 TO 3
BGSCREEN I, BGW, BGH, SZ # 1画面分のBG
BGLOAD I, -OX, -OY - I * MH, MW, MH * ( I + 1), MAP # マップ描写
NEXT
# ゲームループ
WHILE G_STOP_FLAG
D_CONTROLLER # コントローラー関数呼び出し
VSYNC 1 # 垂直同期
WEND
# コントローラー関数
DEF D_CONTROLLER
IF SPVAR(SP_PLAYER, 2) != 1 THEN
VAR B = BUTTON()
IF (B AND #UP) > 0 THEN
D_DIRECTION_MOVE(#UP)
ELSEIF (B AND #DOWN) > 0 THEN
D_DIRECTION_MOVE(#DOWN)
ELSEIF (B AND #LEFT) > 0 THEN
D_DIRECTION_MOVE(#LEFT)
ELSEIF (B AND #RIGHT) > 0 THEN
D_DIRECTION_MOVE(#RIGHT)
ENDIF
ELSE
D_GRID_MOVE SP_PLAYER, (SPVAR 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)%SZ == 0 && (SPVAR SPRITE_NO, 1)%SZ == 0 THEN
SPVAR SPRITE_NO, 2, 0
ENDIF
END