この記事はハクスラローグライク×デッキ構築型カードバトルRPG「呪術迷宮」の作り方講座の第6回です。
前回はカードをプレイするためのゾーンの構築を行い、さらに山札からプレイヤーの手札にカードを配る処理を開発しました。
前回の記事:
現状ではカードの種類・名前・効果が付けられていません。
今回はカードの種類ごとに異なる効果を設定できるように様々な種類の定義方法を駆使していきます。
カードを重ねることで効果を合体させるシステムの開発手法も同時にマスターしていきましょう!
カードバトルゲームのシステムをどう設計・実装していくか
カードバトルゲームを作る際にはそのゲームシステムを実現するためのクラスやオブジェクトの設計が必要になります。
例えば、カードバトルには「カードの名称」、「カードの効果説明文」、「カードの効果の種類」、「カードの効果量」などが最低限必要になりそうですよね。
複雑なシステムを作る場合は最初にどんなパラメータが必要になるかなど紙や表計算ソフトに書き出したりして整理しておきましょう。
さて、呪術迷宮のカードゲームではプレイヤーや敵の持つカードそれぞれに複数効果が付与されています。さらに、それらのカードは合成可能で、合成するとカード同士の持つ効果が合体されます。
カードゲームのように複雑なシステムを組む場合は上手にプログラミングを行い、カードの持つ効果やカード同士が重なったときの処理を構築しなくてはなりません。
今回はまずカードの効果の種類一覧を定義し、その効果の種類ごとに様々な情報を紐づけて呼び出す形で実装していきます。
カード効果の種類をクラスと列挙体を用いて一覧で定義・実装する方法
このゲームは1種類の効果を複数のカードが共通して持つこともあるので、カードそのものの情報と効果の情報は別々に定義しておきます。まずは効果の定義から行います。
新規クラスCardEffectDefineを作成します。作成場所はいつも通りScripts/Battle内でも構いませんが、Scripts以下にDefineというフォルダを作成してここに定義系クラスを格納する事にします。
フォルダ構成においてスクリプトの場所を後から変えても大丈夫ですが、移動させる場合は必ずUnityエディター上で操作するようにしてください。
定義の方法はいくつかありますが、まずは直接スクリプト内に書き込めるシンプルな形式をとります。
CardEffectDefine.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// カードの効果の種類などを定義 /// </summary> [System.Serializable] public class CardEffectDefine { // パラメータ [Header ("効果の種類")] public CardEffect cardEffect; [Header ("効果値")] public int value; #region 効果の種類定義部 // カード効果定義 public enum CardEffect { Damage, // ダメージ WeaponDmg, // 武器ダメージ Heal, // 回復 _MAX, } // 効果名(JP) readonly public static Dictionary<CardEffect, string> Dic_EffectName_JP = new Dictionary<CardEffect, string> () { {CardEffect.Damage, "ダメージ {0}" }, {CardEffect.WeaponDmg, "武器ダメージ {0}" }, {CardEffect.Heal, "回復 {0}" }, }; // 効果名(EN) readonly public static Dictionary<CardEffect, string> Dic_EffectName_EN = new Dictionary<CardEffect, string> () { {CardEffect.Damage, "Damage {0}" }, {CardEffect.WeaponDmg, "Weapon Dmg {0}" }, {CardEffect.Heal, "Heal {0}" }, }; // 効果説明(JP) readonly public static Dictionary<CardEffect, string> Dic_EffectExplain_JP = new Dictionary<CardEffect, string> () { {CardEffect.Damage, "相手に {0} のダメージを与える" }, {CardEffect.WeaponDmg, "相手に {0} のダメージを与える" }, {CardEffect.Heal, "自分の体力を {0} 回復する" }, }; // 効果説明(EN) readonly public static Dictionary<CardEffect, string> Dic_EffectExplain_EN = new Dictionary<CardEffect, string> () { {CardEffect.Damage, "Deals {0} damage to the opponent" }, {CardEffect.WeaponDmg, "Deals {0} damage to the opponent" }, {CardEffect.Heal, "heals {0} of own HP" }, }; // 合成モード readonly public static Dictionary<CardEffect, EffectCompoMode> Dic_EffectCompoMode = new Dictionary<CardEffect, EffectCompoMode> () { {CardEffect.Damage, EffectCompoMode.Possible }, {CardEffect.WeaponDmg, EffectCompoMode.Possible }, {CardEffect.Heal, EffectCompoMode.Possible }, }; #endregion #region 合成モード定義 // 合成モード定義 public enum EffectCompoMode { Possible, // 合成可能 Impossible, // 合成不可能 OnlyNew, // 新規追加のみ OnlyOwn, // 自分とカードとのみ合成可能 OnlyOwn_New,// 自分とカードとのみ合成可能(新規追加のみ) } // 効果名(JP) readonly public static Dictionary<EffectCompoMode, string> Dic_CompoModeName_JP = new Dictionary<EffectCompoMode, string> () { {EffectCompoMode.Possible, "合成可能" }, {EffectCompoMode.Impossible, "合成不可能" }, {EffectCompoMode.OnlyNew, "追加限定" }, {EffectCompoMode.OnlyOwn, "自カード限定" }, {EffectCompoMode.OnlyOwn_New, "追加・自カード限定" }, }; // 効果名(EN) readonly public static Dictionary<EffectCompoMode, string> Dic_CompoModeName_EN = new Dictionary<EffectCompoMode, string> () { {EffectCompoMode.Possible, "Composability" }, {EffectCompoMode.Impossible, "Not Composability" }, {EffectCompoMode.OnlyNew, "Additional limited" }, {EffectCompoMode.OnlyOwn, "Own card only" }, {EffectCompoMode.OnlyOwn_New, "Additional and own card only" }, }; #endregion } |
- 8行目:[System.Serializable]はクラスの宣言に合わせて付与する事が出来る属性の1つです。クラスのシリアライズ(ファイル等への書き出し)が可能になります。今後セーブ&ロードの処理を実装した時に影響がありますが、今は忘れても大丈夫です。
- 9行目:このCardEffectDefineはゲームオブジェクトにアタッチせず使用するのでMonoBehaviourを継承する必要はありません。
- 12行目:[Header]は変数の宣言に合わせて付与する事が出来る属性の1つです。Inspectorでこの変数を表示した時に任意の文字列を説明文として表示する事が可能です。
現在はカードの効果3種類をenumで定義し、それぞれに対して「名前(日本語・英語)」「説明文(日本語・英語)」「合成モード」のデータを入力しています。ここから詳しく説明していきます。
Dictionary型を使ったカード効果種類と各種情報の対応付けについて
効果の種類別に定義したenumの定数データに対し、「この効果にはこの名前」と1つ1つデータを紐づけていく必要があります。1対1のペアで情報を格納する時はDictionary型が便利です。
例えば29行目ではstring型(文字列型)との紐づけを効果種別に対し行っています。初期化子を使ってスクリプト内に直接文字情報を書き込んでいます。
このDictionaryの値はゲーム中に変化する事がないのでreadonlyを付けて読み取り専用にしています。また、staticで宣言する事によって他のクラスから直接このDictionaryを読み取る事が出来るようにしています。
他のクラスからDictionaryの値を使いたい時、例えばDamage効果の日本語名を取得したい時は、
1 |
CardEffectDefine.Dic_EffectName_JP[CardEffectDefine.CardEffect.Damage] |
と呼び出す事によってstring型で名前データが帰ってきます。例えばDebug.Logで確認すると以下のように表示されます。
string.Formatメソッドを用いてカードの効果値を埋め込む
そして名前や説明文章を定義している所で 「 {0} 」という文字列が複数登場している事が確認できると思います。これは文字列内に変数を埋め込む時に使用する書式です。
このゲームではそれぞれの効果に「効果値」を持たせています(15行目に宣言している変数がそれです)。
全部ではないですが、多くがこの効果値によって戦闘に与える影響量が変化します。例えばDamage効果の効果値が10なら相手に10ダメージを与える結果になります。
その効果量を分かりやすく名前のそばに、あるいは説明文中の適切な位置に表示するために「文字列の中に変数を埋め込む」事が必要になります。それを実現できるのがこの書式という事になります。
変数の埋め込みはstring.Formatメソッドで行えます。第1引数に書式(文字列)、第2引数以降に埋め込みたい変数または定数を指定します。変数は何種類でも埋め込む事ができます。2つ目以降は {1}、{2}…と続けて対応させます。
1 |
string.Format (CardEffectDefine.Dic_EffectName_JP[CardEffectDefine.CardEffect.Damage] , 15) |
埋め込んだ後の文字列は戻り値で返ってきています。試しに上記のように15という定数を埋め込んでDebug.Logで確認すればこのように表示されているはずです。
カード合成モードの定義について
スクリプトの後半では合成モードの種類もあわせて定義しています。こちらも各効果ごとにDictionaryで合成モードを紐づけて設定しています。
合成システムについては一度この講座の完成目標となるゲームを遊んでいただければ理解が深まるかと思います。enum型のEffectCompoModeを用いてカード合成モードを5種類で定義されています。
定数 | 名称(日) | 説明 |
Possible | 合成可能 | 他の合成可能カードと合成できる |
Impossible | 合成不可能 | いずれのカードとも合成できない |
OnlyNew | 追加限定 | 合成可能だが、同じ効果があっても効果値の加算が行われない |
OnlyOwn | 自カード限定 | 自分のカードとしか合成できない |
OnlyOwn_New | 追加・自カード限定 | 「追加限定」と「自カード限定」2つの性質を併せ持つ |
今後、カード合成システムを実装する時に必要になるので今のうちに設定しておきましょう。
ゲームに出てくる全てのカード効果のデータを設定する
カード効果やその対応関係の定義についての解説は以上になります。ここからはこの先の開発で必要になる全ての効果をまとめて定義していきます。
実際のゲーム開発ではこうした様々なカード効果を追加しながらゲームの機能拡張をしていくことになりますね。
ここでは、1つ1つ手作業で入力するのは大変なので以下のサンプルスクリプトをCardEffectDefine.csにコピー&ペーストすると良いでしょう。
ScriptableObjectファイルの読み取りについて
作成したカードデータファイルはスクリプトからの読み出しが可能です。試しにBattleManagerを拡張し、カードの効果名をデバッグ表示させてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 戦闘画面マネージャクラス /// </summary> public class BattleManager : MonoBehaviour { // 管理下コンポーネント public FieldManager fieldManager; // フィールド管理クラス public CardDataSO testCardData; // Start void Start() { // 管理下コンポーネント初期化 fieldManager.Init (this); // カード効果名表示テスト foreach (var cardEffect in testCardData.effectList) { // 効果名文字列を取得 string nameText = CardEffectDefine.Dic_EffectName_JP[cardEffect.cardEffect]; // 効果値変数を文字列に埋め込む nameText = string.Format (nameText, cardEffect.value); Debug.Log (nameText); } } // Update void Update() { } } |
Inspectorからデバッグ表示させたいカードデータファイルをドラッグ&ドロップで指定して実行します。
先ほど設定した効果種類・効果値が正しく順番に表示できている事が確認できると思います。確認できたらBattleManagerスクリプトを元に戻してOKです。
サンプルゲームのカードデータ一例
これにてプレイヤーが使うカードの作成が可能になりました。同様に敵が使う用のカードも作成可能になっています。
(敵用のカードはScriptableObjects/EnemyCardsフォルダを作成して格納しましょう。また、プレイヤーと敵が両方とも同じカードを使う実装にしても問題ありません。)
以下、サンプルゲーム『呪術迷宮』にて使用しているカードデータの一部を例として提示します。同じように作る必要はありませんのでご参考程度にお使いください。
プレイヤーカード例
敵カード例
(↑23_Super FIreのSerial Numは23を入力。0になってますが間違いです)
まとめ
Dictionaryを使ってデータを組み合わせる方法、ScriptableObjectを使ってデータファイルをスクリプト外で編集できる機能などを駆使して効果データとカードデータの作成を行いました。
特にScriptableObjectは使いこなせばグループでのゲーム開発においても非常に有効な手法となるので覚えておくと良いでしょう。
次回はここで設定したカードのデータを、ゲーム上で生成されるカードオブジェクトに適用して表示させる部分を主に実装していきます。
次の記事:
コメント
執筆ご苦労様です。読んでいて混乱した点がありましたので、ご報告させていただきます。
「ゲームに出てくる全てのカード効果のデータを設定する」の「CardEffectDefine.csに追記」という表記ですが、実際には追記というよりは全面的な変更だったため、しばらく戸惑いました。
また、「Scriptable Objectを使って実際にカードを作ってみよう」では、Sprite Editorによって分割された画像が出てきますが、これの画像を展開するために「CardIcons」の再生ボタンのようなマークを押せばいいということが分からず、しばらく戸惑いました。
ささやかですが、記事の質の向上に役立てば幸いです。
記事の解説部分へのフィードバックありがとうございます。
記事に軽い修正を加えました。