今回は戦闘終了後の結果画面を作成していきたいと思います。
前回は戦闘中のカメラワークを作成しました。
ユニティちゃんのRPGを作ってみようの他の記事は
から見ることが出来ます。
前回までで戦闘のシステムを作成し、敵の攻撃、味方の攻撃を行えるようにしました。
なので戦闘を繰り返していると敵か味方のパーティーが全滅し、戦闘が終了します。
今回は戦闘の結果、敵の全滅、味方の全滅、戦闘から逃げるのに成功した時に戦闘結果を表示する機能を作成します。
また敵を倒した時と戦闘から逃げ出した時はワールドマップシーンの敵と遭遇した位置に戻し、味方が全滅した時は最初の村に戻すようにします。
今回の記事ではスクリプトを作成したあとはエラーが発生して直ぐにゲームオブジェクトには取り付けられませんので、全部のスクリプトを作り終えてから再度インスペクタの設定等をしてください。
戦闘終了画面UIの作成
まずは戦闘終了時に表示するUIを作成していきます。
BattleシーンのBattleUIを選択した状態で右クリックからUI→Panelを選択し、名前をResultPanelとします。
ResultPanelのImageのColorは黒にし、Aを255にして透けないようにします。
ResultPanelのサイズはゲーム画面全体より少し小さくします。
ResultPanelを選択した状態で右クリックからUI→Textを選択し、名前をTitleTextとします。
TitleTextは結果画面のタイトルを表示する場所です。
Textに戦闘終了という文字列を入力し、Font Sizeを50にして大きめにします。
ParagraphのAlignmentを真ん中にし、Colorを白色にします。
TitleTextはResultPanelの上側に位置するようにします。
ResultPanelを選択した状態で右クリックからUI→Panelを選択し、名前をResultTextMaskとします。
ResultTextMaskは取得した経験値などを表示する領域のマスクの領域に使い、子要素のUI要素がResultTextMaskの領域外に出た時は表示しないようにします。
ResultTextMaskのインスペクタのAdd ComponentからUI→Maskを選択し取り付けます。
インスペクタでImageのColorを白色でAを100にして少し透けるようにしました(もちろん透けないようにしても構いません)。
MaskのShow Mask Graphicにチェックを入れてこのマスク領域のImageを反映させます(チェックをしないとResultTextMaskはマスク領域としてだけ使いImageが表示されません)。
ResultTextMaskの領域は以下のような感じにしました。
ResultTextMaskを選択した状態で右クリックからUI→Textを選択し、名前をResultTextとします。
インスペクタのAnchor Presetsでtop stretchを選択し、縦は親のResultTextMaskの上、横幅は伸縮するようにします。
Heightを600とします(表示する結果画面のテキストに応じて数値を変更してください)。
Textには適当に文字列を書いて、ResultTextMaskの領域外のテキストが表示されないことを確認します。
Font Sizeを25、ParagraphのAlignmentを左上にし、Colorを白色にします。
ResultTextの位置とサイズは以下のようにResultTextMaskの幅を下に突き破るような感じになります。
ResultPanelを選択した状態で右クリックからUI→Textを選択し、名前をFinishTextとします。
Textに戦闘を終了すると入力しておきます。
Font Sizeを30、ParagraphのAlignmentを真ん中にし、Colorを赤色にします。
FinishTextの位置とサイズは以下のようになります。
次に結果は一度に表示できる量を超えた場合にResultTextをスクロールして見れるようにしたいので、スクロールバーを取り付けます。
ResultPanelを選択した状態で右クリックからUI→Scrollbarを選択します。
Anchor Presetsでmiddle centerを選択し、シーンビューで位置やサイズを調整します。
DirectionをTop To Bottomにし、Valueを1とします。
ScrollbarゲームオブジェクトのインスペクタのAdd ComponentからUI→Scroll Rectを選択し取り付けます。
Scroll RectのContentにResultTextを設定します。ScrollbarでスクロールするとResultTextが上下に移動するようになります。
Verticalにチェックを入れ上下のみスクロールするようにし、Movement TypeはClampedにしResultTextの範囲内での移動に制限します。
ViewportにResultTextMaskを設定し、Contentが動く範囲の領域を指定します。
Vertical Scrollbarには自身のScrollbarゲームオブジェクトを設定します。
スクロールバーは以下のような位置とサイズになりました。
これで結果画面のUIが出来ました。
FinishTextは最初は表示しないので、インスペクタの名前の横のチェックを外し非アクティブにしておきます。
ここまでの階層は以下のようになります。
ここまで出来たらResultPanelのインスペクタの名前の横のチェックを外し非アクティブにします。
結果画面処理スクリプトの作成
結果を表示するUIが出来たので、戦闘終了時に取得した経験値やお金、アイテムの表示や得た経験値によってレベルアップした際のステータスの表示等を行いたいと思います。
レベルアップデータの作成
レベルアップに必要な経験値を保持しておくデータを作成します。
Assets/RPG/Scripts/Statusフォルダに新しくLevelUpDictionaryとLevelUpDataスクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 | using System; using System.Collections; using System.Collections.Generic; using UnityEngine; [Serializable] public class LevelUpDictionary : SerializableDictionary<int, int> { } |
LevelUpDictionaryスクリプトは単にLevelUpDataで使用するキーをキャラクターのレベル、値を経験値としてディクショナリーをインスペクタで表示する為に作ったスクリプトです。
これは以前にItemDictionaryを作った時にやった事と同じです。
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 | using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; [Serializable] [CreateAssetMenu(fileName = "LevelUpData", menuName = "CreateLevelUpData")] public class LevelUpData : ScriptableObject { // レベルアップに必要なトータル経験値 [SerializeField] private LevelUpDictionary requiredExperience = null; // MaxHpが上がる率 [SerializeField] private float probabilityToIncreaseMaxHP = 100f; // MaxMPが上がる率 [SerializeField] private float probabliityToIncreaseMaxMP = 100f; [SerializeField] // 素早さが上がる率 private float probabilityToIncreaseAgility = 100f; [SerializeField] // 力が上がる率 private float probabilityToIncreasePower = 100f; [SerializeField] // 打たれ強さが上がる率 private float probabilityToIncreaseStrikingStrength = 100f; [SerializeField] // 魔法力が上がる率 private float probabilityToIncreaseMagicPower = 100f; // MaxHPが上がった時の最低値 [SerializeField] private int minHPRisingLimit = 1; // MaxMPが上がった時の最低値 [SerializeField] private int minMPRisingLimit = 1; // 素早さが上がった時の最低値 [SerializeField] private int minAgilityRisingLimit = 1; // 力が上がった時の最低値 [SerializeField] private int minPowerRisingLimit = 1; // 打たれ強さが上がった時の最低値 [SerializeField] private int minStrikingStrengthRisingLimit = 1; // 魔法力が上がった時の最低値 [SerializeField] private int minMagicPowerRisingLimit = 1; // MaxHPが上がった時の最高値 [SerializeField] private int maxHPRisingLimit = 50; // MaxMPが上がった時の最高値 [SerializeField] private int maxMPRisingLimit = 50; // 素早さが上がった時の最高値 [SerializeField] private int maxAgilityRisingLimit = 2; // 力が上がった時の最高値 [SerializeField] private int maxPowerRisingLimit = 2; // 打たれ強さが上がった時の最高値 [SerializeField] private int maxStrikingStrengthRisingLimit = 2; // 魔法力が上がった時の最高値 [SerializeField] private int maxMagicPowerRisingLimit = 2; // このレベルに必要な経験値 public int GetRequiredExperience(int level) { return requiredExperience.Keys.Contains(level) ? requiredExperience[level] : int.MaxValue; } public LevelUpDictionary GetLevelUpDictionary() { return requiredExperience; } public float GetProbabilityToIncreaseMaxHP() { return probabilityToIncreaseMaxHP; } public float GetProbabilityToIncreaseMaxMP() { return probabliityToIncreaseMaxMP; } public float GetProbabilityToIncreaseAgility() { return probabilityToIncreaseAgility; } public float GetProbabilityToIncreasePower() { return probabilityToIncreasePower; } public float GetProbabilityToIncreaseStrikingStrength() { return probabilityToIncreaseStrikingStrength; } public float GetProbabilityToIncreaseMagicPower() { return probabilityToIncreaseMagicPower; } public int GetMinHPRisingLimit() { return minHPRisingLimit; } public int GetMinMPRisingLimit() { return minMPRisingLimit; } public int GetMinAgilityRisingLimit() { return minAgilityRisingLimit; } public int GetMinPowerRisingLimit() { return minPowerRisingLimit; } public int GetMinStrikingStrengthRisingLimit() { return minStrikingStrengthRisingLimit; } public int GetMinMagicPowerRisingLimit() { return minMagicPowerRisingLimit; } public int GetMaxHPRisingLimit() { return maxHPRisingLimit; } public int GetMaxMPRisingLimit() { return maxMPRisingLimit; } public int GetMaxAgilityRisingLimit() { return maxAgilityRisingLimit; } public int GetMaxPowerRisingLimit() { return maxPowerRisingLimit; } public int GetMaxStrikingStrengthRisingLimit() { return maxStrikingStrengthRisingLimit; } public int GetMaxMagicPowerRisingLimit() { return maxMagicPowerRisingLimit; } } |
LevelUpDataスクリプトはScriptableObjectを継承して作成しアセットファイルとしてデータを設定出来るようにします。
フィールドでLevelUpDictionaryのフィールドrequiredExperienceを宣言し、インスペクタでレベルをキーにし、そのレベルになるのに必要な経験値を値としてインスペクタで設定出来るようにしています。
probabilityToIncreaseMaxHP等のprobability~という名前が付いたものはそのステータスがレベルアップ時に上がる確率です。
minHPRisingLimit等のmin~はレベルアップ時に最低でも上がるステータス量。
maxHPRisingLimit等のmax~はレベルアップ時に上がるステータスの最高値です。
GetRequiredExperienceメソッドは引数で受け取ったレベルがキーに含まれていればそのレベルをキーとする経験値を返し、そのレベルがキーとして見つからなければint値の最大値を返します。
これはそのレベルがキーとして登録されていればレベルアップに必要な経験値を返せるのでその値を返し、見つからなければデータがないのでintの最大値を返してレベルアップしないようにします。
intの最大値を返しているだけなのでintの最大値と同じ経験値を持っていればレベルアップする事になってその後エラーが発生すると思います。
経験値が一定の値を超えていたらレベルアップの処理を行わないような処理をこの後作成するBattleResultスクリプトに入れる必要があるかもしれません。
次にLevelUpDictionaryを反映する為に、Assets/RPG/Editor/SerializableDictionaryスクリプトに処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 | using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(ItemDictionary))] [CustomPropertyDrawer(typeof(LevelUpDictionary))] public class SerializableDictionary : SerializableDictionaryPropertyDrawer { } |
ItemDictionaryの時と同じでCustomPropertyDrawerを追加します。
Assets/RPG/Data/Status/Allyフォルダに移動し、右クリックからCreateLevelUpDataを選択し、名前をUnityChanLevelUpDataとします。
UnityChanLevelUpDataのデータを入力します。
とりあえずレベル1~5までのデータの作成と個々のステータスアップの確率や最低値、最大値の設定をしました。
UnityChanLevelUpDataを選択しCtrl+Dキーで複製し、名前をYujiLevelUpDataに変更しデータを書き換えます。
これでデータが出来ました。
Assets/RPG/Scripts/Status/AllyStatusスクリプトに追加します。
1 2 3 4 5 6 7 8 9 10 | // レベルアップデータ [SerializeField] private LevelUpData levelUpData = null; // レベルアップデータを返す public LevelUpData GetLevelUpData() { return levelUpData; } |
Assets/RPG/Data/Status/AllyフォルダのUnityChanStatusとYujiStatusのLevelUpDataにそれぞれUnityChanLevelUpDataとYujiLevelUpDataアセットファイルを設定します。
先ほど作成したキャラクターのレベルアップデータをインスペクタで設定出来るようにし、メソッドでそれを取得出来るようにします。
BattleResultスクリプトの作成
BattleManagerゲームオブジェクトに新しくBattleResultスクリプトを作成し取り付けます。
BattleResultスクリプトはAssets/RPG/Scripts/Battleフォルダに移動させます。
スクリプトが長いので分割して説明していきます。
クラス、フィールド
まずはクラス定義、フィールドを見ていきます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Linq; public class BattleResult : MonoBehaviour { // 結果を表示してからワールドマップに戻れるようになるまでの時間 [SerializeField] private float timeToDisplay = 3f; [SerializeField] private GameObject resultPanel; [SerializeField] private Text resultText; [SerializeField] private PartyStatus partyStatus; // 戦闘結果表示をしているかどうか private bool isDisplayResult; // 結果を表示し戦闘から抜け出せるかどうか private bool isFinishResult; // 戦闘に勝利したかどうか private bool won; // 逃げたかどうか private bool ranAway; // 戦闘結果テキストのスクロール値 [SerializeField] private float scrollValue = 50f; // MusicManager [SerializeField] private MusicManager musicManager; } |
timeToDisplayは結果画面を表示するまでの待機時間です。
この時間がないと敵を全滅してすぐに結果表示がされてしまいます。
resultPanelは先ほど作成したResultPanelゲームオブジェクトを設定します。
resultTextは先ほど作成したResultTextゲームオブジェクトを設定します。
partyStatusはAssets/RPG/Data/Status/Ally/PartyStatusアセットファイルを設定します。
isDisplayResultはお金の取得等の結果を表示したかどうかです。
isFinishResultは結果を表示し、他のシーンへの遷移が可能となった状態かどうかです。
wonは戦闘に勝利したかどうかです。
ranAwayは戦闘から逃げたかどうかです。
scrollValueは結果をスクロールする値を設定します。
musicManagerは音楽管理スクリプトMusicManagerを設定します(MusicManagerスクリプトは後で作成します)。
Updateメソッド
次に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 | void Update() { // 結果表示前は何もしない if (!isDisplayResult) { return; } // 結果表示後は結果表示テキストをスクロールして見れるようにする if (Input.GetAxis("Vertical") != 0f) { resultText.transform.localPosition += new Vector3(0f, -Input.GetAxis("Vertical") * scrollValue, 0f); } // 戦闘を抜け出すまでの待機時間を越えていない if (!isFinishResult) { return; } // SubmitやActionやFire1ボタンを押したらワールドマップに戻る if (Input.GetButtonDown("Submit") || Input.GetButtonDown("Action") || Input.GetButtonDown("Fire1")) { if (won || ranAway) { GameObject.Find("SceneManager").GetComponent<LoadSceneManager>().GoToNextScene(SceneMovementData.SceneType.BattleToWorldMap); } else { GameObject.Find("SceneManager").GetComponent<LoadSceneManager>().GoToNextScene(SceneMovementData.SceneType.FirstVillage); } } } |
Updateメソッドでは現在の状況に応じて処理を行っています。
isDisplayResultがfalseの時は結果をまだ表示していないのでreturnでそれ以降の処理は何もしません。
結果表示がされたらInput.GetAxis(“Vertical”)が0出ない時、つまり↑や↓のキーやコントローラーの上や下を押した時にresultTextの位置を移動させます。
スクロールはY軸方向なのでVector3のYに-Input.GetAxis(“Vertical”) * scrollValueを計算しスクロール値を求めます。
ここでInputの前にマイナスを付けているのは見た目状、下を押した時にresultTextは上に移動させるので反転させた値を設定します。
isFinishResultがfalseの時はreturnでそれ以降の処理はしません。isFinishResultがtrueになっている時はFinishTextが表示されている時です。
FinishTextが表示されている時にSubmit、Action、Fire1ボタンに割り当てられたキーやボタンが押された時にwon(勝利時)、ranAway(逃げた時)の場合はワールドマップシーンに遷移させ、戦闘に遭遇した位置と角度の場所から再開します。
elseの時(敗北時)は最初の村のシーンに遷移させます。
戦闘勝利時の初期処理メソッド
戦闘勝利時には外部からInitialProcessingOfVictoryResultメソッドを呼び出します。
1 2 3 4 5 6 | // 勝利時の初期処理 public void InitialProcessingOfVictoryResult(List<GameObject> allCharacterList, List<GameObject> allyCharacterInBattleList) { StartCoroutine(DisplayVictoryResult(allCharacterList, allyCharacterInBattleList)); } |
引数で戦闘に参加した全てのキャラクターのリストと戦闘で生き残った味方キャラクターのリストを受け取ります。
その後StartCoroutineでDisplayVictoryResultメソッドにallCharacterListとallyCharacterInBattleListを引数として渡しコルーチンを使って呼び出します。
戦闘勝利時の実際の処理を行うメソッド
戦闘勝利時の実際の処理を行うDisplayVictoryResultメソッドを作成していきます。
処理が長いのでメソッド内を分割して説明していきます。
経験値やお金、アイテムの取得処理
戦闘に参加した敵から得られた経験値やお金、アイテムを取得する処理を記述します。
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 | // 勝利時の結果 public IEnumerator DisplayVictoryResult(List<GameObject> allCharacterList, List<GameObject> allyCharacterInBattleList) { yield return new WaitForSeconds(timeToDisplay); won = true; resultPanel.SetActive(true); // 戦闘で獲得した経験値 var earnedExperience = 0; // 戦闘で獲得したお金 var earnedMoney = 0; // 戦闘で獲得したアイテムとその個数 Dictionary<Item, int> getItemDictionary = new Dictionary<Item, int>(); // Floatのランダム値 float randomFloat; // アイテム取得確率 float probability; // キャラクターステータス CharacterStatus characterStatus; // 敵のアイテムディクショナリー ItemDictionary enemyItemDictionary; foreach (var character in allCharacterList) { characterStatus = character.GetComponent<CharacterBattleScript>().GetCharacterStatus(); if (characterStatus as EnemyStatus != null) { earnedExperience += ((EnemyStatus)characterStatus).GetGettingExperience(); earnedMoney += ((EnemyStatus)characterStatus).GetGettingMoney(); enemyItemDictionary = ((EnemyStatus)characterStatus).GetDropItemDictionary(); // 敵が持っているアイテムの種類の数だけ繰り返し foreach (var item in enemyItemDictionary.Keys) { // 0~100の間のランダム値を取得 randomFloat = Random.Range(0f, 100f); // アイテムの取得確率を取得 probability = enemyItemDictionary[item]; // ランダム値がアイテム取得確率以下の値であればアイテム取得 if (randomFloat <= probability) { if (getItemDictionary.ContainsKey(item)) { getItemDictionary[item]++; } else { getItemDictionary.Add(item, 1); } } } } } resultText.text = earnedExperience + "の経験値を獲得した。\n"; resultText.text += earnedMoney + "のお金を獲得した。\n"; // パーティーステータスにお金を反映する partyStatus.SetMoney(partyStatus.GetMoney() + earnedMoney); // intのランダム値 int randomInt; AllyStatus allyStatus; // 取得したアイテムを味方パーティーに分配する foreach (var item in getItemDictionary.Keys) { // パーティーメンバーの誰にアイテムを渡すか決定 randomInt = Random.Range(0, allyCharacterInBattleList.Count); allyStatus = (AllyStatus) allyCharacterInBattleList[randomInt].GetComponent<CharacterBattleScript>().GetCharacterStatus(); // キャラクターが既にアイテムを持っている時 if (allyStatus.GetItemDictionary().ContainsKey(item)) { allyStatus.SetItemNum(item, allyStatus.GetItemNum(item) + getItemDictionary[item]); } else { allyStatus.SetItemDictionary(item, getItemDictionary[item]); } resultText.text += allyStatus.GetCharacterName() + "は" + item.GetKanjiName() + "を" + getItemDictionary[item] + "個手に入れた。\n"; resultText.text += "\n"; } } |
DisplayVictoryResultメソッドはコルーチンで呼び出されるメソッドでIEnumeratorを返します。
コルーチンに関しては
を参照してください。
最初にyield return new WaitForSeconds(timeToDisplay)でtimeToDisplay秒待機します。
DisplayVictoryResultは勝利時に呼び出されるのでwonをtrueにします。
resultPanelを表示します。
戦闘で獲得した経験値とお金を入れる為の変数を宣言します。
また取得したアイテムとその個数を入れる変数getItemDictionaryも宣言します。
foreachでallCharacterList分の繰り返しを行います。
リストに登録されたキャラクターからCharacterStatusを取得します。
CharacterStatusをEnemyStatusにキャスト出来た場合にその敵から得られる経験値、お金を取得し、earnedExperienceとearnedMoneに足します。
その敵から得られるアイテムディクショナリーをGetDropItemDictionaryメソッドで取得します。
得られたディクショナリーはキーにアイテムデータ、値にそのアイテムを取得出来る確率を設定していますので、ディクショナリーのキー分繰り返しを行ってアイテムを取得出来るかどうかを計算します。
Random.Rangeで0~100の数値でランダムな数値を取得し、そのアイテムの取得率より低い数値であればアイテムを取得したとします。
getItemDictionaryにキーが含まれていればその値を1足し、キーが含まれていなければgetItemDictionaryにキーを登録し、数値を1にします。
その後resultTextに取得した経験値とお金のテキストを足します。
取得したお金をpartyStatusのSetMoneyメソッドで反映させます。
得られたアイテムはランダムに味方パーティーメンバーに分配します。
分配するのはアイテム毎にしているので例えばハーブを5つ取得してもパーティーメンバーの誰かが5つのハーブを手に入れます。
アイテムを取得した数分をメンバーに分割する場合はアイテム数に応じて繰り返す必要がありますね。
キャラクターのレベルアップの処理
キャラクターのレベルアップの処理を先ほどのDisplayVictoryResultメソッドの処理の後(DisplayVictoryResultメソッド内)に追加します。
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 | // 上がったレベル var levelUpCount = 0; // 上がったHP var raisedHp = 0; // 上がったMP var raisedMp = 0; // 上がった素早さ var raisedAgility = 0; // 上がった力 var raisedPower = 0; // 上がった打たれ強さ var raisedStrikingStrength = 0; // 上がった魔法力 var raisedMagicPower = 0; // LevelUpData LevelUpData levelUpData; // レベルアップ等の計算 foreach (var characterObj in allyCharacterInBattleList) { var character = (AllyStatus)characterObj.GetComponent<CharacterBattleScript>().GetCharacterStatus(); // 変数を初期化 levelUpCount = 0; raisedHp = 0; raisedMp = 0; raisedAgility = 0; raisedPower = 0; raisedStrikingStrength = 0; raisedMagicPower = 0; levelUpData = character.GetLevelUpData(); // キャラクターに経験値を反映 character.SetEarnedExperience(character.GetEarnedExperience() + earnedExperience); // そのキャラクターの経験値で何レベルアップしたかどうか for (int i = 1; i < levelUpData.GetLevelUpDictionary().Count; i++) { // レベルアップに必要な経験値を満たしていたら if (character.GetEarnedExperience() >= levelUpData.GetRequiredExperience(character.GetLevel() + i)) { levelUpCount++; } else { break; } } // レベルを反映 character.SetLevel(character.GetLevel() + levelUpCount); // レベルアップ分のステータスアップを計算し反映する for (int i = 0; i < levelUpCount; i++) { if (Random.Range(0f, 100f) <= levelUpData.GetProbabilityToIncreaseMaxHP()) { raisedHp += Random.Range(levelUpData.GetMinHPRisingLimit(), levelUpData.GetMaxHPRisingLimit()); } if (Random.Range(0f, 100f) <= levelUpData.GetProbabilityToIncreaseMaxMP()) { raisedMp += Random.Range(levelUpData.GetMinMPRisingLimit(), levelUpData.GetMaxMPRisingLimit()); } if (Random.Range(0f, 100f) <= levelUpData.GetProbabilityToIncreaseAgility()) { raisedAgility += Random.Range(levelUpData.GetMinAgilityRisingLimit(), levelUpData.GetMaxAgilityRisingLimit()); } if (Random.Range(0f, 100f) <= levelUpData.GetProbabilityToIncreasePower()) { raisedPower += Random.Range(levelUpData.GetMinPowerRisingLimit(), levelUpData.GetMaxPowerRisingLimit()); } if (Random.Range(0f, 100f) <= levelUpData.GetProbabilityToIncreaseStrikingStrength()) { raisedStrikingStrength += Random.Range(levelUpData.GetMinStrikingStrengthRisingLimit(), levelUpData.GetMaxStrikingStrengthRisingLimit()); } if (Random.Range(0f, 100f) <= levelUpData.GetProbabilityToIncreaseMagicPower()) { raisedMagicPower += Random.Range(levelUpData.GetMinMagicPowerRisingLimit(), levelUpData.GetMaxMagicPowerRisingLimit()); } } if (levelUpCount > 0) { resultText.text += character.GetCharacterName() + "は" + levelUpCount + "レベル上がってLv" + character.GetLevel() + "になった。\n"; if (raisedHp > 0) { resultText.text += "最大HPが" + raisedHp + "上がった。\n"; character.SetMaxHp(character.GetMaxHp() + raisedHp); } if (raisedMp > 0) { resultText.text += "最大MPが" + raisedMp + "上がった。\n"; character.SetMaxMp(character.GetMaxMp() + raisedMp); } if (raisedAgility > 0) { resultText.text += "素早さが" + raisedAgility + "上がった。\n"; character.SetAgility(character.GetAgility() + raisedAgility); } if (raisedPower > 0) { resultText.text += "力が" + raisedPower + "上がった。\n"; character.SetPower(character.GetPower() + raisedPower); } if (raisedStrikingStrength > 0) { resultText.text += "打たれ強さが" + raisedStrikingStrength + "上がった。\n"; character.SetStrikingStrength(character.GetStrikingStrength() + raisedStrikingStrength); } if (raisedMagicPower > 0) { resultText.text += "魔法力が" + raisedMagicPower + "上がった。\n"; character.SetMagicPower(character.GetMagicPower() + raisedMagicPower); } resultText.text += "\n"; } } // 結果を計算し終わった isDisplayResult = true; // 戦闘終了のBGMに変更する musicManager.ChangeBGM(); // 結果後に数秒待機 yield return new WaitForSeconds(timeToDisplay); // 戦闘から抜け出す resultPanel.transform.Find("FinishText").gameObject.SetActive(true); isFinishResult = true; |
レベルアップ時に上がった値を入れておく変数とそのキャラクターのLevelUpDataを入れる変数を宣言します。
allyCharacterInBattleListで生き残った味方キャラクターの全てのデータ分繰り返しをします。
キャラクターが変わったらレベルアップ時に上がるステータスをリセットします。
ここで先ほど計算した取得した経験値をそのキャラクターの経験値に反映させます。
1からそのキャラクターのLevelUpDictionaryの最後のデータまで繰り返し、キャラクターの経験値によって今のレベルからどれだけ上がるかを計算します。
キャラクターの経験値が今のキャラクターのレベル+iレベルの時に必要な経験値データを上回っていたら1レベル上げて、上回っていなければbreakでループを抜けます。
ループを抜けたらキャラクターのレベルにレベルを反映します。
キャラクターが上がったレベルlevelUpCountの回数分の繰り返しを行い、ランダム値を計算して、そのステータスの上がる確率以下であればそのステータスの上がる最小値と最大値からランダムを計算し、ステータス変数に足していきます。
levelUpCountが0より大きい時、つまりレベルアップした時はresultTextにレベルアップ情報の表示をし、キャラクターのステータスに上がったステータスを反映します。
ステータスの計算と表示が出来たので、isDisplayResultをtrueにします。
musicManagerのChangeBGMメソッドを呼び出し、戦闘終了時の音楽を流すようにします。
ここでtimeToDisplay秒待機させます。
FinishTextを表示し、isFinishResultをtrueにし結果画面から抜け出せるようにします。
戦闘敗戦時の実際の処理を行うメソッド
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 | // 敗戦時の初期処理 public void InitialProcessingOfDefeatResult() { StartCoroutine(DisplayDefeatResult()); } // 敗戦時の表示 public IEnumerator DisplayDefeatResult() { yield return new WaitForSeconds(timeToDisplay); resultPanel.SetActive(true); resultText.text = "ユニティちゃん達は全滅した。"; isDisplayResult = true; yield return new WaitForSeconds(timeToDisplay); var finishText = resultPanel.transform.Find("FinishText"); finishText.GetComponent<Text>().text = "最初の街へ"; finishText.gameObject.SetActive(true); // 味方が全滅したのでユニティちゃんのHPだけ少し回復しておく var unityChanStatus = partyStatus.GetAllyStatus().Find(character => character.GetCharacterName() == "ユニティちゃん"); if(unityChanStatus != null) { unityChanStatus.SetHp(1); } isFinishResult = true; } |
敗戦をした時は勝利をした時の獲得した経験値の処理やレベルアップの処理が必要なくなります。
resultTextには『ユニティちゃん達は全滅した。』と表示します。
FinishTextには『最初の街へ』という表示に変更します。
敗戦をした時は味方キャラクターのHPが全て0になっているので、ユニティちゃんのキャラクターステータスを取得し、HPを1にしておきます。
逃げた時の処理を行うメソッド
逃げた時の処理を行うメソッドを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 逃げた時の初期処理 public void InitialProcessingOfRanAwayResult() { StartCoroutine(DisplayRanAwayResult()); } // 逃げた時の表示 public IEnumerator DisplayRanAwayResult() { yield return new WaitForSeconds(timeToDisplay); ranAway = true; resultPanel.SetActive(true); resultText.text = "ユニティちゃん達は逃げ出した。"; isDisplayResult = true; yield return new WaitForSeconds(timeToDisplay); var finishText = resultPanel.transform.Find("FinishText"); finishText.GetComponent<Text>().text = "ワールドマップへ"; finishText.gameObject.SetActive(true); isFinishResult = true; } |
逃げた時はranAwayをtrueにし、後はやっていることは大体同じです。
戦闘シーンの音楽管理スクリプトの作成
BattleResultスクリプトで結果が表示された後にMusicManagerスクリプトのChangeBGMメソッドを呼んでいるので、そのMusicManagerスクリプトを作成していきます。
MusicManagerスクリプトはAssets/RPG/Scripts/Battleフォルダに作成し、Main Cameraゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class MusicManager : MonoBehaviour { private AudioSource audioSource; // 戦闘終了後のBGM [SerializeField] private AudioClip BGMAfterTheBattle; // Start is called before the first frame update void Start() { audioSource = GetComponent<AudioSource>(); } public void ChangeBGM() { audioSource.Stop(); audioSource.loop = false; audioSource.clip = BGMAfterTheBattle; audioSource.Play(); } } |
audioSourceはMain Cameraに取り付けてあるAudio Sourceコンポーネントを取得し設定します。
BGMAfterTheBattleには結果表示時のBGMに使うオーディオクリップをインスペクタで設定します。
ChangeBGMメソッドが呼ばれたら音声を一旦止めて、loopにfalseを入れ音楽がループしないようにします。
次にAudio SourceのオーディオクリックをBGMAfterTheBattleに変更し、Playメソッドを呼んで再生をします。
BGMAfterTheBattleに設定したオーディオクリップはアセットストアでARCHEMY STUDIO MUSICさんの『Free music and SFX collection』をインポートし、その中のFantasy victoryを設定しました。
MusicManagerの作成が終わったのでBattleManagerゲームオブジェクトのBattleResultスクリプトの設定も行います。
戦闘結果画面表示メソッドの呼び出しの追加
戦闘結果画面表示と音楽管理スクリプトが出来たので、次は戦闘結果画面のスクリプトのメソッドを呼び出す処理をBattleManagerスクリプトに追加していきます。
まずはフィールドを追加します。
1 2 3 4 5 | // 結果表示処理スクリプト [SerializeField] private BattleResult battleResult; |
BattleManagerゲームオブジェクトのインスペクタでBattleResultスクリプトを設定出来るようにしています。
戦闘に勝利した時はBattleManagerスクリプトのDeleteEnemyCharacterInBattleListメソッドで敵が全滅した時なので、enemyCharacterInBattleList.Countが0になった時の最後にbattleResultのInitialProcessingOfVictoryResultメソッドを引数にallCharacterListとallyCharacterInBattleListを渡して呼び出します。
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 DeleteEnemyCharacterInBattleList(GameObject deleteObj) { enemyCharacterInBattleList.Remove(deleteObj); if (enemyCharacterInBattleList.Count == 0) { //Debug.Log("敵が全滅"); ShowMessage("敵が全滅"); battleIsOver = true; CharacterBattleScript characterBattleScript; foreach (var character in allCharacterList) { // 味方キャラクターの戦闘で増減したHPとMPを通常のステータスに反映させる characterBattleScript = character.GetComponent<CharacterBattleScript>(); if (characterBattleScript.GetCharacterStatus() as AllyStatus != null) { characterBattleScript.GetCharacterStatus().SetHp(characterBattleScript.GetHp()); characterBattleScript.GetCharacterStatus().SetMp(characterBattleScript.GetMp()); characterBattleScript.GetCharacterStatus().SetNumbness(characterBattleScript.IsNumbness()); characterBattleScript.GetCharacterStatus().SetPoisonState(characterBattleScript.IsPoison()); } } // 勝利時の結果表示 battleResult.InitialProcessingOfVictoryResult(allCharacterList, allyCharacterInBattleList); } } |
次に戦闘に敗北した時はDeleteAllyCharacterInBattleListメソッドでallyCharacterInBattleListに登録されている数が0になった時なので、その最後でbattleResultのInitialProcessingOfDefeatResultメソッドを呼び出します。
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 DeleteAllyCharacterInBattleList(GameObject deleteObj) { allyCharacterInBattleList.Remove(deleteObj); if (allyCharacterInBattleList.Count == 0) { //Debug.Log("味方が全滅"); ShowMessage("味方が全滅"); battleIsOver = true; CharacterBattleScript characterBattleScript; foreach (var character in allCharacterList) { // 味方キャラクターの戦闘で増減したHPとMPを通常のステータスに反映させる characterBattleScript = character.GetComponent<CharacterBattleScript>(); if (characterBattleScript.GetCharacterStatus() as AllyStatus != null) { characterBattleScript.GetCharacterStatus().SetHp(characterBattleScript.GetHp()); characterBattleScript.GetCharacterStatus().SetMp(characterBattleScript.GetMp()); characterBattleScript.GetCharacterStatus().SetNumbness(characterBattleScript.IsNumbness()); characterBattleScript.GetCharacterStatus().SetPoisonState(characterBattleScript.IsPoison()); } } // 敗戦時の結果表示 battleResult.InitialProcessingOfDefeatResult(); } } |
最後に逃げる事が出来た時はGetAwayメソッドで処理をしているので、その中で逃げるのに成功した時の処理の最後にbattleResultのInitialProcessingOfRanAwayResultメソッドを呼びます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 逃げる public void GetAway(GameObject character) { character.transform.Find("Marker/Image2").gameObject.SetActive(false); var randomValue = Random.value; if (0f <= randomValue && randomValue <= 0.8f) { //Debug.Log("逃げるのに成功した。"); ShowMessage("逃げるのに成功した。"); commandPanel.gameObject.SetActive(false); battleIsOver = true; // 戦闘終了 battleResult.InitialProcessingOfRanAwayResult(); } else { //Debug.Log("逃げるのに失敗した。"); ShowMessage("逃げるのに失敗した。"); commandPanel.gameObject.SetActive(false); ChangeNextChara(); } } |
これでBattleResultスクリプトのメソッドを呼び出す処理の追加が終わりました。
戦闘終了後のシーン遷移
戦闘終了後に
勝利していた場合と逃げた場合はワールドマップシーンを読み込んで、敵と遭遇した位置と角度にユニティちゃんを位置させます。
しかし敵と遭遇した時のユニティちゃんの位置と角度がわからないのでその情報を保持しておくようにします。
ワールドマップシーンで敵と遭遇した時にユニティちゃんの位置と角度をSceneMovementDataに保持出来るようにします。
Assets/RPG/Scripts/SceneMovementDataスクリプトに処理を追加します。
まずはSceneTypeにBattleToWorldMapを追加します。
1 2 3 4 5 6 7 8 9 | public enum SceneType { StartGame, FirstVillage, FirstVillageToWorldMap, WorldMapToBattle, BattleToWorldMap, } |
次に敵に遭遇した時のユニティちゃんの位置と角度を保持するフィールドを作成します。
1 2 3 4 5 6 | // ワールドマップ→戦闘シーンへ移行した時のワールドマップの位置情報 private Vector3 worldMapPos; // ワールドマップ→戦闘シーンへ移行した時のワールドマップの位置情報 private Quaternion worldMapRot; |
セッターとゲッターを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void SetWorldMapPos(Vector3 pos) { worldMapPos = pos; } public Vector3 GetWorldMapPos() { return worldMapPos; } public void SetWorldMapRot(Quaternion rot) { worldMapRot = rot; } public Quaternion GetWorldMapRot() { return worldMapRot; } |
これでSceneMovementDataにデータを保持し、それを取り出すことが出来るようになりました。
次は敵と遭遇した時のユニティちゃんの位置と角度を設定する処理を追加します。
ワールドマップシーンのEncountManagerゲームオブジェクトに取り付けているEncountManagerスクリプトを開きます。
フィールドを追加します。
1 2 3 4 | [SerializeField] private SceneMovementData sceneMovementData = null; |
EncountManagerゲームオブジェクトのインスペクタでSceneMovementDataを設定出来るようにします。
Updateメソッド内で敵と遭遇した時の処理をしている個所に処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void Update() { // その他処理 if(elapsedTime >= destinationTime) { // ワールドマップ上のユニティちゃんの位置に応じて遭遇する敵を決定する audioSource.Play(); sceneMovementData.SetWorldMapPos(unityChanObjct.transform.position); sceneMovementData.SetWorldMapRot(unityChanObjct.transform.rotation); sceneManager.GoToNextScene(SceneMovementData.SceneType.WorldMapToBattle); elapsedTime = 0f; SetDestinationTime(); } } |
sceneMovementDataのSetWorldMapPosとSetWorldMapRotメソッドでユニティちゃんの位置と角度を設定する処理を追加しました。
次にAssets/RPG/Scripts/SceneLoadingPositionスクリプトでシーン遷移時のユニティちゃんの位置と角度の処理を追加します。
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 | void Start() { // シーン遷移の種類に応じて初期位置のゲームオブジェクトの位置と角度に設定 if (sceneMovementData.GetSceneType() == SceneMovementData.SceneType.StartGame) { var initialPosition = GameObject.Find("InitialPosition"); if (initialPosition != null) { transform.position = initialPosition.transform.position; transform.rotation = initialPosition.transform.rotation; } } else if (sceneMovementData.GetSceneType() == SceneMovementData.SceneType.FirstVillage) { var initialPosition = GameObject.Find("InitialPosition"); if (initialPosition != null) { transform.position = initialPosition.transform.position; transform.rotation = initialPosition.transform.rotation; } } else if (sceneMovementData.GetSceneType() == SceneMovementData.SceneType.FirstVillageToWorldMap) { var initialPosition = GameObject.Find("InitialPositionFirstVillageToWorldMap"); if (initialPosition != null) { transform.position = initialPosition.transform.position; transform.rotation = initialPosition.transform.rotation; } } else if(sceneMovementData.GetSceneType() == SceneMovementData.SceneType.BattleToWorldMap) { transform.position = sceneMovementData.GetWorldMapPos(); transform.rotation = sceneMovementData.GetWorldMapRot(); } } |
sceneMovementData.GetSceneType()がSceneMovementData.SceneType.BattleToWorldMapだった時はSceneMovementDataのGetWorldMapPosとGetWorldMapRotメソッドを使って敵と遭遇時のユニティちゃんの位置と角度を取得し、それを設定します。
Assets/RPG/Scripts/LoadSceneManagerスクリプトに追記します。
FadeAndLoadSceneメソッドのシーンの読み込みの部分にsceneがSceneMovementData.SceneType.BattleToWorldMapとSceneMovementData.SceneType.StartGameの時の処理を追加します。
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 | // フェードをした後にシーン読み込み IEnumerator FadeAndLoadScene(SceneMovementData.SceneType scene) { if (scene != SceneMovementData.SceneType.WorldMapToBattle) { // フェードUIのインスタンス化 fadeInstance = Instantiate<GameObject>(fadePrefab); fadeImage = fadeInstance.GetComponentInChildren<Image>(); // フェードアウト処理 yield return StartCoroutine(Fade(1f)); } else { yield return StartCoroutine(FadeWorldMapToBattle(0.1f)); } // シーンの読み込み 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")); } |
これで機能が出来ました。
実行して試してみましょう。
上のようになりました。
終わりに
結果の表示は文字列が長くなると見えなくなるのでスクロール出来るようにしましたが、ResultTextの高さ分スクロールするので、結果の文字列が短くても空白部分も含めてスクロールしてしまいます。
あらかじめ画面内に収まるように結果表示をしてスクロールしないようにした方がいい気もしますね・・・・(´Д`)
この作品はユニティちゃんライセンス条項の元に提供されています