バーチャル3Dクリエイター神部まゆみです(*^-^*)
この記事はunityでセーブデータを作ってみたメモです。
前回の記事はこちら。
動作確認した最新バージョンは Unity 6000.0.32です。




やりたいこと セーブ床に乗ったらプレイヤーのTransform位置を保存し、タイトル画面でContinueを選んだら続きから始められるようにする
前回タイトル画面のメニューを作ったけど、Continueが実装できていないので実装したいと思った。
そのためにはプレイヤーのデータを保存する機能を実装しないといけない。
とりあえずはプレイヤーのtransform位置を保存するだけのシンプルなやつを作ってみたいですね。
優秀なChatGPT先輩に聞いてみる
とりあえずChatGPTに聞いてみますが、実際の手順は下の方にまとめてあるので、やり方を知りたい方はそちらを見てください。
以下の文章はChatGPTに聞いたそのままを貼り付けた感じになります。
どの言語にもファイルの読み書き命令はあるけど、C#だとどうなるんだろう。
セーブ関連のアセットはアセットストアで見た気がするけど、とりあえず自力で作ってみたいので優秀なChatGPT先輩に聞いてみます。




unityでセーブデータを作るにはどうしたらいいのでしょうか?
とりあえずキャラクターのワールドTransformだけ記録するシンプルなもので動作確認してみたいです。


UnityでキャラクターのワールドTransformをセーブ・ロードする基本的な方法として、PlayerPrefs
やJSON
を使うことができます。
以下に、シンプルなセーブシステムの実装方法を説明します。
げ、前回作ったTitleScreen.csも含めて四つもスクリプトが必要みたいですΣ(゚Д゚)
セーブ処理の実装 SaveSystem.cs
これは他のスクリプトから参照されるので、どのオブジェクトにもアタッチする必要はありません。
using UnityEngine;
public class SaveSystem : MonoBehaviour
{
// セーブ用のキー
private const string PositionXKey = "PositionX";
private const string PositionYKey = "PositionY";
private const string PositionZKey = "PositionZ";
public static void SavePlayerPosition(Transform playerTransform)
{
// プレイヤーの位置を保存
PlayerPrefs.SetFloat(PositionXKey, playerTransform.position.x);
PlayerPrefs.SetFloat(PositionYKey, playerTransform.position.y);
PlayerPrefs.SetFloat(PositionZKey, playerTransform.position.z);
PlayerPrefs.Save(); // PlayerPrefsのデータを保存
}
public static Vector3 LoadPlayerPosition()
{
// 保存された位置を読み込む(デフォルト値はVector3.zero)
float x = PlayerPrefs.GetFloat(PositionXKey, 0f);
float y = PlayerPrefs.GetFloat(PositionYKey, 0f);
float z = PlayerPrefs.GetFloat(PositionZKey, 0f);
return new Vector3(x, y, z);
}
public static bool HasSavedData()
{
// データが保存されているかを確認
return PlayerPrefs.HasKey(PositionXKey);
}
}
セーブトリガーの設定 SaveTrigger.cs
using UnityEngine;
public class SaveTrigger : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
// プレイヤーがトリガーに触れたらセーブ
SaveSystem.SavePlayerPosition(other.transform);
Debug.Log("Game Saved!");
}
}
}
このスクリプトをセーブ床のオブジェクトにアタッチし、Collider
を「Is Trigger」に設定します。
プレイヤーのオブジェクトに「Player」タグを設定します。
ロード処理の実装 PlayerController.cs
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerController : MonoBehaviour
{
private void Start()
{
if (SaveSystem.HasSavedData())
{
// セーブデータがある場合、プレイヤーの位置をロード
Vector3 savedPosition = SaveSystem.LoadPlayerPosition();
transform.position = savedPosition;
Debug.Log("Loaded Player Position: " + savedPosition);
}
}
}
このスクリプトをプレイヤーのオブジェクトにアタッチします。
ゲーム開始時にセーブデータがあれば、その位置にプレイヤーを移動させます。
Continueボタンの実装 TitleScreen.cs (前回の改良版)
using UnityEngine;
using UnityEngine.SceneManagement;
public class TitleScreen : MonoBehaviour
{
public void OnNewGame()
{
// New Game処理
SceneManager.LoadScene("GameScene");
}
public void OnContinue()
{
if (SaveSystem.HasSavedData())
{
// セーブデータがあればゲームシーンに移行
SceneManager.LoadScene("GameScene");
}
else
{
Debug.Log("No saved data found.");
}
}
public void OnOption()
{
// オプション処理(未実装)
Debug.Log("Option selected");
}
}
タイトル画面のUIボタンにOnNewGame、OnContinue、OnOptionメソッドを紐づけます。
動作確認
- プレイヤーがセーブ床に触れると位置が保存されます。
- タイトル画面の「Continue」を選ぶと、保存された位置からゲームが再開されます。
これで、シンプルなセーブ・ロードシステムが実装できました。さらに複雑なセーブデータを扱う場合、JSON
やファイルシステムを使ったセーブ方法を検討することができます。
んー複雑そうだけどChatGPT先輩に聞きながらやればいけるかな…。
PlayerPrefsとかいう謎の技術を使えば簡単なデータを保持できるようです
Unityでセーブデータを実装するには、PlayerPrefsとかいうやつを使えば手軽にできるっぽいです。

PlayerPrefsは、ゲームセッション間のプレイヤー設定を保存するクラスです。文字列、浮動小数点数、および整数値をユーザーのプラットフォームレジストリに格納できます。
Unity は PlayerPrefs を暗号化せずにローカルレジストリに保存します。機密データの保存にPlayerPrefsデータを使用しないでください。




PlayerPrefsとはなんですか


PlayerPrefsは、Unityで簡単にデータを保存・読み込みするための仕組みです。主に、小規模なデータ(数値や文字列など)の保存に適しており、プレイヤーの設定や進行状況を保存するために使われます。
特徴
- 永続的にデータを保存:
PlayerPrefs
は、デバイスにデータを保存し、ゲームを再起動した後もそのデータを読み込むことができます。 - 簡単なデータ保存: 数値や文字列といった簡単なデータの保存に向いていますが、複雑なデータ(大規模なオブジェクトやリスト、配列など)の保存には向いていません。
- プラットフォーム依存: 保存先は、使用するプラットフォームによって異なります(PCではレジストリ、モバイルではファイル)。
保存できるデータの型
- int(整数)
- float(浮動小数点数)
- string(文字列)
使い方
データの保存
// 整数を保存
PlayerPrefs.SetInt("HighScore", 100);
// 浮動小数点数を保存
PlayerPrefs.SetFloat("Volume", 0.5f);
// 文字列を保存
PlayerPrefs.SetString("PlayerName", "Alice");
// 保存処理の確定
PlayerPrefs.Save();
データの読み込み
// 整数の読み込み(デフォルト値0)
int highScore = PlayerPrefs.GetInt("HighScore", 0);
// 浮動小数点数の読み込み(デフォルト値1.0f)
float volume = PlayerPrefs.GetFloat("Volume", 1.0f);
// 文字列の読み込み(デフォルト値 "Unknown")
string playerName = PlayerPrefs.GetString("PlayerName", "Unknown");
データの削除
// "HighScore"に対応するデータを削除
PlayerPrefs.DeleteKey("HighScore");
// 全てのデータを削除
PlayerPrefs.DeleteAll();
使うタイミング
PlayerPrefs
は、例えば次のようなシチュエーションで便利です。
- ゲームの設定: 音量やグラフィック設定などを保存する場合。
- シンプルなセーブデータ: スコアやステージ進行など、シンプルなデータを保存する場合。
ただし、PlayerPrefs
は複雑なゲームデータ(アイテムリストや複数のプレイヤーデータなど)には向いていません。大規模なデータは、ファイルシステムやJSONなど、別の方法で保存する方が適しています。
注意点
- 保存容量が制限される場合がある: 特にモバイルプラットフォームでは、
PlayerPrefs
の保存領域に制限がある場合があり、大量のデータを保存するのには向いていません。 - 暗号化されない: 保存データは簡単にアクセスできるため、データの保護が必要な場合は別途暗号化処理を検討する必要があります。
PlayerPrefsを使えばゲームを終了して再起動してもデータが保持されるみたい




PlayerPrefsでは、ゲームを終了した後もデータが保存されるのですか?


はい、PlayerPrefsを使用して保存されたデータは、ゲームを終了しても保存され続けます。次回ゲームを起動したときに、保存されたデータを読み込むことができます。つまり、PlayerPrefsは永続的なデータストレージとして機能します。
具体的には、PlayerPrefs.SetInt()やPlayerPrefs.SetString()などでデータを保存し、PlayerPrefs.Save()を呼び出すと、データがディスクに書き込まれます。これにより、ゲームが再起動された後でもデータを読み込むことができるようになります。
簡単なデータを保存するだけなら十分使えそうですね。
実際やってみる
↑のChatGPTの言う通りそのままだと動かなかったので、試行錯誤しつつやったらできました。
以下の手順が正しいです。
①SaveSystem.cs を用意する
これは他のスクリプトから参照されるため、どのオブジェクトにもアタッチする必要はありません。
SaveSystem.cs という名前でファイルを作ってコピペし、アセットフォルダに置いておくだけでOK。
using UnityEngine;
public class SaveSystem : MonoBehaviour
{
// セーブ用のキー
private const string PositionXKey = "PositionX";
private const string PositionYKey = "PositionY";
private const string PositionZKey = "PositionZ";
public static void SavePlayerPosition(Transform playerTransform)
{
// プレイヤーの位置を保存
PlayerPrefs.SetFloat(PositionXKey, playerTransform.position.x);
PlayerPrefs.SetFloat(PositionYKey, playerTransform.position.y);
PlayerPrefs.SetFloat(PositionZKey, playerTransform.position.z);
PlayerPrefs.Save(); // PlayerPrefsのデータを保存
}
public static Vector3 LoadPlayerPosition()
{
// 保存された位置を読み込む(デフォルト値はVector3.zero)
float x = PlayerPrefs.GetFloat(PositionXKey, 0f);
float y = PlayerPrefs.GetFloat(PositionYKey, 0f);
float z = PlayerPrefs.GetFloat(PositionZKey, 0f);
return new Vector3(x, y, z);
}
public static bool HasSavedData()
{
// データが保存されているかを確認
return PlayerPrefs.HasKey(PositionXKey);
}
}
②タイトルシーンの TitleScreen.cs を書き換える
前回作ったTitleScreen.csがあるけど、あれを改良します。
↓NewGameが押されたら保存してあるプレイヤーのTransform位置をリセットし、Continueが押されたら保存してあるTransform位置にプレイヤーを移動させます。
これはキャンバスの子のMenuContainerにアタッチしてあります。
using UnityEngine;
using UnityEngine.SceneManagement;
public class TitleScreen : MonoBehaviour
{
// NewGameボタンが押された時の処理
public void OnNewGame()
{
// セーブデータをリセット
PlayerPrefs.DeleteKey("PositionX");
PlayerPrefs.DeleteKey("PositionY");
PlayerPrefs.DeleteKey("PositionZ");
// 初期シーン(例えば、GameScene)をロード
SceneManager.LoadScene("simpletown-game");
}
// Continueボタンが押された時の処理
public void OnContinue()
{
// 続きからスタート(セーブされた位置に移動)
SceneManager.LoadScene("simpletown-game");
}
// Optionボタンが押された時の処理(必要に応じて追加)
public void OnOption()
{
// オプション画面の表示など
Debug.Log("Option screen opened.");
}
}
オプションはまだ作っていません。
③ゲームシーンにセーブ床を設置し、 SaveTrigger.cs をアタッチする
SaveTrigger.cs は↓これね。
using UnityEngine;
public class SaveTrigger : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
// プレイヤーがトリガーに触れたらセーブ
SaveSystem.SavePlayerPosition(other.transform);
Debug.Log("Game Saved!");
}
}
}
プレイヤーにPlayerタグがついていないとダメなので、Playerタグを設定しておきます。


とりあえずゲームシーンにキューブを設置して小さくし、ちゃんとコライダーに触れるようにコライダーのY軸をでかくして当たり判定を広くしておきます。マテリアルは適当にわかりやすい色を設定する。
これをセーブ床として使うので、これにスクリプトをアタッチすればOK。
↓あとコライダーの「トリガーにする」にチェックを入れる。これでコライダーに触れた時にOnTriggerEnterに書いたコードが実行されます。


④ゲームシーンにいるプレイヤーに PlayerController.cs をアタッチする
PlayerController.csは↓これね。セーブしたプレイヤーのTransform位置を代入してワープさせる。
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerController : MonoBehaviour
{
private void Start()
{
if (SaveSystem.HasSavedData())
{
// セーブデータがある場合、プレイヤーの位置をロード
Vector3 savedPosition = SaveSystem.LoadPlayerPosition();
transform.position = savedPosition;
Debug.Log("Loaded Player Position: " + savedPosition);
}
}
}
特に設定とかはないので、プレイヤーにアタッチだけしておけばOK。


しかしPlayerControllerって、名前がプレイヤーを動かすスクリプトみたいで紛らわしいな(~_~;)
LoadPlayerPositionとかに名前を変えておいた方が良いかもしれない。
まぁとりあえずはこれでいきます。
⑤動作確認のため、ゲームシーンにExit床(タイトルに戻る)も設置しておく
セーブ床に乗ってプレイヤーの位置が保存されても、タイトル画面に戻らないとちゃんとセーブされたか確認できません。
なのでタイトル画面に戻るExit床も設置しておきます。
まぁセーブ床をコピペして、スクリプトを削除して入れ替えればOK。
タイトル画面に戻るスクリプトは↓これね。
SceneManager.LoadScene(“title”); のところはタイトルのシーン名が来るので各自書き換えてください。
using UnityEngine;
using UnityEngine.SceneManagement;
public class ExitTrigger : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
// シーン遷移前にEventSystemやカーソルの状態をリセット
ResetScene();
// タイトル画面に戻る
SceneManager.LoadScene("title");
}
}
private void ResetScene()
{
// カーソルを強制的に表示し、ロックを解除
Cursor.lockState = CursorLockMode.None; // カーソルのロックを解除
Cursor.visible = true; // カーソルを表示
Debug.Log("Cursor is now visible and unlocked.");
}
}
タイトル画面に戻ってもカーソルロックが解除されなかったのでリセットするようにした
どの3Dゲームもそうだと思うけど、ゲーム画面をクリックするとカーソルがロックされてしまい、カーソル移動が視点変更になってしまう。
↓このゲーム画面をクリックするとカーソルが消えるやつ。ESCキーを押すとカーソル表示されるけど、ボタンは押せなかった。
タイトル画面に戻った時にカーソル設定が自動で戻らないみたいなので、強制的にカーソルを表示させるようにしています。
↓ResetScene()の部分ね。
private void ResetScene()
{
// カーソルを強制的に表示し、ロックを解除
Cursor.lockState = CursorLockMode.None; // カーソルのロックを解除
Cursor.visible = true; // カーソルを表示
Debug.Log("Cursor is now visible and unlocked.");
}
これやらないとタイトル画面のボタンが押せなかった。
まぁ実際にゲームを作っていく場合はゲームパッドで設定していくと思うから、そっちの設定をしておいたほうがいいけど、今回はこれで。
これで無事動いた!
ちゃんとContinueを押したときはセーブ地点に出現し、NewGameを押したときは初期位置に戻ってますね。
複雑でたくさんのデータを保存する場合はJSONファイルとかに保存したほうがいいみたいだけど、簡単なデータならこれでいけるっぽいですね。
つづく?
案外簡単にできて良かった。
短い簡単なゲームの場合はこれだけでも行けるかもしれない。
まぁせっかくだからオプション画面を作ったり作り込んで行って見ようかな。
ぼちぼちいじってみます(*^-^*)