見出し画像

【Unity】【C#】クエストシステムを実装する 第2回

概要

第2回=================================

5.List<Quest>にデータを持たせる

6.ScrollView GameObjectを作成する(※今回は割愛)

7.QuestStatusを更新する

8.達成済みQuestがあるか判定する

9.達成済み、未達成、報酬受領済みでソートする

10.まとめ ※有料

5.List<Quest>にデータを持たせる

ゲーム中に変化しないデータはScriptableObjectに格納してそこから取得できますが、QuestはQuestStatusが変化するのでMonoBehaviourを継承したGameManagerクラスに格納してゲームに使用します。

また、GameManagerには2.で作成したEntity_Questインスタンスもメンバーに追加し、初期化処理でList<Quest> Questsにデータを読み込みます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
	//2.クエストデータをUnityにインポートする で作成したもの
	[SerializeField]private Entity_Quest EQ;

	public Entity_Quest _Entity_Quest {
		get {
			if (EQ == null) {
				EQ = Resources.Load<Entity_Quest> ("ScriptableObjects/Quest");
			}
			return EQ;
		}
	}

	//4.QuestManager.csを作成する で作成したもの
	public QuestManager _QuestManager = new QuestManager ();

	void Awake ()
	{
		DontDestroyOnLoad (gameObject);
		Load();
		InitQuestManager ()
	}

	//QuestManagerの初期化処理
	void InitQuestManager ()
	{
		if (_QuestManager.Quests.Count < 1) {
			//Listの要素を必要なだけnewする
			while (_QuestManager.Quests.Count < _Entity_Quest.param.Count) {
				_QuestManager.Quests.Add (new Quest ());
			}
			//ScriptableObjectからデータを取得する
			for (int i = 0; i < _QuestManager.Quests.Count; i++) {
				_QuestManager.Quests [i].quest = _Entity_Quest.param [i];
				_QuestManager.Quests [i].Name =	_QuestManager.Quests [i].quest.Descript;
			}
			Save ();
		}
	}

	//セーブデータのセーブ処理
	void Save ()
	{
		SaveData.SetClass<QuestManager> ("QuestManager", _QuestManager);
	}

	//セーブデータのロード処理
	void Load ()
	{
		_QuestManager = SaveData.GetClass<QuestManager> ("QuestManager", new QuestManager ());
	}
}

Entity_QuestのScriptableObjectは、Resourcesフォルダ下に置いておきましょう。通常はTitleSceneなど最初に読み込むSceneにGameManager(GameObject)を配置してScriptableObjectをアタッチしておきますが、TitleScene以外でテストプレイする際にはGameManager(GameObject)を生成するようにしているため、その場合はResourcesから読み込むようにします。

セーブデータのSave(),Load()に関しては、解説を割愛します。こちらの記事を参照ください。

ゲームを再生してみて、こんな感じでデータが読み込まれていればOKです。

6.ScrollView GameObjectを作成する(※今回は割愛)

5.までで元となるList<Quest>をゲームで使用できるようになりました。ここからはScrollViewのContentにクエスト数と同じだけ子要素GameObjectを突っ込んで、クエストに応じて表示や挙動を変えてあげればOKです。

自分の場合はクエストが450個超もあるため、全部をGameObjectとして配置すると負荷が重くなると判断して、下記のAssetを購入して導入しました。ちなみに導入はぶっちゃけ少し面倒でした。Grid Layoutにも対応してないのも残念。しかし導入さえしてしまえば高速低負担で動作していい感じです。

Assetを使わない場合は下記などが参考になると思います。

こういうのもあります。

データを流し込んでUIに反映させればとりあえずは表示されます。

7.QuestStatusを更新する

表示するまではできましたが、クエストは未達成のものが達成されたり、報酬を受け取ったら受け取り済みになったりと、状態の更新があります。

4.QuestManager.csを作成する で作成したQuestManager中のQuestStateUpdate() でこの更新処理を行います。

また、クエスト更新の条件として使うためのフラグ管理クラスや、バトル数などをカウントするカウンタークラスを作成する必要があります。

[System.Serializable]
public class GameFlags
{
	//プレイヤー名が変更されるとtrueになる
	public bool isPlayerNameChanged = false;
	//新しくデッキを編成するとtrueになる
	public bool isNewDeckBuilt = false;
	//ヘルプメニューを一度でも開くとtrueになる
	public bool isHelpMenuOpened = false;
	//属性相性表を一度でも開くとtrueになる
	public bool isAttributionTableOpened = false;
}
[System.Serializable]
public class GameCounter
{
	//バトル数(バトル後に加算される)
	public int BattleCount = 0;
	//勝利数(バトル勝利後に加算される)
	public int WinCount = 0;
	//敗北数(バトル敗北後に加算される)
	public int LoseCount = 0;
}

ここに記載しているのはサンプルなので、実際は自分のゲームに合わせて設定してください。

そしてGameFlagsクラスとGameCounterクラスもGameManagerに持たせます。メンバーに追加して、Save(),Load()の対象にも追加します。

これでようやくQuestStateUpdate ()の中身を書けるようになりました。引数にGameMangerを設定して、GameFlagsやGameCounterを参照できるようにしてます。

public void QuestStateUpdate (GameManager GM)
{
	//未達成のクエストを抽出してListに格納
	var NotAchievedQuests = (from x in Quests
	                         where x._QuestState == Quest.QuestState.NotAchieved
	                         select x).ToList ();
	//for文で順々に処理する
	for (int i = 0; i < NotAchievedQuests.Count; i++) {
		//QuestIDで条件分岐させる
		switch (NotAchievedQuests [i].quest.QuestID) {
		case 100: //プレイヤー名を変更する
			if (GM._GameFlags.isPlayerNameChanged) {
				NotAchievedQuests [i]._QuestState = Quest.QuestState.Achieved;
			}
			break;
		/*中略*/
		case 200: //1回バトルする
			if (GM._GameCounter.BattleCount > 0) {
				NotAchievedQuests [i]._QuestState = Quest.QuestState.Achieved;
			}
			break;
		/*以下略*/
		}
	}
}

ちなみにcase文は1個ずつ手書きだと大変なので、スプレッドシートの文字列結合(""&"" でできる)を使ったりして共通部分を自動生成すると手間を省けます。今回の例ではQuestIDとif文条件だけそれぞれ異なるので、それらをG列で結合させてます。

//G列
="case "&$C2&": if ("&$F2&") {NotAchievedQuests [i]._QuestState = Quest.QuestState.Achieved;} break;"

8.達成済みQuestがあるか判定する

達成済みクエストがある場合は「New」などを画面に表示して、プレイヤーにクエストを確認するよう促すUIがあると快適さが増します。

前述したQuestStateUpdate()内で行ってもいいですが、ただでさえ行数が多くなる処理のため、外出しにした方がいいでしょう。

//QuestManager内メソッド
//達成済みクエストの有無を返す
public bool isAchievedExist ()
{
	//LinqのAny()は1つでもあればtrueを返す
	bool isExist = Quests.Any (q => q._QuestState == Quest.QuestState.Achieved);
	return isExist;
}

9.達成済み、未達成、報酬受領済みでソートする

達成済みクエストを最上部に表示して、報酬受取済みクエストは最下段に表示いたいので、その順でList<Quest>をソートします。既にQuestStateをその順にしているので、OrderByでソートすれば望む順にソートされます。

//QuestManager内メソッド
//QuestStateでソートしてSortedQuestsにキャッシュする
public void SortQuests ()
{
	SortedQuests = Quests.OrderBy (q => q._QuestState).ToList ();
}

10.まとめ

完成イメージ

有料部分では下記について書いてます。支援頂けると今後の記事執筆のモチベーションにもなりますので、よろしければ購入ください🙏

QuestManager.cs まとめ版
GameManger.cs まとめ完成版
テスト用実行Script
クエスト進捗を表示するための備考

ここから先は

4,219字

¥ 200

期間限定!PayPayで支払うと抽選でお得

この記事が気に入ったらチップで応援してみませんか?