今回はユニティちゃんRPGのゲームデータのセーブ、ロード、削除機能を作成していきたいと思います。
前回はタイトルシーンの作成をしました。
ユニティちゃんのRPGを作ってみようの他の記事は
から見ることが出来ます。
今回はゲームのデータを保存し、後からデータをロードしてゲームを再開する機能を作成していきたいと思います。
おそらく独自形式のScritableObjectのSkillやItemのインスタンスIDをシリアライズして情報として保存しているからかもしれません。
SkillやItem情報はstring型のスキル名やアイテム名でシリアライズしてデータを保持しておいて、データロード時にアイテムデータベースに保持していたScriptableObjectのアセットファイルのスキル名やアイテム名と保存していたスキル名とアイテム名が一致したら再度そのSkillやItem型のデータをUnityChanStatusやYujiStatusのSkillListやItemDictionaryに設定し直すといいかもしれません。
スタンドアロン形式だと問題が出なかったんですが、今回の機能の記事は参考程度にしてください。(._.)
ゲームデータのセーブとロードの概要
ゲームデータはPlayerPrefsを使ってデータの保存と読み込みをしたいと思います。
データはJSON形式で扱います。
保存するデータにはキャラクターが持つアイテムのクラスとその数を入れているItemDictioanryのデータを保存する必要がありますが、これをそのままシリアライズしてJSON形式に変換出来ませんでした。
また通常のC#であればDictionaryをシリアライズする事も可能?みたいでItemDictionaryからDictionaryに変換してやろうと思いましたが、UnityのJsonUtilityクラスの場合は出来ないようなので、ItemDictionaryのキーと値を個別にList
データをロードした時はそのキーと値のリストを再度ItemDictionaryに設定するようにします。
保存するデータは全て新しくDataクラスを作成し、シリアライズ可能なフィールドに値を設定しそれをJSON形式に変換して保存します。
味方キャラクターのステータスはAllyStatusでこれをまとめてシリアライズし、ロードした時にそのままDataクラスのフィールドに設定する事も出来そうですが、AllyStatusはItemDictionaryを持つので、クラスのCloneメソッドを使ってデータをそのままクラスに入れてシリアライズせず、多少面倒ではありますが個別のデータをDataクラスのフィールドに設定していくことにします。
PlayerPrefsを使うとWindowsだとレジストリにデータが保存されます。
レジストリにデータを保存したくない場合はファイル等にセーブデータを出力するようにするといいかもしれません。
プラットフォームによって保存先が変わるので、詳しくはUnityのスクリプトリファレンスを確認してください。
セーブデータ用のクラスを作成する
まずはセーブデータ用のクラスを作成します。
Assets/RPG/Scriptsフォルダに新しくDataスクリプトを作成します。
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | using System; using System.Collections.Generic; using UnityEngine; [Serializable] public class Data { // シーンデータ [SerializeField] private SceneMovementData.SceneType sceneType; [SerializeField] private Vector3 position; [SerializeField] private Quaternion rotation; // パーティーステータスの保存データフィールド [SerializeField] private List<AllyStatus> partyStatusList; // ユニティちゃんの保存データフィールド [SerializeField] private string unityChanCharacterName = null; [SerializeField] private bool unityChanIsPoisonState; [SerializeField] private bool unityChanIsNumbnessState; [SerializeField] private int unityChanLevel; [SerializeField] private int unityChanMaxHP; [SerializeField] private int unityChanHP; [SerializeField] private int unityChanMaxMP; [SerializeField] private int unityChanMP; [SerializeField] private int unityChanAgility; [SerializeField] private int unityChanPower; [SerializeField] private int unityChanStrikingStrength; [SerializeField] private int unityChanMagicPower; [SerializeField] private List<Skill> unityChanSkillList = null; [SerializeField] private int unityChanEarnedExperience; [SerializeField] private Item unityChanEquipWeapon = null; [SerializeField] private Item unityChanEquipArmor = null; [SerializeField] private ItemDictionary unityChanItemDictionary; [SerializeField] private List<Item> unityChanItemDictionaryKeys; [SerializeField] private List<int> unityChanItemDictionaryValues; // 大鳥ゆうじの保存データフィールド [SerializeField] private string yujiCharacterName = null; [SerializeField] private bool yujiIsPoisonState; [SerializeField] private bool yujiIsNumbnessState; [SerializeField] private int yujiLevel; [SerializeField] private int yujiMaxHP; [SerializeField] private int yujiHP; [SerializeField] private int yujiMaxMP; [SerializeField] private int yujiMP; [SerializeField] private int yujiAgility; [SerializeField] private int yujiPower; [SerializeField] private int yujiStrikingStrength; [SerializeField] private int yujiMagicPower; [SerializeField] private List<Skill> yujiSkillList = null; [SerializeField] private int yujiEarnedExperience; [SerializeField] private Item yujiEquipWeapon = null; [SerializeField] private Item yujiEquipArmor = null; [SerializeField] private ItemDictionary yujiItemDictionary; [SerializeField] private List<Item> yujiItemDictionaryKeys; [SerializeField] private List<int> yujiItemDictionaryValues; // シーンデータプロパティ public SceneMovementData.SceneType SceneType { get { return sceneType; } set { sceneType = value; } } public Vector3 Position { get { return position; } set { position = value; } } public Quaternion Rotation { get { return rotation; } set { rotation = value; } } // パーティーステータス保存データプロパティ public List<AllyStatus> PertyList { get { return partyStatusList; } set { partyStatusList = value; } } // ユニティちゃん保存データプロパティ public string UnityChanCharacterName { get { return unityChanCharacterName; } set { unityChanCharacterName = value; } } public bool UnityChanIsPoisonState { get { return unityChanIsPoisonState; } set { unityChanIsPoisonState = value; } } public bool UnityChanIsNumbnessState { get { return unityChanIsNumbnessState; } set { unityChanIsNumbnessState = value; } } public int UnityChanLevel { get { return unityChanLevel; } set { unityChanLevel = value; } } public int UnityChanMaxHP { get { return unityChanMaxHP; } set { unityChanMaxHP = value; } } public int UnityChanHP { get { return unityChanHP; } set { unityChanHP = value; } } public int UnityChanMaxMP { get { return unityChanMaxMP; } set { unityChanMaxMP = value; } } public int UnityChanMP { get { return unityChanMP; } set { unityChanMP = value; } } public int UnityChanAgility { get { return unityChanAgility; } set { unityChanAgility = value; } } public int UnityChanPower { get { return unityChanPower; } set { unityChanPower = value; } } public int UnityChanStrikingStrength { get { return unityChanStrikingStrength; } set { unityChanStrikingStrength = value; } } public int UnityChanMagicPower { get { return unityChanMagicPower; } set { unityChanMagicPower = value; } } public List<Skill> UnityChanSkillList { get { return unityChanSkillList; } set { unityChanSkillList = value; } } public int UnityChanEarnedExperience { get { return unityChanEarnedExperience; } set { unityChanEarnedExperience = value; } } public Item UnityChanEquipWeapon { get { return unityChanEquipWeapon; } set { unityChanEquipWeapon = value; } } public Item UnityChanEquipArmor { get { return unityChanEquipArmor; } set { unityChanEquipArmor = value; } } public ItemDictionary UnityChanItemDictionary { get { return unityChanItemDictionary; } set { unityChanItemDictionary = value; } } public List<Item> UnityChanItemDictionaryKeys { get { return unityChanItemDictionaryKeys; } set { unityChanItemDictionaryKeys = value; } } public List<int> UnityChanItemDictionaryValues { get { return unityChanItemDictionaryValues; } set { unityChanItemDictionaryValues = value; } } // 大鳥ゆうじの保存データプロパティ public string YujiCharacterName { get { return yujiCharacterName; } set { yujiCharacterName = value; } } public bool YujiIsPoisonState { get { return yujiIsPoisonState; } set { yujiIsPoisonState = value; } } public bool YujiIsNumbnessState { get { return yujiIsNumbnessState; } set { yujiIsNumbnessState = value; } } public int YujiLevel { get { return yujiLevel; } set { yujiLevel = value; } } public int YujiMaxHP { get { return yujiMaxHP; } set { yujiMaxHP = value; } } public int YujiHP { get { return yujiHP; } set { yujiHP = value; } } public int YujiMaxMP { get { return yujiMaxMP; } set { yujiMaxMP = value; } } public int YujiMP { get { return yujiMP; } set { yujiMP = value; } } public int YujiAgility { get { return yujiAgility; } set { yujiAgility = value; } } public int YujiPower { get { return yujiPower; } set { yujiPower = value; } } public int YujiStrikingStrength { get { return yujiStrikingStrength; } set { yujiStrikingStrength = value; } } public int YujiMagicPower { get { return yujiMagicPower; } set { yujiMagicPower = value; } } public List<Skill> YujiSkillList { get { return yujiSkillList; } set { yujiSkillList = value; } } public int YujiEarnedExperience { get { return yujiEarnedExperience; } set { yujiEarnedExperience = value; } } public Item YujiEquipWeapon { get { return yujiEquipWeapon; } set { yujiEquipWeapon = value; } } public Item YujiEquipArmor { get { return yujiEquipArmor; } set { yujiEquipArmor = value; } } public ItemDictionary YujiItemDictionary { get { return yujiItemDictionary; } set { yujiItemDictionary = value; } } public List<Item> YujiItemDictionaryKeys { get { return yujiItemDictionaryKeys; } set { yujiItemDictionaryKeys = value; } } public List<int> YujiItemDictionaryValues { get { return yujiItemDictionaryValues; } set { yujiItemDictionaryValues = value; } } } |
どのシーンにいたかのデータ、ワールドマップならその位置と角度のデータ、ユニティちゃんと大鳥ゆうじのステータスを保存するデータのフィールドと単純にそれを返すプロパティを用意しています。
List
プロパティを作成する時はvisual studioのエディターでpropと入力してその後Tabキーを押すとプロパティのテンプレートが出来るので、黄色い表示の部分をTabキーで移動し書き換えると素早く作成出来ます。
プロパティは単純にフィールドの値を返すのと値を設定する事をしているだけです。
データのセーブ機能を作成する
ゲームデータのセーブをする機能を作成していきます。
Villageシーンを開いて、ヒエラルキー上で右クリックからCreate Emptyを選択し、名前をSaveManagerとします。
Assets/RPG/Scriptsフォルダに新しくSaveDataScriptスクリプトを作成し、SaveManagerゲームオブジェクトに取り付けます。
スクリプトが長いので分割して説明します。
クラス定義とフィールド
まずはクラス定義とフィールド部分を見ていきます。
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 | using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; [Serializable] public class SaveDataScript : MonoBehaviour { [SerializeField] private PartyStatus partyStatus = null; [SerializeField] private AllyStatus unityChanStatus = null; [SerializeField] private AllyStatus yujiStatus = null; [SerializeField] private string dataName = "UnityChanRPGData"; [SerializeField] private Text saveData1; [SerializeField] private Text saveData2; [SerializeField] private Text saveData3; [SerializeField] private Transform unityChanTransform; } |
partyStatus、UnityChanStatus、yujiStatusはそれぞれのステータスファイルを設定します。
dataNameはレジストリに記録する時の名前です。この後にセーブデータの番号を不可して3つのデータを作成出来るようにします。
saveData1~3はセーブデータの情報を表示するボタンの子要素のTextを設定します。
unityChanTransformにはヒエラルキー上のUnityChanを設定し、ワールドマップでのユニティちゃんの位置と角度を取得する時に使用します。
保存されているデータを表示する
保存されているデータを表示するDisplayDataメソッドを作成します。
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 | // データ情報を表示 public void DisplayData() { Data data; // いる場所 string place = ""; for (int i = 1; i <= 3; i++) { if (PlayerPrefs.HasKey(dataName + i)) { data = JsonUtility.FromJson<Data>(PlayerPrefs.GetString(dataName + i)); if (data.SceneType == SceneMovementData.SceneType.FirstVillage) { place = "最初の村"; } else if (data.SceneType == SceneMovementData.SceneType.WorldMap) { place = "ワールドマップ"; } else { place = ""; } if (i == 1) { saveData1.text = "データ" + i + ": LV" + data.UnityChanLevel + " " + data.UnityChanCharacterName + " " + place; } else if(i == 2) { saveData2.text = "データ" + i + ": LV" + data.UnityChanLevel + " " + data.UnityChanCharacterName + " " + place; } else if(i == 3) { saveData3.text = "データ" + i + ": LV" + data.UnityChanLevel + " " + data.UnityChanCharacterName + " " + place; } } else { if (i == 1) { saveData1.text = "データ" + i + ": " + "データがありません。"; } else if(i == 2) { saveData2.text = "データ" + i + ": " + "データがありません。"; } else if(i == 3) { saveData3.text = "データ" + i + ": " + "データがありません。"; } } } } |
DisplayDataは保存されているデータの簡易データを表示する為のメソッドです。
最初にデータを入れるDataクラスのインスタンスを生成します。
データをセーブした場所の文字列をplaceに入れます。
データは3つ保存できるようにするので、for文を使って3回繰り返します。
通常は0から開始されますが、直感的にiは1~3にした方が分かりやすいと思いiを1で初期化しました。
1 2 3 | if (PlayerPrefs.HasKey(dataName + i)) { |
で、まずそのデータがあるかどうかを調べます。
データがあればJsonUtility.FromJsonを使って指定した型のインスタンスを生成します。
引数で与えるのは読み込むJSON形式のデータなのでPlayerPrefs.GetStringを使って保存したデータを取得しそれを指定しています。
PlayerPrefs.GetStringで保存したstring型のデータを取得出来ます。
PlayerPrefsに関しては
を参照してください。
DataクラスのインスタンスdataのSceneTypeがFirstVillageであればplaceに最初の村、WorldMapの場合はワールドマップを入れます。
SceneTypeはデータを保存した時のシーンに応じて入れるセーブデータです。
最後にセーブデータ用のTextに簡易データであるユニティちゃんのレベル、名前、今いる場所を表示します。
データがなければデータがありませんと表示します。
セーブデータの2と3もやっていることは同じです。
Assets/RPG/Scripts/SceneMovementDataスクリプトのSceneTypeにWorldMapを追加します。
1 2 3 4 5 6 7 8 9 10 | public enum SceneType { StartGame, FirstVillage, FirstVillageToWorldMap, WorldMapToBattle, BattleToWorldMap, WorldMap } |
データを保存する
データを保存する処理をするSaveメソッドを作成します。
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 | // Saveメソッド public void Save(int num) { Data data = new Data(); // シーンデータをセット if (SceneManager.GetActiveScene().name == "Village") { data.SceneType = SceneMovementData.SceneType.FirstVillage; } else if (SceneManager.GetActiveScene().name == "WorldMap") { data.SceneType = SceneMovementData.SceneType.WorldMap; data.Position = unityChanTransform.position; data.Rotation = unityChanTransform.rotation; } // パーティーデータをセット data.PertyList = partyStatus.GetAllyStatus(); // ユニティちゃんデータをセット data.UnityChanCharacterName = unityChanStatus.GetCharacterName(); data.UnityChanAgility = unityChanStatus.GetAgility(); data.UnityChanCharacterName = unityChanStatus.GetCharacterName(); data.UnityChanEarnedExperience = unityChanStatus.GetEarnedExperience(); data.UnityChanEquipArmor = unityChanStatus.GetEquipArmor(); data.UnityChanEquipWeapon = unityChanStatus.GetEquipWeapon(); data.UnityChanMaxHP = unityChanStatus.GetMaxHp(); data.UnityChanMaxMP = unityChanStatus.GetMaxMp(); data.UnityChanHP = unityChanStatus.GetHp(); data.UnityChanMP = unityChanStatus.GetMp(); data.UnityChanIsNumbnessState = unityChanStatus.IsNumbnessState(); data.UnityChanIsPoisonState = unityChanStatus.IsPoisonState(); var unityChanItemDictionaryKeys = new List<Item>(); var unityChanItemDictionaryValues = new List<int>(); foreach (var item in unityChanStatus.GetItemDictionary()) { unityChanItemDictionaryKeys.Add(item.Key); unityChanItemDictionaryValues.Add(item.Value); } data.UnityChanItemDictionaryKeys = unityChanItemDictionaryKeys; data.UnityChanItemDictionaryValues = unityChanItemDictionaryValues; data.UnityChanLevel = unityChanStatus.GetLevel(); data.UnityChanMagicPower = unityChanStatus.GetMagicPower(); data.UnityChanPower = unityChanStatus.GetPower(); data.UnityChanSkillList = unityChanStatus.GetSkillList(); data.UnityChanStrikingStrength = unityChanStatus.GetStrikingStrength(); // 大鳥ゆうじデータをセット data.YujiCharacterName = yujiStatus.GetCharacterName(); data.YujiAgility = yujiStatus.GetAgility(); data.YujiEarnedExperience = yujiStatus.GetEarnedExperience(); data.YujiEquipArmor = yujiStatus.GetEquipArmor(); data.YujiEquipWeapon = yujiStatus.GetEquipWeapon(); data.YujiMaxHP = yujiStatus.GetMaxHp(); data.YujiMaxMP = yujiStatus.GetMaxMp(); data.YujiHP = yujiStatus.GetHp(); data.YujiMP = yujiStatus.GetMp(); data.YujiIsNumbnessState = yujiStatus.IsNumbnessState(); data.YujiIsPoisonState = yujiStatus.IsPoisonState(); var yujiItemDictionaryKeys = new List<Item>(); var yujiItemDictionaryValues = new List<int>(); foreach (var item in yujiStatus.GetItemDictionary()) { yujiItemDictionaryKeys.Add(item.Key); yujiItemDictionaryValues.Add(item.Value); } data.YujiItemDictionaryKeys = yujiItemDictionaryKeys; data.YujiItemDictionaryValues = yujiItemDictionaryValues; data.YujiLevel = yujiStatus.GetLevel(); data.YujiMagicPower = yujiStatus.GetMagicPower(); data.YujiPower = yujiStatus.GetPower(); data.YujiSkillList = yujiStatus.GetSkillList(); data.YujiStrikingStrength = yujiStatus.GetStrikingStrength(); PlayerPrefs.SetString(dataName + num, JsonUtility.ToJson(data)); PlayerPrefs.Save(); } |
最初にDataクラスのインスタンスdataを作成します。
SceneManager.GetActiveSceneで現在のシーンを取得しその名前に応じてSceneTypeを入れます。
WorldMapの場合はロードした時のユニティちゃんの位置と角度も再現したいので、ユニティちゃんの位置と角度もデータに入れます。
後はユニティちゃんや大鳥ゆうじの現在のデータをそのままdataのプロパティにセットしているだけです。
ItemDictionaryに関してはキーと値を別のList型に入れ、それをdataのプロパティに入れています。
CommandScriptに処理を追加する
セーブ関連のUIの操作は以前作成したCommandScriptで操作します。
なのでCommandScriptに処理を追加します。
もう見たくなかったCommandScriptです・・・・(´Д`)
CommandModeに追加
まずはCommandModeにDataPanel、ConfirmationPanelを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public enum CommandMode { CommandPanel, StatusPanelSelectCharacter, StatusPanel, ItemPanelSelectCharacter, ItemPanel, UseItemPanel, UseItemSelectCharacterPanel, UseItemPanelToItemPanel, UseItemPanelToUseItemPanel, UseItemSelectCharacterPanelToUseItemPanel, NoItemPassed, DataPanel, ConfirmationPanel } |
フィールドを追加
フィールドに新しく追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // データパネル private GameObject dataPanel; // 確認パネル private GameObject confirmationPanel; // データ処理結果パネル private GameObject dataResultPanel; // データパネルのCanvasGroup private CanvasGroup dataPanelCanvasGroup; // 確認パネルのCanvasGroup private CanvasGroup confirmationCanvasGroup; // SaveDataScript private SaveDataScript saveDataScript; // セーブ中かどうか private bool saving; |
dataPanelはセーブデータのパネルを設定します。
confirmationPanelはセーブファイルを選択した後に本当にセーブするかどうかの選択パネルを設定します。
dataResultPanelは『セーブしました。』みたいなのを表示するパネルを設定します。
dataPanelCanvasGroupはdataPanelに設定したCanvasGroupを設定します。
confirmationPanelCanvasGroupはconfirmationPanelに設定したCanvasGroupを設定します。
saveDataScriptは先ほど作ったSaveDataScriptを設定します。
savingはセーブ中かどうかです。
Awakeメソッドに追加
Awakeメソッドにゲームオブジェクトを取得する処理、CanvasGroupを取得する処理を追記します。
1 2 3 4 5 6 7 8 9 10 11 | dataPanel = transform.Find("DataPanel").gameObject; confirmationPanel = transform.Find("ConfirmationPanel").gameObject; dataResultPanel = transform.Find("DataResultPanel").gameObject; dataPanelCanvasGroup = dataPanel.GetComponent<CanvasGroup>(); confirmationCanvasGroup = confirmationPanel.GetComponent<CanvasGroup>(); // DataManager取得 saveDataScript = FindObjectOfType<SaveDataScript>(); |
SaveDataScriptは一つしか存在しないのでFindObjectOfTypeで取得しています。
OnEnableメソッドに追加
OnEnableメソッドにはパネルの非表示とCanvasGroupのinteractableの無効化を行う処理を追加します。
1 2 3 4 5 6 7 8 | dataPanel.SetActive(false); dataResultPanel.SetActive(false); confirmationPanel.SetActive(false); dataPanelCanvasGroup.interactable = false; confirmationCanvasGroup.interactable = false; |
Updateメソッドに追加
Updateメソッドにセーブ中は何もしない処理と、Cancelボタンを押した時の処理を追加します。
まずはUpdateメソッドの最初でセーブ中はreturnでそれ以降の処理をしないようにします。
1 2 3 4 5 | if (saving) { return; } |
Cancelボタンの処理にcurrentCommnadがCommandMode.DataPanel、CommandMode.ConfirmationPanelの時の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // データ選択パネルの時 } else if (currentCommand == CommandMode.DataPanel) { dataPanelCanvasGroup.interactable = false; dataPanel.SetActive(false); EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); commandPanelCanvasGroup.interactable = true; currentCommand = CommandMode.CommandPanel; // データを上書きしてもいいかを選択している時 } else if (currentCommand == CommandMode.ConfirmationPanel) { confirmationCanvasGroup.interactable = false; confirmationPanel.SetActive(false); EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); dataPanelCanvasGroup.interactable = true; currentCommand = CommandMode.DataPanel; } |
Cancelボタンを押した時に前のパネルに戻る処理を記述しているだけで以前作成した処理と同じ事をしているだけです。
EventSystemの選択が解除された時の処理にCommandMode.DataPanelとCommandMode.ConfirmationPanelの時の処理を追加します。
1 2 3 4 5 6 7 | } else if(currentCommand == CommandMode.DataPanel) { EventSystem.current.SetSelectedGameObject(dataPanel.transform.GetChild(0).gameObject); } else if(currentCommand == CommandMode.ConfirmationPanel) { EventSystem.current.SetSelectedGameObject(confirmationPanel.transform.Find("Panel/OKButton").gameObject); } |
SelectCommandに処理を変更
次はどのコマンドを選択したかを処理しているSelectCommandメソッドの処理を変更します。
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 | // 選択したコマンドで処理分け public void SelectCommand(string command) { if (command == "Status") { } else if (command == "Item") { } else if (command == "Save") { currentCommand = CommandMode.DataPanel; commandPanelCanvasGroup.interactable = false; selectedGameObjectStack.Push(EventSystem.current.currentSelectedGameObject); } if (command == "Save") { saveDataScript.DisplayData(); dataPanel.transform.SetAsLastSibling(); dataPanel.SetActive(true); dataPanelCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(dataPanel.transform.GetChild(0).gameObject); } else { // 階層を一番最後に並べ替え selectCharacterPanel.transform.SetAsLastSibling(); selectCharacterPanel.SetActive(true); selectCharacterPanelCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(selectCharacterPanel.transform.GetChild(0).gameObject); } } |
最初のStatusとItemの時は以前と同じなので省略しています。
Saveの時はcommandPanelCanvasGroupのinteractableをfalseにしCommandPanelのUIを選択出来ないようにします。
またこの時選択していたセーブコマンドのボタンをselectedGameObjectStackにプッシュして保持しておきます。
その後Saveの時はSaveDataScriptのDisplayDataメソッドを実行し、ボタンのテキストにセーブデータの情報を表示します。
dataPanelをアクティブにし操作出来るようにします。
Save以外の時は以前からあった処理と同じです。
確認パネル表示メソッドの追加
セーブコマンドで3つのセーブデータのうちどれかを押した時に実行した時に表示するパネルの表示処理であるDisplayConfirmPanelを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 確認パネル表示 public void DisplayConfirmPanel(int dataNum) { currentCommand = CommandMode.ConfirmationPanel; selectedGameObjectStack.Push(EventSystem.current.currentSelectedGameObject); dataPanelCanvasGroup.interactable = false; var okButton = confirmationPanel.transform.Find("Panel/OKButton").GetComponent<Button>(); var cancelButton = confirmationPanel.transform.Find("Panel/CancelButton").GetComponent<Button>(); // 取り付けたリスナーの削除 okButton.onClick.RemoveAllListeners(); cancelButton.onClick.RemoveAllListeners(); okButton.onClick.AddListener(() => Save(dataNum)); cancelButton.onClick.AddListener(() => ReturnToDataPanel()); confirmationPanel.transform.SetAsLastSibling(); confirmationPanel.SetActive(true); confirmationCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(confirmationPanel.transform.Find("Panel/OKButton").gameObject); } |
引数でセーブデータの番号を受け取ります。
確認パネル(confirmationPanel)の子要素のOKButtonとCancelButtonのButtonコンポーネントを取得し、最初にボタンを押した時に実行するメソッドの登録を全削除します。
これはボタンにはスクリプトから実行するメソッドを取り付けますが、DisplayConfirmPanelメソッドを実行する度に追加していると2重3重に取り付けたメソッドが実行されてしまう為です。
その後取得したButtonコンポーネントのonClickにAddListenerでOKButtonを押したらSaveメソッド、CancelButtonを押したらReturnToDataPanelメソッドを実行するようにリスナーを登録します。
ここら辺の処理はアイテムコマンドの辺りで嫌というほどやりましたので問題はないかと思います。(^_^;)
EventSystem.current.SetSelectedGameObjectを使って、確認パネルを表示したらOKButtonが選択状態になるようにします。
セーブ処理呼び出しメソッドの追加
実際のデータのセーブはSaveDataScriptで行いますが、CommandScriptでもSaveDataScriptのSaveメソッドと同一の名前のメソッドを追加します。
CommandScriptのSaveメソッドはSaveDataScriptのSaveメソッドを呼び出して、実際の処理をさせます。
1 2 3 4 5 6 7 8 9 10 11 | // セーブ処理 public void Save(int dataNum) { saving = true; confirmationCanvasGroup.interactable = false; confirmationPanel.SetActive(false); dataPanel.SetActive(false); saveDataScript.Save(dataNum); StartCoroutine(ProcessingAfterSaving(dataNum)); } |
行っていることは今までと同じでパネルの非表示やCanvasGroupの無効化等をしている部分はアイテムコマンドの時と同じなので問題はないと思います。
SaveDataScriptのSaveメソッドを引数にセーブデータ番号を渡して呼び出します。
その後『セーブしました』みたいなメッセージを一定時間表示する為、コルーチンを使ってProcessingAfterSavingメソッドを呼び出します。
セーブしましたメッセージ表示処理
セーブ後に『セーブしました』というメッセージを一定時間表示するメソッドを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public IEnumerator ProcessingAfterSaving(int dataNum) { dataResultPanel.transform.Find("Text").GetComponent<Text>().text = "セーブデータ" + dataNum + "にデータを保存しました。"; dataResultPanel.SetActive(true); yield return new WaitForSeconds(1f); currentCommand = CommandMode.CommandPanel; saving = false; dataResultPanel.SetActive(false); commandPanelCanvasGroup.interactable = true; selectedGameObjectStack.Pop(); EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); } |
最初にdataResultPanelのテキストにデータを保存しましたのようなテキストを設定し、dataResultPanelを表示します。
その後yield return new WaitForSeconds(1f)で1秒間待機した後、コマンド選択パネルに戻すようにします。
confirmationPanelを開いている状態ではCommandPanelのボタンとDataPanelのボタンのゲームオブジェクトがselectedGameObjectStackに登録されていて、confirmationPanelから一気にCommandPanelに戻るので一つそのまま空で捨てた後に、CommandPanelのボタンを選択している状態にします。
確認パネルでキャンセルボタンを押した時の処理
確認パネル(ConfirmationPanel)でキャンセルボタンを押してセーブするのをやめた時の処理を記述します。
1 2 3 4 5 6 7 8 9 10 11 | // 確認パネルでキャンセルした時に実行 public void ReturnToDataPanel() { currentCommand = CommandMode.DataPanel; confirmationCanvasGroup.interactable = false; confirmationPanel.SetActive(false); dataPanelCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); } |
やる事は単純にDataPanelでどのデータファイルにセーブするかの選択状態に戻すだけです。
これでCommandScriptへの処理の追加が終わりました。(-_-)
CommandUIにセーブコマンドUIを追加する
ステータスコマンド、アイテムコマンドのパネルに新しくセーブコマンドのUIを追加していきます。
VillageシーンのCommandの子要素のCommandPanelの子要素のItemButtonを選択した状態でCtrl+Dキーをおして複製し、名前をSaveButtonとします。
SaveButtonのButtonのOnClickにCommandゲームオブジェクトをドラッグ&ドロップし、実行するメソッドにSelectCommandを設定しその下にSaveという文字列を入力します。
Commandゲームオブジェクトを選択した状態で右クリックからUI→Panelを選択し、名前をDataPanelとします。
DataPanelにはAdd ComponentからLayout→Vertical Layout GroupとLayout→Canvas Groupを取り付けます。
Vertical Layout GroupのControl Child SizeのWidthとHeight、Child Force ExpandのWidthとHeightにチェックを入れます。
DataPanelの子要素は縦に並び、Vertical Layout Groupの設定によってサイズが調整されます。
CanvasGroupのinteractableのチェックを外しDataPanelの子要素のUIは最初は選択出来ないようにします。
DataPanelを選択した状態で右クリックからUI→Buttonを選択し、名前をData1Buttonとします。
ImageのColorは白色、Buttonの状態に応じた色は適当に設定してください。
わたくしの場合はコントローラー等で遷移した時(Highlighted Color)の色と、マウスで選択した時(Selected Color)の色を同じにしました。
Data1Buttonが押された時はCommandScriptのDisplayConfirmPanelメソッドに1を渡して実行します。
Data1Buttonの子要素のTextの設定をします。
Font Sizeを25、ParagraphのAlignmentを左寄せの中段表示にします。
Data1Buttonを選択した状態でCtrl+Dキーを押して複製し、Data2ButtonとData3Buttonを作成します。
Data2ButtonとData3ButtonのOnClickのDisplayConfirmPanelメソッドへ渡す数値はそれぞれ2と3に変更しておきます。
DataPanelは以下のようになりました。
ゲーム画面全体より少し小さいぐらいにしました。
次にCommandを選択した状態で右クリックからPanelを選択し、名前をConfirmationPanelとします。
ConfirmationPanelにもCanvasGroupを取り付け、interactableのチェックを外しておきます。
ConfirmationPanelを選択した状態で右クリックからUI→Textを選択し、名前をTitleTextとします。
TitleTextはConfirmationPanelの上部に位置するように移動します。
Textには『データを上書きしてもよろしいですか?』という文字列を入力します。
ConfirmationPanelを選択した状態で右クリックからUI→Panelを選択します。
PanelにはAdd ComponentからLayout→Horizontal Layout Groupを取り付けます。
Child AlignmentはMiddle Centerにし子要素が中央に表示されるようにします。
Panelを選択した状態で右クリックからUI→Buttonを選択し、名前をOKButtonとします。
OKButtonのボタンの状態に応じた色を変更します。
OKButtonを押した時の処理はスクリプトから設定したのでOnClickには何も設定しません。
OKButtonを選択した状態でCtrl+Dキーを押して複製し、名前をCancelButtonとします。
OKButtonの子要素のTextのTextには『OK』、CancelButtonの子要素のTextのTextには『キャンセル』と入力します。
それぞれのFont Sizeは25としました。
出来たConfirmationPanelは以下のようになりました。
ConfirmationPanelを選択した状態でCtrl+Dキーを押して複製し、名前をDataResultPanelとします。
DataResultPanelの子要素を全部削除し、新しくUI→Textを作成します。
Textは以下のように設定します。
Textに『データが上書きされました』と入力しますが、スクリプトから上書きしているので特に入力しなくてもいいです。
これでUIが出来、以下のような階層になりました。
DataPanel、ConfirmationPanel、DataResultPanelのインスペクタの名前の横のチェックを外し、最初は表示されないようにしておきます。
これでUIが出来たので、SaveManagerゲームオブジェクトのSaveDataScriptの設定をします。
上のようにステータスデータの設定と、データ名、UIの設定をします。
Villageシーンではユニティちゃんの位置と角度は保存しないのでUnityChanTransformには何も設定しません。
WorldMapシーンにUIやSaveManagerゲームオブジェクト等を複製する
Villageシーンでのセーブ機能が出来たのでこれをWorldMapシーンにCommandをコピーして貼り付け、SaveManagerゲームオブジェクトをコピーして貼り付けます。
UIの設定等はそれぞれのシーンのUIやスクリプト等に変更してください。
変更する箇所としては
DataPanel子要素の3つのボタンをWorldMapシーンのCommandScriptのDisplayConfirmPanelメソッドに変更。
SaveManagerゲームオブジェクトのSaveDataScriptのUIの設定と、unityChanTransformにWorldMapシーンのヒエラルキーのUnityChanを設定。
です。
これでデータのセーブ機能が出来ました。
ロード機能の作成
セーブ機能が出来たので次はロード機能を作成します。
今回はロードはTitleシーンでのみ行えるようにします。
UIの作成
まずはVillageシーンを開き、DataPanelゲームオブジェクトとConfirmationPanelゲームオブジェクトをコピーし、TitleシーンのCanvasの子要素の貼り付けます。
貼り付けたらインスペクタのRect TransformのLeftやRight等を0にします。
またScaleのXYZが0になっている可能性があるので、1にします。
Canvasを選択した状態で右クリックからUI→Panelを選択し、名前をMainPanelとします。
MainPanelのAdd ComponentからLayout→Vertical Layout Group、Layout→Canvas Groupを取り付けます。
Vertical Layout GroupのTopとBottomを調整して子要素に配置するボタンの並びを変更します。
Canvas Groupのinteractableにはチェックを入れておきます。
既に作成してあるStartButtonをMainPanelの子要素にドラッグし、StartButtonを選択した状態でCtrl+Dキーを押して複製し、名前をContinueButtonとします。
ContinueButton子要素のTextには『つづきから』と入力します。
MainPanelのサイズやボタンの配置等は以下のようにしました。
ロード、削除機能のスクリプトを作成
ロード機能のスクリプトを作成していきます。
ヒエラルキー上で右クリックからCreate Emptyを選択し、名前をCommandManagerとします。
Assets/RPG/Scrptsフォルダに新しくLoadDataScriptを作成し、CommandManagerゲームオブジェクトに取り付けます。
スクリプトが長いので分割して説明します。
クラス定義とフィールド
まずはクラス定義とフィールドです。
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 | using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; [Serializable] public class LoadDataScript : MonoBehaviour { private LoadSceneManager loadSceneManager; [SerializeField] private PartyStatus partyStatus = null; [SerializeField] private AllyStatus unityChanStatus = null; [SerializeField] private AllyStatus yujiStatus = null; [SerializeField] private SceneMovementData sceneMovementData = null; [SerializeField] private Text saveData1; [SerializeField] private Text saveData2; [SerializeField] private Text saveData3; } |
データのロードをしてゲームを再開する時にシーンを読み込む必要があるためLoadSceneManagerスクリプトを設定出来るようにしておきます。
後のフィールドはキャラクターのステータスやセーブデータ名とセーブデータのTextを設定します。
Startメソッド
Startメソッドを作成します。
1 2 3 4 5 | void Start() { loadSceneManager = FindObjectOfType<LoadSceneManager>(); } |
StartメソッドでLoadSceneManagerスクリプトを取得しています。
データ情報を表示する処理
データ情報を表示する処理DisplayDataメソッドを記述します。
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 | // データ情報を表示 public void DisplayData(string dataName) { Data data; // いる場所 string place = ""; for (int i = 1; i <= 3; i++) { if (PlayerPrefs.HasKey(dataName + i)) { data = JsonUtility.FromJson<Data>(PlayerPrefs.GetString(dataName + i)); if (data.SceneType == SceneMovementData.SceneType.FirstVillage) { place = "最初の村"; } else if (data.SceneType == SceneMovementData.SceneType.WorldMap) { place = "ワールドマップ"; } else { place = ""; } if (i == 1) { saveData1.text = "データ" + i + ": LV" + data.UnityChanLevel + " " + data.UnityChanCharacterName + " " + place; } else if (i == 2) { saveData2.text = "データ" + i + ": LV" + data.UnityChanLevel + " " + data.UnityChanCharacterName + " " + place; } else if (i == 3) { saveData3.text = "データ" + i + ": LV" + data.UnityChanLevel + " " + data.UnityChanCharacterName + " " + place; } } else { if (i == 1) { saveData1.text = "データ" + i + ": " + "データがありません。"; } else if (i == 2) { saveData2.text = "データ" + i + ": " + "データがありません。"; } else if (i == 3) { saveData3.text = "データ" + i + ": " + "データがありません。"; } } } } |
SaveDataScriptのDisplayDataメソッドとほとんど同じです。
データの読み込み処理
データの読み込み処理を行うLoadメソッドを記述します。
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 | // データ読み込みメソッド public void Load(string dataName) { if (PlayerPrefs.HasKey(dataName)) { var data = JsonUtility.FromJson<Data>(PlayerPrefs.GetString(dataName)); // シーンデータのセット sceneMovementData.SetWorldMapPos(data.Position); sceneMovementData.SetWorldMapRot(data.Rotation); // パーティーデータのセット partyStatus.CreateAllyStatus(data.PertyList); // ユニティちゃんデータをセット unityChanStatus.SetAgility(data.UnityChanAgility); unityChanStatus.SetCharacterName(data.UnityChanCharacterName); unityChanStatus.SetEarnedExperience(data.UnityChanEarnedExperience); unityChanStatus.SetEquipArmor(data.UnityChanEquipArmor); unityChanStatus.SetEquipWeapon(data.UnityChanEquipWeapon); // Max系のメソッドを先に実行する必要あり unityChanStatus.SetMaxHp(data.UnityChanMaxHP); unityChanStatus.SetMaxMp(data.UnityChanMaxMP); unityChanStatus.SetHp(data.UnityChanHP); unityChanStatus.SetMp(data.UnityChanMP); unityChanStatus.SetNumbness(data.UnityChanIsNumbnessState); unityChanStatus.SetPoisonState(data.UnityChanIsPoisonState); unityChanStatus.CreateItemDictionary(data.UnityChanItemDictionary); var unityChanItemDictionary = new ItemDictionary(); for (int i = 0; i < data.UnityChanItemDictionaryKeys.Count; i++) { unityChanItemDictionary.Add(data.UnityChanItemDictionaryKeys[i], data.UnityChanItemDictionaryValues[i]); } unityChanStatus.CreateItemDictionary(unityChanItemDictionary); unityChanStatus.SetLevel(data.UnityChanLevel); unityChanStatus.SetMagicPower(data.UnityChanMagicPower); unityChanStatus.SetPower(data.UnityChanPower); unityChanStatus.SetSkillList(data.UnityChanSkillList); unityChanStatus.SetStrikingStrength(data.UnityChanStrikingStrength); // 大鳥ゆうじデータをセット yujiStatus.SetAgility(data.YujiAgility); yujiStatus.SetCharacterName(data.YujiCharacterName); yujiStatus.SetEarnedExperience(data.YujiEarnedExperience); yujiStatus.SetEquipArmor(data.YujiEquipArmor); yujiStatus.SetEquipWeapon(data.YujiEquipWeapon); // Max系のメソッドを先に実行する必要あり yujiStatus.SetMaxHp(data.YujiMaxHP); yujiStatus.SetMaxMp(data.YujiMaxMP); yujiStatus.SetHp(data.YujiHP); yujiStatus.SetMp(data.YujiMP); yujiStatus.SetNumbness(data.YujiIsNumbnessState); yujiStatus.SetPoisonState(data.YujiIsPoisonState); yujiStatus.CreateItemDictionary(data.YujiItemDictionary); var yujiItemDictionary = new ItemDictionary(); for (int i = 0; i < data.YujiItemDictionaryKeys.Count; i++) { yujiItemDictionary.Add(data.YujiItemDictionaryKeys[i], data.YujiItemDictionaryValues[i]); } yujiStatus.CreateItemDictionary(yujiItemDictionary); yujiStatus.SetLevel(data.YujiLevel); yujiStatus.SetMagicPower(data.YujiMagicPower); yujiStatus.SetPower(data.YujiPower); yujiStatus.SetSkillList(data.YujiSkillList); yujiStatus.SetStrikingStrength(data.YujiStrikingStrength); // シーンの読み込み loadSceneManager.GoToNextScene(data.SceneType); } else { Debug.Log("データがありません。"); } } |
引数でセーブデータの番号を受け取り、そのデータが存在していればそのJSONデータをDataクラスのdataに入れます。
あとはユニティちゃんのステータス(unityChanStatus)や大鳥ゆうじのステータス(yujiStatus)のフィールドの設定メソッドにdataのデータを設定するだけです。
最後にLoadSceneManagerスクリプトのGoToNextSceneメソッドをdata.ScenTypeを引数に渡して呼び出し、シーン遷移をします。
LoadSceneManagerスクリプトに処理を追加
セーブ機能でSceneMovementDataスクリプトのSceneTypeにWorldMapを追加したので、ロード時のシーンタイプを比較しているFadeAndLoadSceneメソッド内にWorldMapも追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // シーンの読み込み if (scene == SceneMovementData.SceneType.FirstVillage) { yield return StartCoroutine(LoadScene("Village")); } else if (scene == SceneMovementData.SceneType.FirstVillageToWorldMap) { yield return StartCoroutine(LoadScene("WorldMap")); } else if (scene == SceneMovementData.SceneType.WorldMapToBattle) { yield return StartCoroutine(LoadScene("Battle")); } else if (scene == SceneMovementData.SceneType.BattleToWorldMap) { yield return StartCoroutine(LoadScene("WorldMap")); } else if (scene == SceneMovementData.SceneType.StartGame) { yield return StartCoroutine(LoadScene("Village")); } else if (scene == SceneMovementData.SceneType.WorldMap) { yield return StartCoroutine(LoadScene("WorldMap")); } |
PartyStatusスクリプトにCreateAllyStatusメソッドを作成する
Assets/RPG/Scripts/Status/PartyStatusスクリプトに追記します。
1 2 3 4 5 | public void CreateAllyStatus(List<AllyStatus> partyList) { partyMembers = partyList; } |
削除機能の作成
次は削除機能を作成します。
MainPanelのContinueButtonを選択した状態でCtrl+Dキーを押して複製し、名前をDeleteButtonとします。
Assets/RPG/Scriptsフォルダに新しくDeleteDataScriptスクリプトを作成し、CommandManagerゲームオブジェクトに取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class DeleteDataScript : MonoBehaviour { // データ削除メソッド public void Delete(string dataName) { if (PlayerPrefs.HasKey(dataName)) { PlayerPrefs.DeleteKey(dataName); } } } |
Deleteメソッドは引数で受け取ったデータがあるかどうか調べ、あれば削除しています。
タイトルシーン用のコマンド操作スクリプトの作成
ロード機能と削除機能が出来たので次はタイトルシーンでもVillageシーンでコマンド処理用のCommandScriptスクリプトのようにパネルとCanvasGroupの操作をするスクリプトを作成します。
Assets/RPG/Scriptsフォルダに新しくTitleCommandScirptスクリプトを作成します。
TitleCommandScriptはCommandManagerゲームオブジェクトに取り付けます。
スクリプトは分割して説明します。
基本的には以前作成したCommandScriptの簡易版だと思って頂ければいいと思います。
クラス定義とフィールド
まずはクラス定義とフィールドです。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class TitleCommandScript : MonoBehaviour { public enum CommandMode { MainPanel, DataPanel, ConfirmationPanel } private CommandMode currentCommand; private LoadDataScript loadDataScript; private DeleteDataScript deleteDataScript; [SerializeField] private GameObject mainPanel; [SerializeField] private CanvasGroup mainPanelCanvasGroup; [SerializeField] private GameObject dataPanel; [SerializeField] private CanvasGroup dataPanelCanvasGroup; [SerializeField] private GameObject confirmationPanel; [SerializeField] private CanvasGroup confirmationPanelCanvasGroup; [SerializeField] private GameObject dataResultPanel; private Button data1Button; private Button data2Button; private Button data3Button; private Button okButton; private Button cancelButton; // 最後に選択していたゲームオブジェクトをスタック private Stack<GameObject> selectedGameObjectStack = new Stack<GameObject>(); [SerializeField] private string dataName = "UnityChanRPGData"; // データを削除中かどうか private bool deleting; } |
列挙型のCommandModeは現在どのパネルを開いている状態かを表しています。
currentModeは現在のCommandModeを入れます。
loadDataScriptは先ほど作成したLoadDataScriptを入れます。
mainPanelは『はじめから』と『つづきから』『データを削除』のボタンの親のMainPanelを設定します。
mainPanelCanvasGroupはMainPanelに取り付けてあるCanvas Groupです。
dataPanelはセーブデータを表示しているパネルを設定します。
dataPanelCanvasGroupはdataPanelに取り付けてあるCanvas Groupを設定します。
confirmationPanelはセーブデータをロードするかの最終確認パネルを設定します。
confirmationPanelCanvasGroupはconfirmationPanelに取り付けてあるCanvas Groupを設定します。
dataResultPanelはデータを削除時に使用するパネルです。
data1Buttonからdata3ButtonまではDataPanelの子要素のボタンを設定します。
okButtonとcancelButtonはConfirmationPanelの子要素のOKButtonとCancelButtonを設定します。
selectedGameObjectStackは新しいパネルを開いた時に前のパネルのゲームオブジェクトを登録しておいて、前のパネルに戻った時にそのゲームオブジェクトを選択しなおす時に使用します。
dataNameはセーブデータの名前を設定します。
deletingは現在削除中かどうかです。
StartとUpdateメソッド
StartとUpdateメソッドを作成していきます。
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 | void Start() { loadDataScript = FindObjectOfType<LoadDataScript>(); deleteDataScript = FindObjectOfType<DeleteDataScript>(); data1Button = dataPanel.transform.Find("Data1Button").GetComponent<Button>(); data2Button = dataPanel.transform.Find("Data2Button").GetComponent<Button>(); data3Button = dataPanel.transform.Find("Data3Button").GetComponent<Button>(); okButton = confirmationPanel.transform.Find("Panel/OKButton").GetComponent<Button>(); cancelButton = confirmationPanel.transform.Find("Panel/CancelButton").GetComponent<Button>(); } void Update() { if (deleting) { return; } // キャンセルボタンを押した時の処理 if (Input.GetButtonDown("Cancel")) { if (currentCommand == CommandMode.DataPanel) { currentCommand = CommandMode.MainPanel; dataPanelCanvasGroup.interactable = false; dataPanel.SetActive(false); mainPanelCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); } else if (currentCommand == CommandMode.ConfirmationPanel) { SelectCommand("Back"); } } // ボタンが一つも選択されていない時に強制的に選択させる if (EventSystem.current.currentSelectedGameObject == null) { if (currentCommand == CommandMode.MainPanel) { EventSystem.current.SetSelectedGameObject(mainPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.DataPanel) { EventSystem.current.SetSelectedGameObject(dataPanel.transform.GetChild(0).gameObject); } else if (currentCommand == CommandMode.ConfirmationPanel) { EventSystem.current.SetSelectedGameObject(confirmationPanel.transform.Find("Panel/OKButton").gameObject); } } } |
StartメソッドでFindObjectOfTypeでヒエラルキー上にひとつだけあるLoadDataScritpとDeleteDataScriptを探して取得します。
その他のボタンも各パネルの階層から取得しておきます。
Updateメソッドではデータを削除中はその後の処理はしないようにします。
Cancelボタンを押した時の処理で、現在のcurrentCommandに応じてパネルを戻る処理を実行します。
確認パネル(CommandMode.ConfirmationPanel)の場合はSelectCommandにBackという文字列を渡してDataPanelの表示に戻します。
マウス等でボタン外を押した時にボタンからフォーカスが外れるのを防ぐため、EventSystemがどのUIも選択状態でなければ現在のコマンドモードに応じて該当するボタンを強制的に選択状態にします。
実行コマンドの処理分け
選択したコマンドに応じた処理分けをするSelectCommandメソッドを作成します。
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 | // 選択したコマンドで処理分け public void SelectCommand(string command) { currentCommand = CommandMode.DataPanel; mainPanelCanvasGroup.interactable = false; confirmationPanelCanvasGroup.interactable = false; confirmationPanel.SetActive(false); dataPanelCanvasGroup.interactable = true; dataPanel.SetActive(true); if (command == "Load" || command == "Delete") { selectedGameObjectStack.Push(EventSystem.current.currentSelectedGameObject); loadDataScript.DisplayData(dataName); data1Button.onClick.RemoveAllListeners(); data2Button.onClick.RemoveAllListeners(); data3Button.onClick.RemoveAllListeners(); // データがあるものだけボタンを押した時の処理を設定 if (PlayerPrefs.HasKey(dataName + 1)) { data1Button.onClick.AddListener(() => DisplayConfirmationPanel(command, 1)); } if (PlayerPrefs.HasKey(dataName + 2)) { data2Button.onClick.AddListener(() => DisplayConfirmationPanel(command, 2)); } if (PlayerPrefs.HasKey(dataName + 3)) { data3Button.onClick.AddListener(() => DisplayConfirmationPanel(command, 3)); } EventSystem.current.SetSelectedGameObject(dataPanel.transform.GetChild(0).gameObject); } else if (command == "Back") { EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); } } |
引数で受け取った文字列のコマンドに応じて実行する処理分けをしています。
ここではdataPanel(セーブデータファイル表示)の表示とconfirmationPanel(確認パネル)の非表示を行います。
commandがLoadの場合はconfirmationPanelの表示と、そのCanvasGroupを無効化する処理はいらないですが、confirmationPanelを開いている状態でキャンセルした時(commandがBackの時)に必要になるので実行しています。
if文で条件に応じて実行させるようにしてもいいです。
commandがLoadかDeleteの時は現在選択しているゲームオブジェクト(MainPanelのContinueButtonゲームオブジェクト)をselectedGameObjectStackにプッシュしておき、LoadDataScriptのDisplayDataメソッドを呼び出してmainPanelのボタンにデータ情報を表示します。
data1Button等のボタンが押された時に実行する処理をRemoveAllListenerを使って全削除します。
セーブデータがあるかどうかを調べ、セーブデータがあるボタンが押された時だけ実行するメソッドを登録する事にします。
その後dataPanelの最初の子要素を選択状態にします。
commandがBackの時はconfirmationPanelでCancelButtonを押して戻ってきた時の処理なので、selectedGameObjectStackからポップしたゲームオブジェクト(confirmationPanelを開く前に選択していたボタン)を選択状態にします。
確認パネルの表示
確認パネルを表示するDisplayLoadConfirmPanelメソッドを作成します。
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 | // 確認パネルの表示 public void DisplayConfirmationPanel(string command, int dataNum) { currentCommand = CommandMode.ConfirmationPanel; dataPanelCanvasGroup.interactable = false; selectedGameObjectStack.Push(EventSystem.current.currentSelectedGameObject); // 取り付けたリスナーの削除 okButton.onClick.RemoveAllListeners(); cancelButton.onClick.RemoveAllListeners(); if (command == "Load") { confirmationPanel.transform.Find("TitleText").GetComponent<Text>().text = "データ" + dataNum + "をロードしてもよろしいですか?"; okButton.onClick.AddListener(() => loadDataScript.Load(dataName + dataNum)); cancelButton.onClick.AddListener(() => SelectCommand("Back")); } else if(command == "Delete") { confirmationPanel.transform.Find("TitleText").GetComponent<Text>().text = "データ" + dataNum + "を削除してもよろしいですか?"; okButton.onClick.AddListener(() => Delete(dataName, dataNum)); cancelButton.onClick.AddListener(() => SelectCommand("Back")); } confirmationPanel.transform.SetAsLastSibling(); confirmationPanel.SetActive(true); confirmationPanelCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(confirmationPanel.transform.Find("Panel/OKButton").gameObject); } |
DisplayConfirmPanelメソッドは何をするかというcommandと何番目のデータか?という情報を引数で受け取ります。
最初にokButtonとcancelButtonに取り付けてあるリスナーを削除します。
次にcommandがLoadの時とDeleteの時で処理を分けます。
confirmationPanelのTitleTextのTextをcommandに応じて変更します。
okButtonが押された時にcommandがLoadであればLoadDataScriptのLoadメソッドを実行するようにします。
DeleteであればDeleteメソッドを実行するようにします。
cancelButtonが押された時はcommandがLoadであれDeleteであれSelectCommandメソッドを実行します。
データ削除要請処理
データ削除をDeleteDataScriptに要請する処理を作成します。
1 2 3 4 5 6 7 8 9 10 11 | // データ削除処理 public void Delete(string dataName, int dataNum) { deleting = true; confirmationPanelCanvasGroup.interactable = false; confirmationPanel.SetActive(false); dataPanel.SetActive(false); deleteDataScript.Delete(dataName + dataNum); StartCoroutine(ProcessingAfterDeleting(dataNum)); } |
データの削除処理をした後コルーチンを使ってProcessingAfterDeletingメソッドを呼び出します。
データ削除後の処理
データ削除後の処理ProcessingAfterDeletingメソッドを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // データ削除後の処理 public IEnumerator ProcessingAfterDeleting(int dataNum) { dataResultPanel.transform.Find("Text").GetComponent<Text>().text = "セーブデータ" + dataNum + "を削除しました。"; dataResultPanel.SetActive(true); yield return new WaitForSeconds(1f); currentCommand = CommandMode.MainPanel; deleting = false; dataResultPanel.SetActive(false); mainPanelCanvasGroup.interactable = true; selectedGameObjectStack.Pop(); EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); } |
データを削除した事をdataResultPanelを表示して知らせ、MainPanelに処理を戻します。
確認パネルでキャンセルボタンを押した時の処理
確認パネルでキャンセルボタンを押した時に実行するメソッドを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 | // 確認パネルでキャンセルした時に実行 public void ReturnToDataPanel() { currentCommand = CommandMode.DataPanel; confirmationPanelCanvasGroup.interactable = false; confirmationPanel.SetActive(false); dataPanelCanvasGroup.interactable = true; EventSystem.current.SetSelectedGameObject(selectedGameObjectStack.Pop()); } |
これもCommandScriptのReturnToDataPanelメソッドの処理と同じです。
UIのボタンの設定
ロード機能のスクリプトとコマンド処理スクリプトが出来たのでUIのボタンを押した時にスクリプトのメソッドを呼び出すように設定します。
TitleCommandScriptのSelectCommandメソッドにLoadという文字列を渡して呼び出すように設定します。
DeleteButtonを押した時にはTitleCommandScriptのSelectCommandにDeleteを渡して呼び出すように設定します。
CommandManagerゲームオブジェクトのインスペクタの設定は以下のようになります。
これでセーブ機能、ロード機能と削除機能が出来ました。
セーブ、ロード、削除関連のボタン選択時の効果音を設定する
セーブ、ロード、削除のボタンには選択時のアイコンを作らなかったので、CommandPanelButtonScriptを使って効果音を鳴らそうと思うとエラーになります。
CommandPanelButtonScriptをそのまま使う場合はタイトルシーンのボタン群にも選択時のアイコンを取り付けるかダミーのアイコンを取り付けてください。
今回はとりあえずAssets/RPG/Scriptsに新しくPlayButtonSoundEffectというスクリプトを作成し、各ボタンに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class PlayButtonSoundEffect : MonoBehaviour, ISelectHandler { // ボタンを選択した時に表示する画像 private GameObject selectedImage; // 選択した時の音のAudioSource private AudioSource audioSource; void Awake() { audioSource = GetComponent<AudioSource>(); } private void OnEnable() { // アクティブになった時自身がEventSystemで選択されていたら if (EventSystem.current.currentSelectedGameObject == this.gameObject) { audioSource.Play(); } } // ボタンが選択された時に実行 public void OnSelect(BaseEventData eventData) { audioSource.Play(); } } |
各ボタンにAudio Sourceコンポーネントを取り付け、AudioClipに効果音を設定します。
セーブ、ロード、削除の各ボタンはTitleシーン、Villageシーン、WorldMapシーンにありますので全てのセーブ、ロード、削除関連のボタンにPlayButtonSoundEffectとAudioSourceの取り付けを行います。
これでボタン選択時に効果音が鳴るようになりました。
実際に確認してみると以下のようになります。
この動画はユニティちゃんのRPGを作ってみようの最初の記事で示した全体のサンプルと同じです。
終わりに
この記事でユニティちゃんのRPGを作ってみようのカテゴリは一応の完結となりました。(^^)/
とにかくやる事が多いので全部で33記事にもなってしまいました。
最後の方はもう疲れて説明を省いている所が多いかもしれません。(^_^;)
全記事通して機能の見直しやら書き直しやらをしているので、処理が抜けていたり、間違った処理が記事に書かれている可能性もなきにしもあらずです。
そういったものがあればコメント欄に書いて頂けると、わたくしの返信はありませんが、記事に反映させて頂きたいと思います。(._.)
全記事を読んで機能を作って頂いた方はお疲れさまでした。(^^)/
あ・・・・ゲームをやめるシステム組み込んでなかった・・・・(´Д`)
この作品はユニティちゃんライセンス条項の元に提供されています