ふりかえり
前回は「レアモンスター」の追加を行いました!
倒すと確定でダイヤや大量の経験値がもらえ、演出も豪華でテンションが上がる仕様です。プレイヤーの強化に直結する、ちょっとしたご褒美のような存在になりました。
ストーリーを実装!
今回は、ついにストーリー機能の実装に着手しました!
これまでのメニューには、
- ステータス
- お知らせ
- 設定
- クレジット
…といった項目がありましたが、ここに「ストーリー」という新しいボタンを追加しました。
ストーリーで没入感アップ
ゲームの世界観にプレイヤーを引き込むには、物語性の強化がとても効果的です。
そこで、ステージの進行に応じてストーリーが段階的に解放されていく仕組みを導入。
ゲームプレイだけでなく、物語の進行というもう一つの楽しみを提供できるようになります。
スクリプトの変更
StoryManager.cs
以下のコード StoryManager.cs は、「ストーリー閲覧機能」を統括するマネージャークラスです。
主な役割
- ストーリーボタンを初期化し、押されたら該当ストーリーを表示
- ストーリーごとの**解放条件(到達ステージ)**を管理
- 未解放のストーリーはボタンをグレーアウトして押せないようにする
- ストーリータイトル・本文の表示更新
- ストーリーのスクロール管理や、ボタンの見た目変更 など
処理の大まかな流れ
- 初期化処理:
- 登録されているボタンやストーリーデータを読み込み。
- RecordManager を通じてプレイヤーの到達ステージ情報を取得。
- 解放チェックとUI更新:
- 各ストーリーに設定された requiredStage に到達しているかを確認。
- 未達成の場合は「???(ステージXで解放)」というテキストを表示し、ボタンを無効化。
- 表示処理:
- ストーリーボタンを押すと該当の StoryData を参照し、テキストを更新。
- 同時にスクロール位置やボタンの色なども更新してユーザビリティを向上。
- データ構造:
- StoryData にはタイトルと本文を格納。
- StoryUnlockCondition では、各ストーリーの解放条件(インデックス・必要ステージ)を管理。
using UnityEngine; using UnityEngine.UI; using TMPro; using System.Collections.Generic; public class StoryManager : MonoBehaviour { [Header("ストーリー選択ボタン")] public List<Button> storyButtons = new List<Button>(); // ストーリー選択ボタンのリスト public List<string> storyTitles = new List<string>(); // ストーリーのタイトルリスト [Header("ストーリー表示UI")] public ScrollRect storyScrollRect; // ストーリー表示用のスクロールビュー public TextMeshProUGUI storyContentText; // ストーリー内容表示用のテキスト public TextMeshProUGUI storyTitleText; // ストーリータイトル表示用のテキスト [Header("ストーリーデータ")] public List<StoryData> stories = new List<StoryData>(); // ストーリーデータのリスト [Header("Manager References")] public RecordManager recordManager; // RecordManagerへの参照 [Header("ストーリー解放設定")] [Tooltip("各ストーリーの解放条件を設定")] public List<StoryUnlockCondition> storyUnlockConditions = new List<StoryUnlockCondition>(); private int currentStoryIndex = 0; // 現在選択されているストーリーのインデックス void Start() { InitializeStoryButtons(); InitializeStories(); InitializeStoryUnlockConditions(); UpdateStoryUnlockStatus(); // 最初のストーリーを表示 if (stories.Count > 0) { ShowStory(0); } } // ストーリーボタンを初期化 private void InitializeStoryButtons() { for (int i = 0; i < storyButtons.Count; i++) { int index = i; // クロージャーのためにローカル変数を作成 if (storyButtons[i] != null) { storyButtons[i].onClick.AddListener(() => ShowStory(index)); // ボタンのテキストを設定 if (i < storyTitles.Count) { TextMeshProUGUI buttonText = storyButtons[i].GetComponentInChildren<TextMeshProUGUI>(); if (buttonText != null) { buttonText.text = storyTitles[i]; } } } } } // ボタンの見た目を更新 private void UpdateButtonAppearance() { for (int i = 0; i < storyButtons.Count; i++) { if (storyButtons[i] != null) { bool isActive = (i == currentStoryIndex); UpdateButtonAppearance(storyButtons[i], isActive); } } } // 個別のボタンの見た目を更新 private void UpdateButtonAppearance(Button button, bool isActive) { if (button == null) return; ColorBlock colors = button.colors; if (isActive) { // アクティブなボタンは明るく表示 colors.normalColor = Color.white; colors.highlightedColor = Color.white * 1.1f; colors.pressedColor = Color.white * 0.9f; colors.selectedColor = Color.white; } else { // 非アクティブなボタンは暗く表示 colors.normalColor = Color.gray; colors.highlightedColor = Color.gray * 1.1f; colors.pressedColor = Color.gray * 0.9f; colors.selectedColor = Color.gray; } button.colors = colors; } // 次のストーリーを表示 public void ShowNextStory() { int nextIndex = (currentStoryIndex + 1) % stories.Count; ShowStory(nextIndex); } // 前のストーリーを表示 public void ShowPreviousStory() { int previousIndex = (currentStoryIndex - 1 + stories.Count) % stories.Count; ShowStory(previousIndex); } // 現在のストーリーインデックスを取得 public int GetCurrentStoryIndex() { return currentStoryIndex; } // ストーリーの総数を取得 public int GetStoryCount() { return stories.Count; } // ストーリー解放状態を更新 public void UpdateStoryUnlockStatus() { if (recordManager == null) return; int maxStageReached = recordManager.GetRecords().maxStageReached; for (int i = 0; i < storyButtons.Count; i++) { if (storyButtons[i] != null) { bool isUnlocked = IsStoryUnlocked(i, maxStageReached); UpdateButtonUnlockStatus(storyButtons[i], isUnlocked, i); } } } // ストーリー解放条件を初期化 private void InitializeStoryUnlockConditions() { // デフォルトの解放条件を設定(Inspectorで設定されていない場合) if (storyUnlockConditions.Count == 0) { storyUnlockConditions.Add(new StoryUnlockCondition { storyIndex = 0, requiredStage = 1, description = "はじまりの物語" }); storyUnlockConditions.Add(new StoryUnlockCondition { storyIndex = 1, requiredStage = 4, description = "魔法の森の秘密" }); storyUnlockConditions.Add(new StoryUnlockCondition { storyIndex = 2, requiredStage = 7, description = "闇の王の復活" }); } } // 指定されたストーリーが解放されているかチェック private bool IsStoryUnlocked(int storyIndex, int maxStageReached) { // storyUnlockConditionsから該当する条件を検索 foreach (var condition in storyUnlockConditions) { if (condition.storyIndex == storyIndex) { return maxStageReached >= condition.requiredStage; } } // 条件が見つからない場合は解放されていないとする return false; } // ボタンの解放状態を更新 private void UpdateButtonUnlockStatus(Button button, bool isUnlocked, int storyIndex) { if (button == null) return; // ボタンの有効/無効を設定 button.interactable = isUnlocked; // ボタンのテキストを取得 TextMeshProUGUI buttonText = button.GetComponentInChildren<TextMeshProUGUI>(); if (buttonText != null) { if (isUnlocked) { // 解放済み:通常のテキスト if (storyIndex < storyTitles.Count) { buttonText.text = storyTitles[storyIndex]; } } else { // 未解放:解放条件を表示 int requiredStage = GetRequiredStageForStory(storyIndex); buttonText.text = $"??? (ステージ{requiredStage}で解放)"; } } // ボタンの色を更新 ColorBlock colors = button.colors; if (isUnlocked) { // 解放済み:通常の色 colors.normalColor = Color.white; colors.highlightedColor = Color.white * 1.1f; colors.pressedColor = Color.white * 0.9f; colors.selectedColor = Color.white; } else { // 未解放:グレーアウト colors.normalColor = Color.gray; colors.highlightedColor = Color.gray; colors.pressedColor = Color.gray; colors.selectedColor = Color.gray; } button.colors = colors; } // 指定されたストーリーを表示(解放チェック付き) public void ShowStory(int storyIndex) { if (storyIndex < 0 || storyIndex >= stories.Count) { Debug.LogWarning($"無効なストーリーインデックス: {storyIndex}"); return; } // 解放チェック if (recordManager != null) { int maxStageReached = recordManager.GetRecords().maxStageReached; if (!IsStoryUnlocked(storyIndex, maxStageReached)) { int requiredStage = GetRequiredStageForStory(storyIndex); Debug.Log($"ストーリー{storyIndex}はまだ解放されていません。ステージ{requiredStage}に到達してください。"); return; } } currentStoryIndex = storyIndex; StoryData story = stories[storyIndex]; // ストーリー内容を表示 if (storyContentText != null) { storyContentText.text = story.content; } // ストーリータイトルを表示 if (storyTitleText != null) { storyTitleText.text = story.title; } // スクロール位置を一番上に設定 if (storyScrollRect != null) { storyScrollRect.verticalNormalizedPosition = 1f; } // ボタンの見た目を更新 UpdateButtonAppearance(); Debug.Log($"ストーリーを表示: {story.title}"); } // 指定されたストーリーの必要ステージ数を取得 private int GetRequiredStageForStory(int storyIndex) { foreach (var condition in storyUnlockConditions) { if (condition.storyIndex == storyIndex) { return condition.requiredStage; } } return 999; // 条件が見つからない場合は非常に高い値を返す } // 外部から呼び出し可能なストーリー解放状態更新メソッド public void CheckAndUpdateStoryUnlocks() { UpdateStoryUnlockStatus(); } } // ストーリーデータクラス [System.Serializable] public class StoryData { public string title; // ストーリーのタイトル public string content; // ストーリーの内容 } // ストーリー解放条件クラス [System.Serializable] public class StoryUnlockCondition { [Tooltip("ストーリーのインデックス(0から開始)")] public int storyIndex; [Tooltip("解放に必要なステージ到達数")] public int requiredStage; [Tooltip("ストーリーの説明(デバッグ用)")] public string description; }
Unityでの設定
Unityエディタ上では以下の手順で簡単に設定できます:
1.空のGameObjectを作成して StoryManager.cs をアタッチ。
2.ボタンを複数作成し、storyButtons リストにアサイン。
3.Scroll View を作成して、本文やタイトルの表示エリアをセット。

4.StoryData と解放条件をインスペクターで入力。
※この構造なら、後からストーリー数を増やしたり、解放条件を調整したりするのも簡単です。

動作確認
- ストーリーボタンを押すと正しくストーリーが表示されました!
- 到達していないステージのストーリーは「???」で表示され、ボタンも無効になっていました。
これにより、「ストーリーを読みたければ先に進まないと!」という自然な動機づけが実現できました。
まとめ
- ストーリー閲覧機能を無事実装!
- 解放型の構成により、プレイヤーの成長と物語の進行がリンク。
- ゲーム世界への没入感を強化する大きな一歩となりました。
ストーリーの中身はこれから本格的に練っていきますが、まずは土台ができたということで大きな前進です。
進捗
今回はレアモンスターの追加をしました!
- (済)オート戦闘:ボタン操作なしでもバトルが進むようにする。
- (済)オートスキル発動:スキルも自動で発動する機能。
- (済)UIのブラッシュアップ:デザインとレイアウトを整えて見やすくする。
- (済)ステージ数の拡充(最低20):遊びごたえを高めるためにボリュームアップ。
- 復活機能(広告視聴):ゲームオーバー時、広告視聴で復活できる仕組み。
- 広告報酬でダイヤ獲得:無課金でもダイヤが手に入る仕組み。
- (済)G強化・アーティファクト・転生の拡張:各システムにさらなる深みを持たせる。
- (済)回復薬の追加:ステージを進めやすくする。
- アニメーションの追加:ゲーム画面にもっと動きを持たせる。
- (済)レアモンスターの追加:わくわく感を追加する。
- 放置要素の追加:プレイしていない時間も強くなる。
- (仮済)メニューの内容追加:ストーリー、記録、ガイドなどプレイヤーが遊びやすくする。
次回予告
次回は「記録機能」を追加予定です!
プレイヤーのバトルの履歴や転生回数などを記録していくことで、達成感を深める機能となる予定です。お楽しみに!