「 👆 Unity でセーブデータを作る方法を調べようぜ?」
📖 JsonUtility
📖 EditorJsonUtility.ToJson
「 👆 ユーティリティーが2種類あるらしい。使ってみようぜ?」
「 👆 セーブファイルを 100個ぐらい分けたいとき どうするんだぜ?」
「 1つのJSONファイルに すべて詰め込むんじゃないか?」
「 Webブラウザーの上で実行するアプリだったら、どこへ保存される?」
📖 UnityTechnologies/UniteNow20-Persistent-Data
「 ソース読んだが 使い方 分からん。
どうやって クロス・プラットフォーム のセーブデータを作れる?
Windows を想定して ファイルパス書いていいのか?」
📖 PlayerPrefsと同様な使い勝手で独自クラスもセーブできる機能実装【Unity】【セーブ】【Json】
「 👆 とりあえず マネージャー・クラスを作る 鉄板のやり方で サンプルを書き直そう」
「 👆 OnSave
という Publicメソッドを作って」
「 👆 Save
ボタンをクリックしたら、 SaveDataManager
クラスの OnSave
メソッドを呼び出すところまで
作るのは 鉄板だぜ」
「 👆 上のリンク先の、他人の記事を読めだぜ。
マウスで動かせるオブジェクトを画面上に置こうぜ?」
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputManager : MonoBehaviour
{
/// <summary>
/// レイを描く距離
/// </summary>
float DebugDrawRayDistance = 15.0f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// マウス右ボタン 押しっぱなし時
if (Input.GetMouseButton(0))
{
// レイ(見えない光線のようなもの)を飛ばす
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out var hit))
{
// レイが当たったオブジェクト
string goName = hit.collider.gameObject.name;
Debug.Log("右クリック:" + goName);
// そのオブジェクトの中心位置を、クリックした場所へ移動(カクカクした動き)
GameObject.Find(goName).transform.position = new Vector3(hit.point.x, hit.point.y, 0);
}
else
{
Debug.Log("右クリック 外れ");
}
// レイを描く
Debug.DrawRay(ray.origin, ray.direction * DebugDrawRayDistance, Color.green, 5, false);
}
}
}
「 Update()
じゃなくて、 LateUpdate()
にすりゃいいんじゃないかだぜ?」
「 👆 セーブボタンだけあっても練習できない。ロードボタンも追加するぜ」
SaveDataManager.cs
:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SaveDataManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
// - イベントハンドラ
public void OnSave()
{
Debug.Log("セーブします");
var goCube = GameObject.Find("Cube");
PlayerPrefs.SetFloat("x", goCube.transform.position.x);
PlayerPrefs.SetFloat("y", goCube.transform.position.y);
PlayerPrefs.SetFloat("z", goCube.transform.position.z);
}
public void OnLoad()
{
Debug.Log("ロードします");
var goCube = GameObject.Find("Cube");
var x = PlayerPrefs.GetFloat("x");
var y = PlayerPrefs.GetFloat("y");
var z = PlayerPrefs.GetFloat("z");
goCube.transform.position = new Vector3(x, y, z);
}
}
📺 開発中画面
📅 2023-03-12 sun 00:29
「 バカでかい容量のセーブをしたいとき こんなシンプルな構造で 対応しきれるのかだぜ?
クラスを シリアライズ/デシリアライズ して 投げ込めないの?」
「 String
型のセッターに JSON を投げ込めだぜ」
📖 【Unity uGUI】ドロップダウン(Dropdown)を使用してオプションを選択する方法
「 👆 何かをやろうとしても、はて、と分からんことばっかりで つらい。調べる」
📅 2023-03-12 sun 15:14
SaveDataManager.cs
:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class SaveDataManager : MonoBehaviour
{
TMP_Dropdown dropdown;
// Start is called before the first frame update
void Start()
{
dropdown = GameObject.Find("Save Slot Dropdown").GetComponent<TMP_Dropdown>();
// ドロップダウンリストのラベル
dropdown.options[0].text = "Save Data 1";
dropdown.options[1].text = "Save Data 2";
dropdown.options[2].text = "Save Data 3";
// コンボボックスのラベル
//
// - 最初に選択されている項目に合わせないと挙動がおかしくなる
dropdown.captionText.text = dropdown.options[0].text;
}
// Update is called once per frame
void Update()
{
}
// - フィールド
string[] gameObjectNamesToSave = new string[]
{
"Red Cube",
"Green Cube",
"Blue Cube",
};
// - イベントハンドラ
public void OnSave()
{
Debug.Log($"{(dropdown.value)}+1番へ、セーブしたい");
foreach (var gameObjectName in gameObjectNamesToSave)
{
var gameObject = GameObject.Find(gameObjectName);
// ゲームオブジェクトの名前には、いろんな文字が使えるので、区切りなるような文字がない。とりあえずドット区切りにする
var nameSpace = $"Slot{dropdown.value}.{gameObjectName}";
PlayerPrefs.SetFloat($"{nameSpace}.x", gameObject.transform.position.x);
PlayerPrefs.SetFloat($"{nameSpace}.y", gameObject.transform.position.y);
PlayerPrefs.SetFloat($"{nameSpace}.z", gameObject.transform.position.z);
}
}
public void OnLoad()
{
Debug.Log($"{(dropdown.value)}+1番から、ロードしたい");
foreach (var gameObjectName in gameObjectNamesToSave)
{
var gameObject = GameObject.Find(gameObjectName);
// ゲームオブジェクトの名前には、いろんな文字が使えるので、区切りなるような文字がない。とりあえずドット区切りにする
var nameSpace = $"Slot{dropdown.value}.{gameObjectName}";
gameObject.transform.position = new Vector3(
PlayerPrefs.GetFloat($"{nameSpace}.x"),
PlayerPrefs.GetFloat($"{nameSpace}.y"),
PlayerPrefs.GetFloat($"{nameSpace}.z"));
}
}
}
「 👆 階層構造はこうなるぜ。
関係あるところを見ていこう」
Assets.Scripts.Models.SaveData.GameObject.cs
:
namespace Assets.Scripts.Models.SaveData
{
using UnityEngine;
/// <summary>
/// UnityのGameObjectをラッピング
/// </summary>
[System.Serializable]
public class GameObject
{
// -^ その他
/// <summary>
/// 生成
/// </summary>
/// <param name="gameObject"></param>
/// <returns></returns>
internal static GameObject FromGameObject(UnityEngine.GameObject gameObject)
{
return new GameObject
{
name = gameObject.name,
x = gameObject.transform.position.x,
y = gameObject.transform.position.y,
z = gameObject.transform.position.z
};
}
// - フィールド
public string name;
public float x;
public float y;
public float z;
// - メソッド
internal Vector3 ToPosition()
{
return new Vector3(this.x, this.y, this.z);
}
}
}
「 👆 GameObject
と同名で、保存したいところだけ作ったクラスを用意するぜ。
クラス名の上の行に [System.Serializable]
が付いているのを忘れるなだぜ」
Assets.Scripts.Models.SaveData.Init.cs
:
namespace Assets.Scripts.Models.SaveData
{
using System.Collections.Generic;
using ModelOfSaveData = Assets.Scripts.Models.SaveData;
/// <summary>
/// セーブ・データ
/// </summary>
public class Init
{
// - その他
internal Init()
{
this.gameObjects = new List<ModelOfSaveData.GameObject>();
}
// - フィールド
// JsonUtilクラスは、Dictionary型には対応していない
public List<ModelOfSaveData.GameObject> gameObjects;
// - メソッド
/// <summary>
/// ゲームオブジェクトの追加
/// </summary>
/// <param name="item"></param>
public void AddGameObject(ModelOfSaveData.GameObject item)
{
this.gameObjects.Add(item);
}
}
}
「 👆 SaveData.Init
クラスが、セーブデータ・クラス階層構造のトップ・レベルのクラスだぜ。
セーブスロット1個分のデータが全部入ってると思えだぜ」
Assets.Scripts.SaveDataManager.cs
:
using TMPro;
using UnityEngine;
using ModelOfSaveData = Assets.Scripts.Models.SaveData;
public class SaveDataManager : MonoBehaviour
{
TMP_Dropdown dropdown;
// Start is called before the first frame update
void Start()
{
dropdown = GameObject.Find("Save Slot Dropdown").GetComponent<TMP_Dropdown>();
// ドロップダウンリストのラベル
dropdown.options[0].text = "Save Data 1";
dropdown.options[1].text = "Save Data 2";
dropdown.options[2].text = "Save Data 3";
// コンボボックスのラベル
//
// - 最初に選択されている項目に合わせないと挙動がおかしくなる
dropdown.captionText.text = dropdown.options[0].text;
}
// Update is called once per frame
void Update()
{
}
// - フィールド
string[] gameObjectNamesToSave = new string[]
{
"Red Cube",
"Green Cube",
"Blue Cube",
};
// - イベントハンドラ
public void OnSave()
{
// セーブデータ・モデルの作成
var saveDataModel = new ModelOfSaveData.Init();
// 記憶したいゲームオブジェクトを格納
foreach (var gameObjectName in gameObjectNamesToSave)
{
var gameObject = GameObject.Find(gameObjectName);
var gameObject2 = ModelOfSaveData.GameObject.FromGameObject(gameObject);
saveDataModel.AddGameObject(gameObject2);
}
// JSON文字列化(シリアライズ)
var jsonText = JsonUtility.ToJson(saveDataModel);
// 保存
Debug.Log($"{(dropdown.value)}+1番へ、セーブしたい。 Json:{jsonText}");
PlayerPrefs.SetString($"Slot{dropdown.value}", jsonText);
}
public void OnLoad()
{
// 読取(JSONテキスト取得)
var jsonText = PlayerPrefs.GetString($"Slot{dropdown.value}");
Debug.Log($"{(dropdown.value)}+1番から、ロードしたい。 Json:{jsonText}");
// セーブデータ・モデルへ復元
var saveDataModel2 = JsonUtility.FromJson<ModelOfSaveData.Init>(jsonText);
if (saveDataModel2==null)
{
// 復元できません
Debug.Log($"{(dropdown.value)}+1番 ロードできませんでした。 Json:{jsonText}");
return;
}
// 記憶されているゲームオブジェクトを取出し
foreach (var gameObject2 in saveDataModel2.gameObjects)
{
var gameObject = GameObject.Find(gameObject2.name);
gameObject.transform.position = gameObject2.ToPosition();
}
}
}
「 WebGL 形式で出力して、ブラウザで動かしても セーブ/ロード できんの?」
「 👆 できたり、できなかったりするな、ちょっと 調べるか」
「 気になったのは、 デバッグ・ログ そのまんま出てくるのか 整えないと カッコ悪いな」
📅 2023-03-15 wed 22:38
「 👆 アップロード後に ゲーム画面上の×ボタンを押して ブラウザの戻るボタンを押したら
画面が真っ白になった!」
「 一度 間違った操作を行うと 続けて 間違った操作を行ってしまう。
わらう」
📅 2023-03-15 wed 23:41
「 👆 1時間放置したら アクセスできるようになった。アップロードした。
もう終わり!」
<おわり>
第1回 | Unity練習 セーブデータを作ろうぜ(^~^)? |
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント