ターン制RPGの作り方#25|セーブ・ロードとタイトル画面:遊んだ続きから始めよう

タイトル画面で「まほう図書館の地下」の文字が上から落ちてきて、ぽよんと弾む。下にボタンのウインドが出て「つづきから」を選ぶと、画面が暗くなり、Lv10のスライム・480ゴールドのロビーから始まる
タイトルの「つづきから」で、前に遊んだ続きから

前回、自動プレイで、地下3階とボスのバランスを決めました。これで最後まで遊べるようになりましたが、今はゲームを終わると、レベルもお金も最初に戻ってしまいます・・・。今回は、ロビーでセーブして、タイトル画面の「つづきから」で続きから遊べるようにしたいと思います!

目次

今回作るもの

セーブが無いときのタイトル画面。「つづきから」は灰色で選べず、「はじめから」を選ぶと、Lv1の3人と50ゴールドのロビーから始まる
セーブが無いときは、はじめからだけ
  • ロビーの セーブ のボタンで、仲間・持ち物・お金・進んだ階を、JSONのファイルに保存する
  • 新しい Titleシーン を作る。タイトルの文字が落ちてきて、「はじめから」「つづきから」を選べる
  • セーブが無いときは、「つづきから」を押せないようにする
スクリプト変えること
SaveData(新しく作る)セーブするものを、1つのクラスにまとめる
SaveSystem(新しく作る)JSONにして、ファイルに書く・読む
Inventory個数を調べる・全部なくす
GameSessionセーブデータを作る・セーブの中身に置きかえる
LobbyMenuセーブのボタン
TitleMenu(新しく作る)タイトル画面のボタン

JSONで保存するやり方は、12-6 データを保存しよう:PlayerPrefsとJSONで紹介しています。今回は、そのやり方をRPGのデータに使います。

スライム

これで、ボスの前でセーブして、ひと休みできますね!

セーブするものを決めよう

ロビーの画面。スライムLv10・フェアリーLv9・キョンシーLv9のHPとMP、右上に480 G、左にメドゥーサが本を読んでいる
ロビーに見えているものを、全部残したい

まず、何を残せば「続きから」になるかを決めます。#17で、シーンをまたいで持ち越すものをGameSessionにまとめました。セーブするのも、その中身です。

残すものGameSessionの中
仲間4人(並び順・経験値・今のHPとMP・装備)Members
持ち物(どうぐと個数)Inventory
装備のふくろ(買って、まだ付けていない装備)Equipments
お金Gold
クリアした階の数ClearedFloorCount

レベルと能力は、#17で経験値から計算するようにしたので、経験値だけ残せば戻せます。セーブはロビーでだけ出来るようにするので、今いる階や、バトルの途中のことは残さなくて大丈夫です。

アセットは名前で残す

仲間のData(CharacterData)や、どうぐ(ItemData)、装備(EquipmentData)は、ScriptableObjectのアセットです。JsonUtilityでアセットをそのまま書き出すと、{"instanceID":12345}のような番号になります。この番号は、ゲームを起動するたびに変わるので、次に遊ぶときには使えません。

そこで、アセットは名前(SlimeやHerbTeaのような、ファイルの名前)で残し、読むときに、その名前のアセットを探して戻します。

名前で探すので、ゲームを配ったあとでアセットのファイルの名前を変えると、前のセーブが読めなくなります。名前を変えたいときは、画面に出す名前(characterNameなど)のほうを変えます。

SaveData

Scriptsフォルダに、SaveDataを作ります。セーブするものを、1つのクラスにまとめます。

using System.Collections.Generic;

// セーブするもの。キャラクター・どうぐ・装備のアセットは、そのままでは保存できないので、アセットの名前で持つ
[System.Serializable]
public class SaveData
{
    public List<MemberSaveData> members = new List<MemberSaveData>(); // 仲間全員(並び順のまま。上の3人がバトルに出る)
    public List<ItemSaveData> items = new List<ItemSaveData>();       // 持ち物
    public List<string> equipments = new List<string>();              // 装備のふくろ
    public int gold;
    public int clearedFloorCount; // クリアした階の数
}

// 仲間1人分
[System.Serializable]
public class MemberSaveData
{
    public string character; // CharacterData の名前(Slime など)
    public int totalExp;
    public int hp;
    public int mp;
    public string weapon; // ぶきの名前(無ければ空)
    public string armor;  // ぼうぐの名前(無ければ空)
}

// どうぐ1種類と、その個数
[System.Serializable]
public class ItemSaveData
{
    public string item;
    public int count;
}
  • [System.Serializable]を付けたクラスは、JsonUtilityでJSONにできます(12-6)
  • JsonUtilityがJSONにするのは、publicの変数(フィールド)です。{ get; set; }のプロパティは書き出されないので、ここでは変数で書いています
  • Listや、クラスの中のクラス(MemberSaveData)も、[System.Serializable]が付いていれば、そのまま書き出せます

ファイルに書いて、読もう

ロビーで「セーブ」を選ぶと、スライム・フェアリー・キョンシーが正面を向いて本を開き、「ぼうけんの きろくを かきこんだ!」と出る
セーブのボタンで、3人が記録を書きこむ

この節では、SaveDataをファイルに書く・読むSaveSystemを作り、ロビーのセーブのボタンから使えるようにします。

SaveSystem

Scriptsフォルダに、SaveSystemを作ります。どのシーンからでも使えるように、staticのクラスにします。

using System.IO;
using UnityEngine;

// セーブデータを JSON にして、ファイルに書く・読む
public static class SaveSystem
{
    // 保存する場所(Windows なら C:/Users/<ユーザー名>/AppData/LocalLow/<会社名>/<プロダクト名>)
    private static string SavePath => Path.Combine(Application.persistentDataPath, "save.json");

    // セーブのファイルがあるか(タイトルの「つづきから」を押せるか)
    public static bool HasSave => File.Exists(SavePath);

    public static void Save(SaveData data)
    {
        string json = JsonUtility.ToJson(data, true);
        File.WriteAllText(SavePath, json);
        Debug.Log($"セーブしました:{SavePath}");
    }

    // セーブを読む(無ければ null)
    public static SaveData Load()
    {
        if (!HasSave)
        {
            return null;
        }
        string json = File.ReadAllText(SavePath);
        return JsonUtility.FromJson<SaveData>(json);
    }
}
  • Application.persistentDataPathは、アプリがデータを保存してよい場所です。会社名とプロダクト名は、Edit>Project Settings>Player の Company Name と Product Name です
  • ToJsonの2つ目をtrueにすると、改行の入った読みやすいJSONになります
  • セーブしたら、保存した場所をConsoleに出します。あとでファイルを見たり、消したりするときに使います

持ち物の個数を調べる

セーブデータを作るときに、どうぐの個数が要ります。読むときには、最初の持ち物を一度消してから、入れ直します。InventoryのItemsの上に、2つのメソッドを足します。

    // item を何個持っているか(持っていなければ 0)
    public int CountOf(ItemData item)
    {
        foreach (ItemStack stack in stacks)
        {
            if (stack.item == item)
            {
                return stack.count;
            }
        }
        return 0;
    }

    // 全部なくす(セーブから読むとき、最初の持ち物を消してから入れ直す)
    public void Clear()
    {
        stacks.Clear();
    }

GameSessionで、セーブデータを作る・戻す

GameSessionのInspector。All Itemsに HerbTea・MagicCookie・AntidoteCandy・AlarmClock、All Equipmentsに PaperKnife・MagicPen・Paperweight・BookCover・LibrarianApron が並んでいる
名前で探すアセットを、全部入れておく

GameSessionに、今の中身からSaveDataを作るメソッドと、SaveDataの中身に置きかえるメソッドを足します。

まず、名前からアセットを探すための一覧を、floorsの下に足します。仲間は、startMembersに4人全員が入っているので、そこから探します。

    [SerializeField] private ItemData[] allItems;           // セーブから読むときに、名前で探す どうぐ(全部入れておく)
    [SerializeField] private EquipmentData[] allEquipments; // 同じく、装備(全部入れておく)

Instanceの下に、「つづきから始めるか」を足します。タイトル画面でtrueにしてから、ロビーを読み込みます。

    // true にしてからロビーを読み込むと、セーブの続きから始める(タイトルの「つづきから」)
    public static bool LoadsSave { get; set; }

Awakeの最後(Gold = startGold;の下)に、つづきからのときに、セーブの中身で置きかえる処理を足します。

        // つづきから:最初の値を、セーブの中身で置きかえる
        if (LoadsSave)
        {
            LoadsSave = false; // 次に再生したときに、残らないように
            ApplySaveData(SaveSystem.Load());
        }

Awakeの下に、4つのメソッドを足します。

    // 今の仲間・持ち物・お金・進んだ階を、セーブデータにする
    public SaveData CreateSaveData()
    {
        SaveData data = new SaveData();
        foreach (PartyMember member in Members)
        {
            MemberSaveData memberData = new MemberSaveData();
            memberData.character = member.Data.name;
            memberData.totalExp = member.TotalExp;
            memberData.hp = member.Hp;
            memberData.mp = member.Mp;
            memberData.weapon = NameOf(member.Weapon);
            memberData.armor = NameOf(member.Armor);
            data.members.Add(memberData);
        }
        foreach (ItemData item in Inventory.Items())
        {
            ItemSaveData itemData = new ItemSaveData();
            itemData.item = item.name;
            itemData.count = Inventory.CountOf(item);
            data.items.Add(itemData);
        }
        foreach (EquipmentData equipment in Equipments)
        {
            data.equipments.Add(equipment.name);
        }
        data.gold = Gold;
        data.clearedFloorCount = ClearedFloorCount;
        return data;
    }

    // セーブデータの中身に置きかえる(名前から、アセットを探して戻す)
    private void ApplySaveData(SaveData data)
    {
        Members.Clear();
        foreach (MemberSaveData memberData in data.members)
        {
            CharacterData character = FindByName(startMembers, memberData.character);
            PartyMember member = new PartyMember(character, memberData.totalExp);
            member.Equip(EquipSlot.Weapon, FindByName(allEquipments, memberData.weapon));
            member.Equip(EquipSlot.Armor, FindByName(allEquipments, memberData.armor));
            // HP・MP は、装備を付けてから入れる(装備の分だけ、最大HP・MPが増えるので)
            member.Hp = memberData.hp;
            member.Mp = memberData.mp;
            Members.Add(member);
        }

        Inventory.Clear();
        foreach (ItemSaveData itemData in data.items)
        {
            Inventory.Add(FindByName(allItems, itemData.item), itemData.count);
        }
        Equipments.Clear();
        foreach (string equipmentName in data.equipments)
        {
            Equipments.Add(FindByName(allEquipments, equipmentName));
        }
        Gold = data.gold;
        ClearedFloorCount = data.clearedFloorCount;
    }

    // アセットの名前(無ければ空の文字)
    private static string NameOf(Object asset)
    {
        if (asset == null)
        {
            return "";
        }
        return asset.name;
    }

    // assets の中から、名前が assetName のものを探す(無ければ null)
    private static T FindByName<T>(T[] assets, string assetName) where T : Object
    {
        foreach (T asset in assets)
        {
            if (asset.name == assetName)
            {
                return asset;
            }
        }
        return null;
    }
  • nameは、アセットのファイルの名前です(Slime.assetならSlime)
  • FindByNameは、#24のFindSkill<T>と同じように、型をTにしたメソッドです。where T : Objectと書くと、TはUnityのアセットだけになるので、nameが使えます。どうぐ・装備・キャラクターの3つを、1つのメソッドで探せます
  • 装備を付けてから、HPとMPを入れます。Equipは、HPが最大をこえないように削るので、先にHPを入れると、装備で増えた分が消えてしまいます
  • LoadsSaveは、読んだらすぐfalseに戻します。static の値は、シーンが変わっても残るので、戻さないと、あとで別の場面からロビーへ来たときにも、また読み込んでしまいます

GameSessionのオブジェクトを選び、All Items にどうぐの4つ(ハーブティー・まほうのクッキー・どくけしキャンディ・めざましどけい)、All Equipments に装備の6つを入れます。Itemsフォルダ・Equipmentsフォルダのアセットを、まとめて欄の名前にドラッグすると、一度に入ります。

新しいどうぐや装備を作ったら、ここにも入れるのを忘れないようにします。入れ忘れると、セーブから読んだときに、そのどうぐが消えてしまいます。

ロビーのセーブのボタン

LobbyMenuのInspectorの一部。Party Buttonの下に、Save Buttonの欄があり、SaveButton (Button) が入っている
Save Buttonの欄に、SaveButtonを入れる

#17で、ロビーにセーブのボタンだけ置いて、押せないようにしていました。これを使えるようにします。

LobbyMenuのpartyMenuの下に、ボタンの欄を足します。

    [SerializeField] private Button saveButton;       // セーブ

Startで、ほかのボタンと同じように、押されたときのメソッドを登録します。

        partyButton.onClick.AddListener(OnPartyClicked);
        saveButton.onClick.AddListener(OnSaveClicked);

OnShopClickedの上に、押されたときのメソッドを足します。休憩室のOnRestClickedと同じ形です。

    private async void OnSaveClicked()
    {
        if (isBusy)
        {
            return;
        }
        isBusy = true;
        try
        {
            await SaveAsync();
        }
        catch (System.OperationCanceledException)
        {
            return;
        }
        isBusy = false;
    }

RestAsyncの上に、セーブするメソッドを足します。ただ保存するだけだと、押したのかどうか分かりにくいので、3人が本を開いて、記録を書きこむようにします。

    // セーブ:3人が本に、ぼうけんの記録を書きこむ。そのあいだに、ファイルに保存する
    private async Awaitable SaveAsync()
    {
        GameSession session = GameSession.Instance;
        List<PartyMember> party = session.Party;
        List<Awaitable> writings = new List<Awaitable>();
        for (int i = 0; i < party.Count; i++)
        {
            if (!party[i].IsDead) // 倒れている人は、書かない
            {
                writings.Add(partyViews[i].PlayBookAsync());
            }
        }
        SaveSystem.Save(session.CreateSaveData());
        foreach (Awaitable writing in writings)
        {
            await writing;
        }
        await messageWindow.ShowAsync("ぼうけんの きろくを かきこんだ!");
        foreach (UnitView view in partyViews)
        {
            view.ResumeIdle();
        }
    }
  • 本を読むポーズ(PlayBookAsync)は、#14でまほうを覚えたときに作ったものです
  • 3人のポーズを先に全部始めてから、順にawaitします(同時に動かす形)
  • 最後の絵(book_03)のまま、メッセージを出します。読み終わったら、ResumeIdleで待機の弾みに戻します

Lobbyシーンで、次のように設定します。

  • LobbyWindowの中の SaveButton を選び、Buttonの Interactable にチェックを入れる
  • Lobbyのオブジェクトを選び、LobbyMenuの Save Button に、SaveButtonをドラッグする

保存したファイルを見てみる

再生して、ロビーの セーブ を押すと、Consoleに「セーブしました:」と、保存した場所が出ます。エクスプローラーでその場所を開き、save.jsonをメモ帳で開くと、次のように書かれています(レベルを上げて、少し買い物をしたあとのセーブです)。

{
    "members": [
        {
            "character": "Slime",
            "totalExp": 330,
            "hp": 83,
            "mp": 17,
            "weapon": "PaperKnife",
            "armor": ""
        },
        {
            "character": "Fairy",
            "totalExp": 300,
            "hp": 50,
            "mp": 44,
            "weapon": "",
            "armor": "BookCover"
        },
        {
            "character": "Jiangshi",
            "totalExp": 310,
            "hp": 61,
            "mp": 14,
            "weapon": "",
            "armor": ""
        },
        {
            "character": "Medusa",
            "totalExp": 280,
            "hp": 52,
            "mp": 42,
            "weapon": "",
            "armor": ""
        }
    ],
    "items": [
        {
            "item": "HerbTea",
            "count": 3
        },
        {
            "item": "MagicCookie",
            "count": 2
        },
        {
            "item": "AlarmClock",
            "count": 1
        }
    ],
    "equipments": [
        "MagicPen"
    ],
    "gold": 480,
    "clearedFloorCount": 2
}
  • アセットは、"Slime"や"PaperKnife"のように、名前で入っています
  • 装備を付けていないところは、空の文字("")です。読むときは、FindByNameが見つけられずにnullを返すので、装備なしに戻ります
  • キョンシーのHPは、61/64のまま残っています。セーブしたときのHPから続きます
フェアリー

ちゃんと、わたしの ブックカバー も書いてあるわね!

タイトル画面を作ろう

タイトル画面。暗い画面が明るくなりながら、「まほう図書館の地下」の文字が上から落ちてきて、着地で横につぶれてから戻る。そのあと、下に「はじめから」「つづきから」のウインドがふわっと出る
タイトルの文字が落ちてきて、ぽよんと弾む

この節では、ゲームを起動したときに最初に出る、タイトル画面を作ります。

Titleシーンを作る

Titleシーンを開いたエディタ。HierarchyにCanvas(Background・Stage/PartyArea のSlime・Fairy・Jiangshi・Medusa・Logo のShadowとText・TitleWindow のNewGameButtonとContinueButton・SceneFade)、EventSystem、Title。Gameビューに、タイトルの文字と4人、下に2つのボタン
Titleシーン

ロビーには、背景・4人・ボタンのウインド・暗くする板(SceneFade)と、タイトル画面に要るものがそろっています。Lobbyシーンを複製して作ります。

  1. ProjectのScenesフォルダで、Lobbyを選んで Ctrl+D で複製し、名前を Title にする。ダブルクリックで開く
  2. いらないものを消す。Canvasの中の StatusWindow・GoldWindow・MessageWindow・ScreenFade・ShopWindow・EquipWindow・CompareWindow・FloorWindow・PartyWindow と、Canvasの外の GameSession・Lobby・Shop・EquipMenu・PartyMenu・BalanceTester
  3. Background の Image の Color を、少し暗い色(R 140・G 140・B 158)にする。上に出す文字が読みやすくなります
  4. PartyArea(Stageの中)の Anchor Presets を真ん中にして、Pos X 0・Pos Y -40 にする。中の4人を横に並べる
オブジェクト名前を変えるPos XPos YWidth・HeightIdle Bounceの Frames(Imageの Source Imageは1枚目)
Slimeそのまま4500300slime_walk_61・62・63
Fairyそのまま1500300fairy_walk_61・62・63
Jiangshiそのまま-1500300jiangshi_walk_61・62・63
ReserveViewMedusa-4500300medusa_walk_61・62・63

ロビーでは、LobbyMenuが仲間の絵を入れていました。タイトルにはLobbyMenuが無いので、Idle Bounceの Frames に、待機の3コマを直接入れておきます。4人とも右を向いて、地下へ向かっていくように並べます。

タイトルの文字を作ります。

  1. Canvasを右クリック>Create Empty で、名前を Logo にする。Hierarchyで、Stageのすぐ下に動かす
  2. Anchor Presets を上の真ん中にして、Pivot を X 0.5・Y 0(下の真ん中)、Pos X 0・Pos Y -330、Width 1600・Height 200 にする。Pivotを下にしておくと、つぶれるときに、床に着いたように下を基準にしてつぶれます
  3. Logoを右クリック>UI>Text – TextMeshPro で、名前を Shadow にする。Anchor Presets を Alt+右下(全体に広げる)にして、Left 8・Right -8・Top 8・Bottom -8(右下に8ずらす)。Textに「まほう図書館の地下」、Font Asset は NotoSansJP SDF、Font Size 130、Bold、真ん中ぞろえ、Color は黒に近い紺(R 26・G 20・B 51・A 217)
  4. Shadowを Ctrl+D で複製して、名前を Text にする。Left・Right・Top・Bottom を0、Color を薄い黄色(R 255・G 237・B 153)にする

影の文字を先に置いて、同じ文字を少し右下にずらして後ろに重ねると、明るい背景の上でも読みやすくなります。

ボタンのウインドを作ります。

  1. LobbyWindow の名前を TitleWindow にする。Anchor Presets を下の真ん中にして、Pivot X 0.5・Y 0、Pos X 0・Pos Y 70、Width 308・Height 188 にする
  2. Grid Layout Groupの Cell Size を X 260・Y 64、Constraint Count を 1 にする(1列に並べる)
  3. 中の ShopButton・EquipButton・PartyButton・SaveButton を消す
  4. DungeonButton の名前を NewGameButton、中の文字を「はじめから」にする。RestButton の名前を ContinueButton、中の文字を「つづきから」にする

TitleWindowには、ロビーで使っていた Canvas Group が、そのまま付いています。これを、タイトルの文字が落ちてくるまで、ボタンを隠しておくのに使います。

TitleMenu

TitleのInspector。Title Menu (Script) に、Logo・Menu Group(TitleWindow)・New Game Button・Continue Button・Scene Fader(SceneFade)、Drop Seconds 0.5、Squash Seconds 0.3
TitleMenuの設定

Scriptsフォルダに、TitleMenuを作ります。

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

// タイトル:はじめから・つづきから。セーブが無ければ、つづきからは押せない(灰色)
public class TitleMenu : MonoBehaviour
{
    [SerializeField] private RectTransform logo;    // タイトルの文字(上から落ちてきて、ぽよんと弾む)
    [SerializeField] private CanvasGroup menuGroup; // ボタンのウインド(文字が落ちてから出す)
    [SerializeField] private Button newGameButton;  // はじめから
    [SerializeField] private Button continueButton; // つづきから
    [SerializeField] private SceneFader sceneFader;
    [SerializeField] private float dropSeconds = 0.5f;   // 落ちてくる時間
    [SerializeField] private float squashSeconds = 0.3f; // つぶれて戻る時間

    private bool isBusy; // ロビーへ切り替えている間は、ボタンを受け付けない

    private async void Start()
    {
        bool hasSave = SaveSystem.HasSave;
        continueButton.interactable = hasSave;
        newGameButton.onClick.AddListener(OnNewGameClicked);
        continueButton.onClick.AddListener(OnContinueClicked);

        menuGroup.alpha = 0f;
        menuGroup.interactable = false;
        try
        {
            await DropLogoAsync();
            await ShowMenuAsync();
        }
        catch (System.OperationCanceledException)
        {
            return;
        }
        // セーブがあれば「つづきから」、無ければ「はじめから」を選んでおく
        Button firstButton = hasSave ? continueButton : newGameButton;
        EventSystem.current.SetSelectedGameObject(firstButton.gameObject);
    }

    private void OnNewGameClicked()
    {
        StartGame(false);
    }

    private void OnContinueClicked()
    {
        StartGame(true);
    }

    // ロビーへ。loadsSave が true なら、セーブの続きから
    private async void StartGame(bool loadsSave)
    {
        if (isBusy)
        {
            return;
        }
        isBusy = true;
        GameSession.LoadsSave = loadsSave;
        try
        {
            await sceneFader.LoadSceneAsync("Lobby");
        }
        catch (System.OperationCanceledException)
        {
            // シーンが切り替わったら、そこで終わり
        }
    }

    // タイトルの文字が、上から落ちてきて、着地でつぶれてから戻る
    private async Awaitable DropLogoAsync()
    {
        Vector2 to = logo.anchoredPosition;
        Vector2 from = to + new Vector2(0f, 600f);
        float time = 0f;
        while (time < dropSeconds)
        {
            time += Time.deltaTime;
            float rate = Mathf.Clamp01(time / dropSeconds);
            logo.anchoredPosition = Vector2.Lerp(from, to, rate * rate); // だんだん速く落ちる
            await Awaitable.NextFrameAsync(destroyCancellationToken);
        }

        time = 0f;
        while (time < squashSeconds)
        {
            time += Time.deltaTime;
            // Sin で 0 → 1 → 0。いちばんつぶれたときに、横 1.15 倍・縦 0.8 倍
            float wave = Mathf.Sin(Mathf.Clamp01(time / squashSeconds) * Mathf.PI);
            logo.localScale = new Vector3(1f + 0.15f * wave, 1f - 0.2f * wave, 1f);
            await Awaitable.NextFrameAsync(destroyCancellationToken);
        }
        logo.localScale = Vector3.one;
    }

    // ボタンのウインドを、ふわっと出す
    private async Awaitable ShowMenuAsync()
    {
        const float seconds = 0.3f;
        float time = 0f;
        while (time < seconds)
        {
            time += Time.deltaTime;
            menuGroup.alpha = Mathf.Clamp01(time / seconds);
            await Awaitable.NextFrameAsync(destroyCancellationToken);
        }
        menuGroup.alpha = 1f;
        menuGroup.interactable = true;
    }
}
  • セーブのファイルが無ければ、「つづきから」のinteractableを外します。押せないボタンは灰色になり、キーボードやゲームパッドで動かしても、選ばれなくなります
  • 落ちてくるところは、rate * rate(2乗)にして、だんだん速くしています。そのままrateを使うと、同じ速さでスーッと下りてくるだけで、落ちた感じが出ません
  • 着地したら、横に広く・縦に低くつぶれてから戻ります。#22の ふくらむ と同じく、Sinで 0 → 1 → 0 と変えています
  • 「はじめから」でも「つづきから」でも、ロビーを読み込むのは同じです。違うのはGameSession.LoadsSaveだけで、ロビーのGameSessionがAwakeで見て、セーブを読むかどうかを決めます

Canvasの外に、Create Emptyで Title を作り、TitleMenuを付けます。Logo にLogo、Menu Group にTitleWindow、New Game Button にNewGameButton、Continue Button にContinueButton、Scene Fader にSceneFadeをドラッグします。

GameSessionはロビーに置いたままです。Lobbyシーンから再生すると、今までどおり最初の値で始まるので、ロビーやバトルを確かめるときは、そのままLobbyから再生できます。

最初に開くシーンをTitleにする

Build ProfilesのScene List。0がTurnBasedRpg/Scenes/Title、1がTurnBasedRpg/Scenes/Lobby、2がTurnBasedRpg/Scenes/Battle
Titleを0番に

ゲームを起動したときは、Scene Listの0番のシーンから始まります(8-3 スクリプトでシーンを切り替えよう)。

  • File>Build Profiles を開き、左上の Scene List を選ぶ
  • Open Scene List の上の一覧に、ProjectのTitleをドラッグして入れる
  • Titleをドラッグして、一番上(0番)に動かす
スライム

起動したら、まずタイトルですね!

動かして確認!

ロビーで「セーブ」を選ぶと、3人が本を開いて「ぼうけんの きろくを かきこんだ!」。続けてタイトル画面から「つづきから」を選ぶと、Lv10のスライム・480ゴールド・61/64のキョンシーのロビーに戻る
セーブして、つづきから
  • Titleシーンを開いて再生する。初めてなら「つづきから」は灰色で、「はじめから」が選ばれている
  • 「はじめから」で、Lv1の3人と50ゴールドのロビーから始まる
  • 地下へ行ってレベルを上げたり、売店で買い物をしたりしてから、ロビーの セーブ を押す。3人が本を開いて「ぼうけんの きろくを かきこんだ!」と出る
  • 再生を止めて、もう一度Titleから再生する。今度は「つづきから」が選ばれている
  • 「つづきから」で、セーブしたときのレベル・HP・装備・持ち物・お金のロビーから始まる。地下へ行くと、クリアした階の、1つ下まで選べる
  • 最初から確かめ直したいときは、Consoleに出た場所のsave.jsonを消す

#25の動作確認

  • ロビーのセーブで、仲間・持ち物・装備のふくろ・お金・クリアした階が、save.jsonに書かれる
  • アセットは、名前でJSONに入っている
  • タイトルの文字が落ちてきて、弾んでから、ボタンが出る
  • セーブが無いときは、「つづきから」が灰色で押せない
  • 「つづきから」で、セーブしたときの続きから始まる

あとがき

今回は、JSONでセーブ・ロードして、タイトル画面から続きを遊べるようにしました。アセットをそのまま保存せずに、名前で残すところがポイントです。名前で残しておけば、あとで数字を直しても(#24のように敵を強くしても)、前のセーブがそのまま読めます。次回は、ボタンや攻撃に効果音を付けて、場面ごとにBGMを切りかえたいと思います!

スライム

タイトルの文字、ぽよんってなるのが、わたしみたいで好きです・・・!

フェアリー

ちゃんとセーブしてから、ボスに挑むのよ!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次