ターン制RPGの作り方#2|キャラクターと敵のデータをScriptableObjectで作ろう

再生すると、左に3人、右にこぼれたインク・紙ひこうき・本の虫が並ぶ。右のInspectorでは、BattleのBattle LineupのPartyに3人、Enemiesに3体のデータが入っている

RPGには、仲間や敵がたくさん出てきます。名前・HP・攻撃力・すばやさ・・・。それをスクリプトの中に直接書いていたら、敵を1体増やすたびにコードを書き換えることになります。今回は、キャラクター1人分・敵1体分の情報をScriptableObjectのアセットにして、データを差し替えるだけで並ぶキャラクターと敵が変わる形を作ってみたいと思います!

目次

今回作るもの

  • 6つの能力をまとめたStats
  • 仲間1人分のデータCharacterDataと、敵1体分のデータEnemyData
  • 4人の仲間と、地下1階の敵3体のデータ
  • データから見た目を作るUnitViewと、並べるBattleLineup
スクリプト役割付ける場所
StatsHP・MP・こうげき・ぼうぎょ・すばやさ・まりょく(付けない。ほかのデータの中で使う)
CharacterData仲間1人分の名前・待機の絵・能力・成長(付けない。アセットを作る)
EnemyData敵1体分の名前・絵・能力・経験値・お金(付けない。アセットを作る)
UnitViewデータの絵を表示する3人の仲間・敵のプレハブ
IdleBounce待機のコマ(#1で作ったものに1つ足す)3人の仲間
BattleLineupデータから仲間と敵を並べるBattle

ScriptableObjectの基本は、12-8 ScriptableObjectでデータを作ろうで紹介しています。

キャラクターに何が要るか考えよう

作り始める前に、仲間と敵がそれぞれ何を持っていればいいかを書き出してみます。

仲間敵
名前○○
絵待機の3コマ(攻撃やダメージの絵は、使う回で足す)1枚
能力Lv1の能力能力
成長1レベル上がるごとに増える分-
倒したとき-経験値・お金

能力は、仲間も敵も同じ6つです。

能力変数名使いみち
HPmaxHp0になると倒れる
MPmaxMpまほうを使うと減る(#13)
こうげきattackたたかうのダメージ(#7)
ぼうぎょdefense受けるダメージを減らす
すばやさspeed行動の順番(#9)・にげる(#11)
まりょくmagicまほうの強さ(#13)

同じ6つを仲間と敵の両方で使うので、6つをまとめた小さなクラスを1つ作っておきます。

スライム

まほうやどうぐは、まだ入れないんですか?

フェアリー

使う回で足していくよ!最初から全部入れると、どこで使うのか分からない項目ばかりになっちゃうからね

能力をまとめたStatsを作ろう

  • TurnBasedRpg/Scriptsに「Stats」スクリプトを作り、中身を次のように書き換える
// HP・MP と 4つの能力をまとめたもの(仲間と敵の両方で使う)
[System.Serializable]
public class Stats
{
    public int maxHp;   // HP
    public int maxMp;   // MP
    public int attack;  // こうげき
    public int defense; // ぼうぎょ
    public int speed;   // すばやさ
    public int magic;   // まりょく
}
  • [System.Serializable]
    • このクラスを、ほかのスクリプトやScriptableObjectの中に入れたときに、Inspectorに中身が表示されるようにする印
    • MonoBehaviourでもScriptableObjectでもない、普通のクラスです
  • public の変数
    • 能力は「読むだけ」ではなく、#12のレベルアップや#19の装備で足し算するので、ここではpublicにしています

Inspectorでは、変数名のmaxHpが「Max Hp」のように、大文字の前で区切って表示されます。日本語の名前にしたいときは、変数名ではなく[InspectorName]などで表示だけを変える方法もありますが、このシリーズでは英語のままにしておきます。

CharacterDataとEnemyDataを作ろう

続けて、仲間のデータと敵のデータを作ります。

  • 「CharacterData」スクリプトを作る
using UnityEngine;

// 仲間1人分のデータ
[CreateAssetMenu(fileName = "NewCharacter", menuName = "TurnBasedRpg/Character Data")]
public class CharacterData : ScriptableObject
{
    [SerializeField] private string characterName;
    [SerializeField] private Sprite[] idleFrames; // 待機の3コマ(walk_61・62・63)
    [SerializeField] private Stats baseStats;     // Lv1の能力
    [SerializeField] private Stats growth;        // 1レベル上がるごとに増える分

    public string CharacterName => characterName;
    public Sprite[] IdleFrames => idleFrames;
    public Stats BaseStats => baseStats;
    public Stats Growth => growth;
}
  • 「EnemyData」スクリプトを作る
using UnityEngine;

// 敵1体分のデータ
[CreateAssetMenu(fileName = "NewEnemy", menuName = "TurnBasedRpg/Enemy Data")]
public class EnemyData : ScriptableObject
{
    [SerializeField] private string enemyName;
    [SerializeField] private Sprite sprite;
    [SerializeField] private Stats stats;
    [SerializeField] private int exp;  // 倒したときの経験値
    [SerializeField] private int gold; // 倒したときのお金

    public string EnemyName => enemyName;
    public Sprite Sprite => sprite;
    public Stats Stats => stats;
    public int Exp => exp;
    public int Gold => gold;
}
  • [CreateAssetMenu]
    • Projectウインドの右クリック>Create に、このデータを作るメニューを足す。menuNameの「/」で区切った分だけ、メニューが階層になる
  • [SerializeField] private と、=>のプロパティ
    • Inspectorでは書き換えられるけど、ほかのスクリプトからは読むだけにしておく形。バトル中にうっかりデータのHPを減らしてしまう事故を防げます(バトル中のHPは、#7で別に持ちます)
  • Stats baseStats / Stats growth
    • さっき作ったStatsを、Lv1の能力と成長の2つ分使っている

ScriptableObjectのアセットは、再生中にスクリプトから値を書き換えると、再生を止めても書き換わったまま残ります。バトルで減ったHPなどをデータに書き込まないように、データは「読むだけ」にしておきましょう。

4人と、最初の敵3体を作ろう

データのファイル名(Slime・SpilledInkなど)は英語、画面に出す名前(スライム・こぼれたインク)は日本語、と分けておきます。ファイル名は、#25のセーブでデータを探すときの目印にも使います。

  • TurnBasedRpg/Dataの中に、Characters・Enemiesの2つのフォルダを作る
  • Charactersフォルダで右クリック>Create>TurnBasedRpg>Character Data を4回して、名前を「Slime」「Fairy」「Jiangshi」「Medusa」にする
  • Enemiesフォルダで右クリック>Create>TurnBasedRpg>Enemy Data を3回して、名前を「SpilledInk」「PaperPlane」「Bookworm」にする
Data>Charactersの中にFairy・Jiangshi・Medusa・Slimeのデータが並んでいる

仲間の能力

それぞれのアセットを選んで、Inspectorで値を入れます。Idle Framesには、#1と同じように<名前>_walk_61〜63の3枚をドラッグします。

名前Character NameHPMPこうげきぼうぎょすばやさまりょく
Slimeスライム388121196
Fairyフェアリー2620671212
Jiangshiキョンシー326148154
Medusaメドゥーサ281877814

成長(Growth)は、1レベルごとに増える分です。#12のレベルアップで使います。

名前HPMPこうげきぼうぎょすばやさまりょく
Slime512211
Fairy331122
Jiangshi413120
Medusa331113
SlimeのデータのInspector。Character Nameがスライム、Idle Framesにslime_walk_61〜63、Base Statsが38・8・12・11・9・6、Growthが5・1・2・2・1・1

4人の役割は、数字にそのまま表れています。

  • スライムちゃん:HPとぼうぎょが高い、打たれ強いバランス型
  • フェアリー:MPとすばやさが高く、回復のまほうで支える
  • キョンシー:こうげきとすばやさが高い。まほうは苦手
  • メドゥーサ:まりょくが一番高い、攻撃のまほうの担当

敵の能力

名前Enemy NameSpriteHPMPこうげきぼうぎょすばやさまりょくExpGold
SpilledInkこぼれたインクenemy_spilled_ink180945034
PaperPlane紙ひこうきenemy_paper_plane1208216033
Bookworm本の虫enemy_bookworm2201067056
SpilledInkのデータのInspector。Enemy Nameがこぼれたインク、Spriteがenemy_spilled_ink、Statsが18・0・9・4・5・0、Expが3、Goldが4
  • 紙ひこうきは、HPは低いけど、すばやさが16で4人のだれよりも速い
スライム

紙ひこうきって、キョンシーよりも速いんですね・・・。先に動かれちゃいそう

  • 本の虫は、3体の中では一番かたい。#15で「どく」の攻撃をしてくるようになる

数字は、このあと実際に戦ってみながら直していきます。

データから見た目を作ろう

データが出来たので、画面に並べてみます。#1では3人の絵をImageに直接入れていましたが、これをデータから入れる形に変えます。

IdleBounceに、コマを差し替えるメソッドを足す

#1で作ったIdleBounceは、Inspectorで入れた3枚をくり返していました。データの3枚に差し替えられるように、メソッドを1つ足します。

    // 待機のコマを差し替える(データから見た目を作るときに呼ぶ)
    public void SetFrames(Sprite[] newFrames)
    {
        frames = newFrames;
        image.sprite = frames[frameOrder[orderIndex]];
    }

UnitView

  • 「UnitView」スクリプトを作る
using UnityEngine;
using UnityEngine.UI;

// 仲間や敵の見た目。データを受け取って、絵を表示する
public class UnitView : MonoBehaviour
{
    [SerializeField] private Image image;
    [SerializeField] private IdleBounce idleBounce; // 仲間だけ。敵は空のまま

    // 仲間のデータで見た目を作る
    public void SetupCharacter(CharacterData data)
    {
        name = data.CharacterName;
        idleBounce.SetFrames(data.IdleFrames);
    }

    // 敵のデータで見た目を作る
    public void SetupEnemy(EnemyData data)
    {
        name = data.EnemyName;
        image.sprite = data.Sprite;
    }
}
  • name = …
    • GameObjectの名前を、データの名前にする。再生中のHierarchyで、だれがだれだか分かりやすくなる
  • SetupCharacter / SetupEnemy
    • 仲間は待機の3コマ、敵は1枚の絵。持っているデータが違うので、メソッドを分けています

3人の仲間(Fairy・Slime・Jiangshi)にUnitViewを付けて、ImageとIdle Bounceに自分自身をドラッグします。

敵をプレハブにする

敵は戦いごとに数が変わるので、プレハブにしてスクリプトから並べます(7-3 スクリプトからプレハブを生成)。

  • #1で置いたSpilledInkの名前を「Enemy」に変え、UnitViewを付けて、Imageに自分自身をドラッグ(Idle Bounceは空のまま)
  • EnemyをTurnBasedRpg/Prefabsにドラッグしてプレハブにする
  • シーンのEnemyは消す
EnemyのプレハブのInspector。ImageのSource Imageがenemy_spilled_inkでPreserve Aspectにチェック、Unit ViewのImageにEnemy、Idle BounceはNone

BattleLineup

  • 「BattleLineup」スクリプトを作る
using UnityEngine;

// データから仲間と敵を並べる(バトルの流れは #9 で BattleManager に移す)
public class BattleLineup : MonoBehaviour
{
    [SerializeField] private CharacterData[] party;  // バトルに出る3人
    [SerializeField] private UnitView[] partyViews;  // PartyArea の3人
    [SerializeField] private EnemyData[] enemies;    // 出てくる敵(1〜4体)
    [SerializeField] private UnitView enemyPrefab;
    [SerializeField] private RectTransform enemyArea;
    [SerializeField] private float enemySpacing = 300f; // 敵の横の間隔

    private void Start()
    {
        for (int i = 0; i < partyViews.Length; i++)
        {
            partyViews[i].SetupCharacter(party[i]);
        }

        for (int i = 0; i < enemies.Length; i++)
        {
            UnitView view = Instantiate(enemyPrefab, enemyArea);
            // 真ん中をそろえて横に並べ、1体おきに上下へずらす
            float x = (i - (enemies.Length - 1) / 2f) * enemySpacing;
            float y = (i % 2 == 0) ? 60f : -60f;
            ((RectTransform)view.transform).anchoredPosition = new Vector2(x, y);
            view.SetupEnemy(enemies[i]);
        }
    }
}
  • (i – (enemies.Length – 1) / 2f) * enemySpacing
    • 敵の数が何体でも、真ん中にそろうように並べる計算。3体なら -1・0・1 倍、2体なら -0.5・0.5 倍の位置になる
  • (i % 2 == 0) ? 60f : -60f
    • 1体おきに上下へずらす。横一列より、奥行きがあるように見えます
  • (RectTransform)view.transform
    • UIの位置はanchoredPositionで決めるので、TransformをRectTransformとして使う

BattleLineupは、データから並べるところだけを受け持つ、仮の入れ物です。#9でバトルの流れを作るときに、BattleManagerの中に移します。

Canvasの外に空のGameObject「Battle」を作り、BattleLineupを付けて設定します。

  • Party:Slime・Fairy・Jiangshi(データ)
  • Party Views:PartyAreaのSlime・Fairy・Jiangshi(シーンのオブジェクト)
    • PartyとParty Viewsは同じ順番にする(1番目のデータが1番目の見た目に入る)
  • Enemies:SpilledInk・PaperPlane・Bookworm
  • Enemy Prefab:PrefabsのEnemy
  • Enemy Area:EnemyArea
BattleのBattle Lineup。PartyにSlime・Fairy・Jiangshi、Party Viewsにスライムちゃん・フェアリー・キョンシー、EnemiesにSpilledInk・PaperPlane・Bookworm、Enemy PrefabにEnemy、Enemy AreaにEnemyArea、Enemy Spacingは300

動かして確認!

再生すると、仲間3人と敵3体が並びます。

再生中のHierarchyで、PartyAreaの下がフェアリー・スライム・キョンシー、EnemyAreaの下がこぼれたインク・紙ひこうき・本の虫という名前になっている。Gameビューでは敵が3体、1体おきに上下にずれて並ぶ

試しに、データを入れ替えてみましょう。

  • BattleのPartyの2番目をFairyからMedusaにする
  • Enemiesを2つに減らして、両方SpilledInkにする

再生すると、2人目がメドゥーサになり、こぼれたインクが2体、真ん中にそろって並びます。スクリプトを1行も変えずに、並ぶキャラクターと敵が変わりました。

2人目がメドゥーサになり、右にこぼれたインクが2体、真ん中にそろって並んでいる

#2の動作確認

  • 仲間3人が、データの待機の絵で弾んでいる
  • 敵が3体、真ん中にそろって並び、1体おきに上下にずれている
  • 再生中のHierarchyで、仲間と敵の名前がデータの名前になっている
  • PartyやEnemiesを入れ替えると、並ぶキャラクターと敵が変わる

確認が終わったら、Partyは「Slime・Fairy・Jiangshi」、Enemiesは「SpilledInk・PaperPlane・Bookworm」に戻しておいてください。

あとがき

今回は、仲間と敵をデータにして、データから画面に並べる形を作りました。まだ並んでいるだけですが、このあとの回で増えていく敵やまほうも、同じようにデータを足していくだけで済むようになります。次回は、右下のウインドに仲間のHPとMPを表示する、ステータスのウインドを作りたいと思います!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次