【プチコン4講座】RPGを作るためのグリッド移動の実装

プチコン4

プチコン グリッド移動

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

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

前回「【プチコン4講座】RPG作成講座第1回~第3回のコードの最適化」でコードを最適化しました。

現状はドット移動のままなので、あまりRPGぽくありません。

マス目移動が理想なので、今回はグリッド移動を実装することにしましょう。

グリッド移動にすると厄介な衝突判定がかなり楽になりますので、最初はグリッド式のRPG作成をオススメします。

基本的にプチコンBIGでやったこととほぼ同じなので、グリッド移動とはなんぞや?

って方は「【プチコン講座】プレイヤーをRPGぽく操作してみよう:グリッド移動編」を参照してください。

基本的に過去記事をみなくてもこの記事で完結できるようにしていますので、
どうしてもわからなかった場合や知識を深めたい人は上記のアドレスからぜひご閲覧ください。

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

  1. コントローラー関数からキャラクターの移動処理を切り放そう
  2. グリッド移動を実装しよう

コントローラー関数からキャラクターの移動処理を切り放そう

前回のコード最適化でやればよかったのですが混乱しそうなので、
今回からキャラクターの操作をコントローラー関数から切り離そうと思います。

プチコン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