【プチコン講座】イベントシステム構築:イベント配置とアニメ編

プチコン

プチコン RPG イベント

ソースコードを少し変更しました。グローバル変数には「G_」を付けてください。

今回までの最終コードは記事最下部に乗せてあります。

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

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

前回「【プチコン講座】RPGのイベントシステムの作り方」でイベントシステムの作り方の全容をお話ししました。

今回からRPGイベントシステムの実際のコーディングに入ります。

イベントシステム構築第一弾は見た目の部分となるイベント用スプライトを複数配置とアニメーションをしてみましょう。

やることはプレイヤーを配置した処理を関数にして値を渡してスプライトを簡単に生成できるようにするいわば復習です。

実はここから新しいことはあまりやりません。大体今までやってきたことの応用と復習になります。

まずは仮で良いのでイベント用配列を簡単に作ってその配列を元にスプライトを生成してください。

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

  1. プレイヤーキャラクターをアニメーションしてみよう
  2. アニメーション用の配列を作ろう
  3. スプライトの設定を関数にして置き換えてみよう
  4. イベントを表示するための配列を作ろう
  5. イベント配列をもとに画面に表示してみよう

プレイヤーキャラクターをアニメーションしてみよう

スプライト表示を関数にまとめてしまう前にまずはアニメーションの設定をしてみましょう。

今のままだと止まったまま移動するので味気ないですね。

アニメーションを設定するのはプチコンではかなり簡単になっていますので実際にコードを打ち込んでみましょう。


VAR F=20
SPANIM SP_PLAYER, F,500, F,501, F,502, F,503, 0

これだけです!

実際に動かしてみましょう。

……プレイヤーが足踏みを始めました。

そのまま移動してみると歩いているように見えますね。

カニ歩きをどうするかは自由

ですがこのままでは横に移動しようが上に移動しようが下を向いたままの移動になるのでおかしいですね。

あのドラゴンクエストでも初代はカニ歩きのまま発売しました(容量的な問題ですが)

当時はファミコンという容量との戦いもあってのことなのですが、
今の時代の性能でカニ歩きで発売しようものなら手抜きとしてしか見られません。

今回の講座ではカニ歩きのまま制作を進めていく予定です。

もし気になるのであれば各自、自力で実装してみ下さい。

方法だけ書いておきます。

    1. 上下左右のスプライト番号を格納したアニメーションの配列を作っておく
    1. SPANIM命令を関数化して、引数に方向とスプライトNo.を受け取る。
    1. キーを押した時に衝突判定の前に2で作った関数に方向とスプライトNo.を渡す。

スプライト番号は各自スマイルツールで確認してみてください。

これでカニ歩きは治るはずです。

アニメーション用の配列を作ろう

プレイヤーのスプライトNo.を配列にしていた理由が今明かされます!

プチコンのスプライトは基本的に同じ方向のアニメーションが4つずつ用意されています。

なのでアニメーションの起点さえ配列に組み込んでおけば、+1・+2……と増やせばアニメーションできます。

アニメーション用の配列は1番目はプレイヤーで2番目以降はイベント用にすればOKですね。

この時、イベントidと配列の添え字を関連付けておくとアクセスが楽になります。

今回作るのはプレイヤー一人の簡単なRPGなのでこれでもいいですが、
仲間とかが出てくるRPGの場合はプレイヤーとイベントは配列を分けたほうが良いでしょう。

自分の好きなようにしてくださって構いませんが、最初のうちは下記の通りに配列を作り直してみてください。

DIM SP_ANIM_NO[6]

COPY SP_ANIM_NO, @SP_TX_LIST
@SP_TX_LIST
DATA 500,1040,920,1000,980,1020

javascriptのように配列に値を代入できないようなので、今回はラベルを使うことにしました。
GOTOではないのでよしとします。

プチコンでは配列を一括で入れたい場合はこうやるのがベストっぽいですね。

スプライトの設定を関数にして置き換えてみよう

これでスプライトに関する一通りのことは完成したので、
現在プレイヤーを表示しているスプライトの一連の処理を関数化してしまいましょう。


DEF D_SP_SETUP A_NO, A_X, A_Y
  VAR F = 20
  VAR ANIM = SP_ANIM_NO[A_NO]

  SPSET A_NO ANIM
  SPSET A_NO 0, G_SZ * A_X
  SPSET A_NO 1, G_SZ * A_Y
  SPSET A_NO SPVAR(A_NO,0), SPVAR(A_NO,1)

  SPANIM A_NO, "I", F,ANIM, F,ANIM+1, F,ANIM+2, F,ANIM+3, 0 
END

関数がちゃんと動くか確認したいのでプレイヤー用の処理を書いていたところに、
先ほど作った関数にスプライトNoを渡してみましょう。


D_SP_SETUP SP_PLAYER, 1, 0

問題なく動いていればOKです。

今後スプライトを表示するときは基本的にこの関数を呼び出します。

特殊な表示の仕方をしたい場合は新たな関数を作るか、既存の関数の中で条件分岐をしてください。

イベントを表示するための配列を作ろう

これで自由にスプライトを表示できる関数ができましたので、
イベント用配列を作ってループで回せば画面にイベントが表示されます。

それでは今回表示したいイベントのidだけを含めた配列を作ってみましょう。

イベントidなんて連番だからFORを使って初期値を1にして表示したらいいんじゃない?

って考えが出た人は大変素晴らしいです!

何も考えず与えられたことだけをしているとプログラミングは上達しません。

しかし間違いではないですが、それをやってしまうと修正するときが大変です。

もし何かの都合でこのイベント3番目は消しちゃえ!って時に修正したとしましょう。

その時にFORで回していると消した3番目を表示するときに4番目が表示されてしまい、
配列の範囲外に行ってしまいます。

ちゃんとイベントIDで見ていれば飛ばしてもそのIDになるので修正箇所を減らせますね。

私がやろうとしていることが100%正しい訳じゃないので、こっちの方が効率良いし修正点も少ない!
っていうのを見つけたらぜひ自力で実装してみてください。

イベント配列はIDと位置を保持してみよう

IDだけだと座標がわからないのでX座標とY座標を保持してみましょう。

1次元配列だとわかり辛いかもしれませんが、コーディングの際にスペースを2個いれておけば区切りが分かりやすいですね。

DIM EV_ID[15]

COPY EV_ID, @EV_ID_LIST
@EV_ID_LIST
DATA 1,5,2,  2,6,2,  3,7,2,  4,8,2,  5,9,2

左からイベントID・X座標・Y座標となっています。

イベント配列をもとに画面に表示してみよう

これで準備は整いました。

イベントIDをもとにスプライトを生成し、画面上にイベントを配置してみましょう。


FOR I=0 TO LEN(EV_ID)-1 STEP 3
  SP_SETUP EV_ID[I], EV_ID[I+1], EV_ID[I+2]
NEXT

FORを使って配列を回しています。

0~は配列の長さ-1で3つ飛ばしの設定ですね。

LENは配列の数を返すので-1にしておかないと配列の添え字を越えてしまいます。

STEP3にしているのはID・X座標・Y座標にしているからですね。

それでは実行してみましょう。

プチコン イベント表示

……どうでしょうか?
プレイヤーもモンスターも足踏みをしていてなんだか活き活きしてますね!

ですがこのままだと画面に表示されているだけですり抜けられますし、
イベント内容も用意していないので何もできません。

これではゲームとは言えませんね。

次回はイベントの衝突判定を作ってみましょう。

イベントがいるところはプレイヤーは重なることができないようにしなければいけません。

毒の沼や床系のイベントの場合は重ならなきゃいけないのですが、
今回はトラップなどは実装する予定はありません。

イベントの衝突判定ができてしまえば、あとは隣接して方向があっている状態で
Aボタンを押したら何かをするという処理を加えれば簡単なイベントシステムの出来上がりです。

余裕があれば一気に簡単なイベントシステム作りまではいきたいですが、
長くなりそうだったら記事を分けさせていただきますのでご了承ください。

恒例の今回のソースコード完全版を置いておきます。

ソースコードを少し変化させました。
グローバル変数にすべて「G_」を付けていることに気を付けてください

それでは。

ACLS

# 変数定義
VAR G_STOP_FLAG = TRUE # ゲームループフラグ
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[6] # スプライトアニメーション初期値変数 添え字=ID
DIM G_EV_ID[15] # イベント用情報の配列


# アニメーション配列初期値代入
COPY G_SP_ANIM_NO, @SP_TX_LIST
@SP_TX_LIST
DATA 500,1040,920,1000,980,1020

# イベントID,X座標,Y座標代入
COPY G_EV_ID, @EV_ID_LIST
@EV_ID_LIST
DATA 1,5,2,  2,6,2,  3,7,2,  4,8,2,  5,9,2

# マップデータ準備
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 3
  D_SP_SETUP G_EV_ID[G_I], G_EV_ID[G_I+1], EV_ID[G_I+2]
NEXT

# ゲームループ
WHILE G_STOP_FLAG
  D_CONTROLLER # コントローラー関数呼び出し
  VSYNC 1 # 垂直同期
WEND

# コントローラー関数
DEF D_CONTROLLER
  IF SPVAR(SP_PLAYER, 2) != 1 THEN
    VAR B = BUTTON()
    VAR POS = D_GET_MAP_POSITION(SPVAR(SP_PLAYER,0), SPVAR(SP_PLAYER,1), 1)
    IF (B AND #UP) > 0 THEN
      IF D_CHECK_COLLISION(POS, #UP) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #UP
      ENDIF
    ELSEIF (B AND #DOWN) > 0 THEN
      IF D_CHECK_COLLISION(POS, #DOWN) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #DOWN
      ENDIF
    ELSEIF (B AND #LEFT) > 0 THEN
      IF D_CHECK_COLLISION(POS, #LEFT) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #LEFT
      ENDIF
    ELSEIF (B AND #RIGHT) > 0 THEN
      IF D_CHECK_COLLISION(POS, #RIGHT) == 1 THEN
        D_DIRECTION_MOVE SP_PLAYER, #RIGHT
      ENDIF
    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) 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_POS, A_DIRECTION)
  VAR DIRECT = 0
  VAR SUM_POS = 0

  IF A_DIRECTION == #UP THEN
    DIRECT = -MW
  ELSEIF A_DIRECTION == #DOWN THEN
    DIRECT = MW
  ELSEIF A_DIRECTION == #LEFT THEN
    DIRECT = -1
  ELSEIF A_DIRECTION == #RIGHT THEN
    DIRECT = 1
  ENDIF

  SUM_POS = A_POS + DIRECT

  IF G_MAP[SUM_POS] == 0 THEN
    RETURN 1
  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
  SPSET A_NO 0, G_SZ * A_X
  SPSET A_NO 1, G_SZ * A_Y
  SPSET A_NO SPVAR(A_NO,0), SPVAR(A_NO,1)

  SPANIM A_NO, "I", F,ANIM, F,ANIM+1, F,ANIM+2, F,ANIM+3, 0 
END