1, Scriptフォルダを作成し、SavingSystemとSavingWrapperスクリプトを作成する
2, 各スクリプトに下記コードを書く
SavingWrapper
using UnityEngine;
namespace RPG.Saving
{
public class SavingWrapper : MonoBehaviour
{
const string defaultSaveFile = "save";
private void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
GetComponent<SavingSystem>().Save(defaultSaveFile);
}
if (Input.GetKeyDown(KeyCode.L))
{
GetComponent<SavingSystem>().Load(defaultSaveFile);
}
}
}
}
SavingSystem
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
print("Saving to " + saveFile);
}
public void Load(string saveFile)
{
print("Load to " + saveFile);
}
}
}
3, PersistentObjectsにSavingSystemオブジェクトを作成し、各スクリプトをコンポーネントする
セーブファイルを作成できるようにする
1, 下記スクリプトを追加する
SavingSystem
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
print("Saving to " + GetPathFromSaveFile(saveFile)); //変更
}
public void Load(string saveFile)
{
print("Load to " + GetPathFromSaveFile(saveFile)); //変更
}
//追加
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
セーブとロードを実行する
1, 下記スクリプトを記述する
SavingSystem
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
Transform playertransform = GetPlayerTransform();
byte[] buffer = SerializeVector(playertransform.position);
stream.Write(buffer, 0, buffer.Length);
}
}
public void Load(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Loading from " + path);
using (FileStream stream = File.Open(path, FileMode.Open))
{
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
Transform playertransform = GetPlayerTransform();
playertransform.position = DeserializeVector(buffer);
}
}
private Transform GetPlayerTransform()
{
return GameObject.FindWithTag("Player").transform;
}
private byte[] SerializeVector(Vector3 vector)
{
byte[] vectorBytes = new byte[3 * 4];
BitConverter.GetBytes(vector.x).CopyTo(vectorBytes, 0);
BitConverter.GetBytes(vector.y).CopyTo(vectorBytes, 4);
BitConverter.GetBytes(vector.z).CopyTo(vectorBytes, 8);
return vectorBytes;
}
private Vector3 DeserializeVector(byte[] buffer)
{
Vector3 result = new Vector3();
result.x = BitConverter.ToSingle(buffer, 0);
result.y = BitConverter.ToSingle(buffer, 4);
result.z = BitConverter.ToSingle(buffer, 8);
return result;
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
シリアライズのための BinaryFormatter
1, SerializableVector3.csを作成する
2, 各スクリプトに下記コードを記述する
SavingSystem
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
Transform playerTransform = GetPlayerTransform();
BinaryFormatter formatter = new BinaryFormatter();
SerializableVector3 position = new SerializableVector3(playerTransform.position);
formatter.Serialize(stream, position);
}
}
public void Load(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Loading from " + path);
using (FileStream stream = File.Open(path, FileMode.Open))
{
Transform playerTransform = GetPlayerTransform();
BinaryFormatter formatter = new BinaryFormatter();
SerializableVector3 position = (SerializableVector3)formatter.Deserialize(stream);
playerTransform.position = position.ToVector();
}
}
private Transform GetPlayerTransform()
{
return GameObject.FindWithTag("Player").transform;
}
private byte[] SerializeVector(Vector3 vector)
{
byte[] vectorBytes = new byte[3 * 4];
BitConverter.GetBytes(vector.x).CopyTo(vectorBytes, 0);
BitConverter.GetBytes(vector.y).CopyTo(vectorBytes, 4);
BitConverter.GetBytes(vector.z).CopyTo(vectorBytes, 8);
return vectorBytes;
}
private Vector3 DeserializeVector(byte[] buffer)
{
Vector3 result = new Vector3();
result.x = BitConverter.ToSingle(buffer, 0);
result.y = BitConverter.ToSingle(buffer, 4);
result.z = BitConverter.ToSingle(buffer, 8);
return result;
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
SerializableVector3
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Saving
{
[System.Serializable]
public class SerializableVector3
{
float x, y, z;
public SerializableVector3(Vector3 vector)
{
x = vector.x;
y = vector.y;
z = vector.z;
}
public Vector3 ToVector()
{
return new Vector3(x, y, z);
}
}
}
SaveableEntityの作成
2,各スクリプトに下記コードを記述する
SavingSystem
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, CaptureState());
}
}
public void Load(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Loading from " + path);
using (FileStream stream = File.Open(path, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
RestoreState(formatter.Deserialize(stream));
}
}
private object CaptureState()
{
Dictionary<string, object> state = new Dictionary<string, object>();
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
}
return state;
}
private void RestoreState(object state)
{
Dictionary<string, object> stateDict = (Dictionary<string, object>)state;
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
saveable.RestoreState(stateDict[saveable.GetUniqueIdentifier()]);
}
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
SaveableEntity
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Saving
{
public class SaveableEntity : MonoBehaviour
{
public string GetUniqueIdentifier()
{
return "";
}
public object CaptureState()
{
print("Capturing state for " + GetUniqueIdentifier());
return null;
}
public void RestoreState(object state)
{
print("Restoring state for " + GetUniqueIdentifier());
}
}
}
UUIDs生成
1, 下記スクリプトの追加
SaveableEntity
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RPG.Saving
{
[ExecuteAlways]
public class SaveableEntity : MonoBehaviour
{
[SerializeField] string uniqueIdentifier = "";
public string GetUniqueIdentifier()
{
return uniqueIdentifier;
}
public object CaptureState()
{
print("Capturing state for " + GetUniqueIdentifier());
return null;
}
public void RestoreState(object state)
{
print("Restoring state for " + GetUniqueIdentifier());
}
private void Update()
{
if (Application.IsPlaying(gameObject)) return;
print("Editing");
}
}
}
SerializedFieldsの編集
1, 下記スクリプトを追加
SaveableEntity
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RPG.Saving
{
[ExecuteAlways]
public class SaveableEntity : MonoBehaviour
{
[SerializeField] string uniqueIdentifier = "";
public string GetUniqueIdentifier()
{
return uniqueIdentifier;
}
public object CaptureState()
{
print("Capturing state for " + GetUniqueIdentifier());
return null;
}
public void RestoreState(object state)
{
print("Restoring state for " + GetUniqueIdentifier());
}
private void Update()
{
if (Application.IsPlaying(gameObject)) return;
if (string.IsNullOrEmpty(gameObject.scene.path)) return;
SerializedObject serializedObject = new SerializedObject(this);
SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier");
if (string.IsNullOrEmpty(property.stringValue))
{
property.stringValue = System.Guid.NewGuid().ToString();
serializedObject.ApplyModifiedProperties();
}
}
}
}
SaveableEntitiesでのシリアライズ
1, 下記のコードを追加
SaveableEntity
using System.Collections;
using System.Collections.Generic;
using RPG.Core; //追加
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Saving
{
[ExecuteAlways]
public class SaveableEntity : MonoBehaviour
{
[SerializeField] string uniqueIdentifier = "";
public string GetUniqueIdentifier()
{
return uniqueIdentifier;
}
public object CaptureState()
{
return new SerializableVector3(transform.position); //追加
}
public void RestoreState(object state)
{
SerializableVector3 position = (SerializableVector3)state; //追加
GetComponent<NavMeshAgent>().enabled = false; //追加
transform.position = position.ToVector(); //追加
GetComponent<NavMeshAgent>().enabled = true; //追加
GetComponent<ActionScheduler>().CancelCurrentAction(); //追加
}
private void Update()
{
if (Application.IsPlaying(gameObject)) return;
if (string.IsNullOrEmpty(gameObject.scene.path)) return;
SerializedObject serializedObject = new SerializedObject(this);
SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier");
if (string.IsNullOrEmpty(property.stringValue))
{
property.stringValue = System.Guid.NewGuid().ToString();
serializedObject.ApplyModifiedProperties();
}
}
}
}
複数シーン保存①
1, 下記コードを追加
SavingSystem
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
SaveFile(saveFile, CaptureState());
}
public void Load(string saveFile)
{
RestoreState(LoadFile(saveFile));
}
private Dictionary<string, object> LoadFile(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
print("Loading from " + path);
using (FileStream stream = File.Open(path, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
return (Dictionary<string, object>) formatter.Deserialize(stream);
}
}
private void SaveFile(string saveFile, object state)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, state);
}
}
private Dictionary<string, object> CaptureState()
{
Dictionary<string, object> state = new Dictionary<string, object>();
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
}
return state;
}
private void RestoreState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
saveable.RestoreState(state[saveable.GetUniqueIdentifier()]);
}
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
複数シーン保存②
1, 下記コードを追加する
SavingSystem
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
public void Save(string saveFile)
{
Dictionary<string, object> state = LoadFile(saveFile); //追加
CaptureState(state); //追加
SaveFile(saveFile, state); //変更
}
public void Load(string saveFile)
{
RestoreState(LoadFile(saveFile));
}
private Dictionary<string, object> LoadFile(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
//追加
if (!File.Exists(path))
{
return new Dictionary<string, object>();
}
using (FileStream stream = File.Open(path, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
return (Dictionary<string, object>)formatter.Deserialize(stream);
}
}
private void SaveFile(string saveFile, object state)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, state);
}
}
private void CaptureState(Dictionary<string, object> state) //変更
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
}
}
private void RestoreState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
string id = saveable.GetUniqueIdentifier(); //変更
//追加
if (state.ContainsKey(id))
{
saveable.RestoreState(state[id]);
}
}
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
シーン間のチェックポイント
1, 各スクリプトに各コードの記述
Portal
using System;
using System.Collections;
using RPG.Saving; //追加
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
namespace RPG.SceneManagement
{
public class Portal : MonoBehaviour
{
enum DestinationIdentifier
{
A, B, C, D, E
}
[SerializeField] int sceneToLoad = -1;
[SerializeField] Transform spawnPoint;
[SerializeField] DestinationIdentifier destination;
[SerializeField] float fadeOutTime = 1f;
[SerializeField] float fadeInTime = 2f;
[SerializeField] float fadeWaitTime = 0.5f;
//private void Start()
//{
// transform.parent = null;
// DontDestroyOnLoad(gameObject);
//}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
StartCoroutine(Transition());
}
}
private IEnumerator Transition()
{
if (sceneToLoad < 0)
{
Debug.LogError("Scene to load not set.");
yield break;
}
transform.parent = null;
DontDestroyOnLoad(gameObject);
Fader fader = FindObjectOfType<Fader>();
SavingWrapper savingWrapper = FindObjectOfType<SavingWrapper>(); //追加
yield return fader.FadeOut(fadeOutTime);
savingWrapper.Save(); //追加
yield return SceneManager.LoadSceneAsync(sceneToLoad);
savingWrapper.Load(); //追加
Portal otherPortal = GetOtherPortal();
UpdatePlayer(otherPortal);
yield return new WaitForSeconds(fadeWaitTime);
yield return fader.FadeIn(fadeInTime);
Destroy(gameObject);
}
private void UpdatePlayer(Portal otherPortal)
{
GameObject player = GameObject.FindWithTag("Player");
player.GetComponent<NavMeshAgent>().enabled = false; //追加
player.GetComponent<NavMeshAgent>().Warp(otherPortal.spawnPoint.position);
player.transform.rotation = otherPortal.spawnPoint.rotation;
player.GetComponent<NavMeshAgent>().enabled = true; //追加
}
private Portal GetOtherPortal()
{
foreach (Portal portal in FindObjectsOfType<Portal>())
{
if (portal == this) continue;
if (portal.destination != destination) continue;
return portal;
}
return null;
}
}
}
SavingWrapper
using UnityEngine;
namespace RPG.Saving
{
public class SavingWrapper : MonoBehaviour
{
const string defaultSaveFile = "save";
private void Start()
{
Load();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
Save();
}
if (Input.GetKeyDown(KeyCode.L))
{
Load();
}
}
public void Load()
{
GetComponent<SavingSystem>().Load(defaultSaveFile);
}
public void Save()
{
GetComponent<SavingSystem>().Save(defaultSaveFile);
}
}
}
ISaveableコンポーネント
1,各スクリプトに下記コードを記述する
SaveableEntity
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Saving
{
[ExecuteAlways]
public class SaveableEntity : MonoBehaviour
{
[SerializeField] string uniqueIdentifier = "";
public string GetUniqueIdentifier()
{
return uniqueIdentifier;
}
public object CaptureState()
{
Dictionary<string, object> state = new Dictionary<string, object>();
foreach (ISaveable saveable in GetComponents<ISaveable>())
{
state[saveable.GetType().ToString()] = saveable.CaptureState();
}
return state;
}
public void RestoreState(object state)
{
Dictionary<string, object> stateDict = (Dictionary<string, object>)state;
foreach (ISaveable saveable in GetComponents<ISaveable>())
{
string typeString = saveable.GetType().ToString();
if (stateDict.ContainsKey(typeString))
{
saveable.RestoreState(stateDict[typeString]);
}
}
}
private void Update()
{
if (Application.IsPlaying(gameObject)) return;
if (string.IsNullOrEmpty(gameObject.scene.path)) return;
SerializedObject serializedObject = new SerializedObject(this);
SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier");
if (string.IsNullOrEmpty(property.stringValue))
{
property.stringValue = System.Guid.NewGuid().ToString();
serializedObject.ApplyModifiedProperties();
}
}
}
}
Mover
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEngine;
using UnityEngine.AI;
using RPG.Saving;
namespace RPG.Movement
{
public class Mover : MonoBehaviour, IAction, ISaveable
{
[SerializeField] Transform target;
[SerializeField] float maxSpeed = 6f;
NavMeshAgent navMeshAgent;
Health health;
private void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
health = GetComponent<Health>();
}
void Update()
{
navMeshAgent.enabled = !health.IsDead();
UpdateAnimator();
}
public void StartMoveAction(Vector3 destination, float speedFraction)
{
GetComponent<ActionScheduler>().StartAction(this);
MoveTo(destination, speedFraction);
}
public void MoveTo(Vector3 destination, float speedFraction)
{
navMeshAgent.destination = destination;
navMeshAgent.speed = maxSpeed * Mathf.Clamp01(speedFraction);
navMeshAgent.isStopped = false;
}
public void Cancel()
{
navMeshAgent.isStopped = true;
}
private void UpdateAnimator()
{
Vector3 velocity = navMeshAgent.velocity;
Vector3 localVelocity = transform.InverseTransformDirection(velocity);
float speed = localVelocity.z;
GetComponent<Animator>().SetFloat("fowardSpeed", speed);
}
public object CaptureState()
{
return new SerializableVector3(transform.position);
}
public void RestoreState(object state)
{
SerializableVector3 position = (SerializableVector3)state;
GetComponent<NavMeshAgent>().enabled = false;
transform.position = position.ToVector();
GetComponent<NavMeshAgent>().enabled = true;
GetComponent<ActionScheduler>().CancelCurrentAction();
}
}
}
ISaveable
namespace RPG.Saving
{
public interface ISaveable
{
object CaptureState();
void RestoreState(object state);
}
}
UUIDの重複排除
1, 下記コードを追加する
SaveableEntity
using System;
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Saving
{
[ExecuteAlways]
public class SaveableEntity : MonoBehaviour
{
[SerializeField] string uniqueIdentifier = "";
static Dictionary<string, SaveableEntity> globalLookup = new Dictionary<string, SaveableEntity>(); //追加
public string GetUniqueIdentifier()
{
return uniqueIdentifier;
}
public object CaptureState()
{
Dictionary<string, object> state = new Dictionary<string, object>();
foreach (ISaveable saveable in GetComponents<ISaveable>())
{
state[saveable.GetType().ToString()] = saveable.CaptureState();
}
return state;
}
public void RestoreState(object state)
{
Dictionary<string, object> stateDict = (Dictionary<string, object>)state;
foreach (ISaveable saveable in GetComponents<ISaveable>())
{
string typeString = saveable.GetType().ToString();
if (stateDict.ContainsKey(typeString))
{
saveable.RestoreState(stateDict[typeString]);
}
}
}
private void Update()
{
if (Application.IsPlaying(gameObject)) return;
if (string.IsNullOrEmpty(gameObject.scene.path)) return;
SerializedObject serializedObject = new SerializedObject(this);
SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier");
if (string.IsNullOrEmpty(property.stringValue) || !IsUnique(property.stringValue)) //追加
{
property.stringValue = System.Guid.NewGuid().ToString();
serializedObject.ApplyModifiedProperties();
}
globalLookup[property.stringValue] = this; //追加
}
//追加
private bool IsUnique(string candidate)
{
if (!globalLookup.ContainsKey(candidate)) return true;
if (globalLookup[candidate] == this) return true;
if (globalLookup[candidate] == null)
{
globalLookup.Remove(candidate);
return true;
}
if (globalLookup[candidate].GetUniqueIdentifier() != candidate)
{
globalLookup.Remove(candidate);
return true;
}
return false;
}
}
}
2, PlayerプレハブのUnique Identifierをplayerに変更する。(その他シーンがある場合はそちらのPlayerもplayerに変更する)
Healthポイントの保存
1, 下記コードを追加
Health
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Saving;
namespace RPG.Core
{
public class Health : MonoBehaviour, ISaveable
{
[SerializeField] float healthPoints = 100f;
bool isDead = false;
public bool IsDead()
{
return isDead;
}
public void TakeDamage(float damage)
{
healthPoints = Mathf.Max(healthPoints - damage, 0);
if (healthPoints == 0)
{
Die();
}
}
private void Die()
{
if (isDead) return;
isDead = true;
GetComponent<Animator>().SetTrigger("die");
GetComponent<ActionScheduler>().CancelCurrentAction();
}
public object CaptureState()
{
return healthPoints;
}
public void RestoreState(object state)
{
healthPoints = (float)state;
if (healthPoints <= 0)
{
Die();
}
}
}
}
SaveableEntity
using System;
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Saving
{
[ExecuteAlways]
public class SaveableEntity : MonoBehaviour
{
[SerializeField] string uniqueIdentifier = "";
static Dictionary<string, SaveableEntity> globalLookup = new Dictionary<string, SaveableEntity>(); //追加
public string GetUniqueIdentifier()
{
return uniqueIdentifier;
}
public object CaptureState()
{
Dictionary<string, object> state = new Dictionary<string, object>();
foreach (ISaveable saveable in GetComponents<ISaveable>())
{
state[saveable.GetType().ToString()] = saveable.CaptureState();
}
return state;
}
public void RestoreState(object state)
{
Dictionary<string, object> stateDict = (Dictionary<string, object>)state;
foreach (ISaveable saveable in GetComponents<ISaveable>())
{
string typeString = saveable.GetType().ToString();
if (stateDict.ContainsKey(typeString))
{
saveable.RestoreState(stateDict[typeString]);
}
}
}
# if UNITY_EDITOR
private void Update()
{
if (Application.IsPlaying(gameObject)) return;
if (string.IsNullOrEmpty(gameObject.scene.path)) return;
SerializedObject serializedObject = new SerializedObject(this);
SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier");
if (string.IsNullOrEmpty(property.stringValue) || !IsUnique(property.stringValue)) //追加
{
property.stringValue = System.Guid.NewGuid().ToString();
serializedObject.ApplyModifiedProperties();
}
globalLookup[property.stringValue] = this;
}
# endif
private bool IsUnique(string candidate)
{
if (!globalLookup.ContainsKey(candidate)) return true;
if (globalLookup[candidate] == this) return true;
if (globalLookup[candidate] == null)
{
globalLookup.Remove(candidate);
return true;
}
if (globalLookup[candidate].GetUniqueIdentifier() != candidate)
{
globalLookup.Remove(candidate);
return true;
}
return false;
}
}
}
ラストシーンのリロード
1,各スクリプトに下記コードを記述する
SavingWrapper
using System.Collections; //追加
using UnityEngine;
namespace RPG.Saving
{
public class SavingWrapper : MonoBehaviour
{
const string defaultSaveFile = "save";
//変更
private IEnumerator Start()
{
yield return GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
Save();
}
if (Input.GetKeyDown(KeyCode.L))
{
Load();
}
}
public void Load()
{
GetComponent<SavingSystem>().Load(defaultSaveFile);
}
public void Save()
{
GetComponent<SavingSystem>().Save(defaultSaveFile);
}
}
}
SavingSystem
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement; //追加
namespace RPG.Saving
{
public class SavingSystem : MonoBehaviour
{
//追加
public IEnumerator LoadLastScene(string saveFile)
{
Dictionary<string, object> state = LoadFile(saveFile);
if (state.ContainsKey("lastSceneBuildIndex"))
{
int buildIndex = (int)state["lastSceneBuildIndex"];
if (buildIndex != SceneManager.GetActiveScene().buildIndex)
{
yield return SceneManager.LoadSceneAsync(buildIndex);
}
}
RestoreState(state);
}
public void Save(string saveFile)
{
Dictionary<string, object> state = LoadFile(saveFile);
CaptureState(state);
SaveFile(saveFile, state);
}
public void Load(string saveFile)
{
RestoreState(LoadFile(saveFile));
}
private Dictionary<string, object> LoadFile(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
if (!File.Exists(path))
{
return new Dictionary<string, object>();
}
using (FileStream stream = File.Open(path, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
return (Dictionary<string, object>)formatter.Deserialize(stream);
}
}
private void SaveFile(string saveFile, object state)
{
string path = GetPathFromSaveFile(saveFile);
print("Saving to " + path);
using (FileStream stream = File.Open(path, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, state);
}
}
private void CaptureState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
}
state["lastSceneBuildIndex"] = SceneManager.GetActiveScene().buildIndex; //追加
}
private void RestoreState(Dictionary<string, object> state)
{
foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
{
string id = saveable.GetUniqueIdentifier();
if (state.ContainsKey(id))
{
saveable.RestoreState(state[id]);
}
}
}
private string GetPathFromSaveFile(string saveFile)
{
return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
}
}
}
Portal
using System;
using System.Collections;
using RPG.Saving;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
namespace RPG.SceneManagement
{
public class Portal : MonoBehaviour
{
enum DestinationIdentifier
{
A, B, C, D, E
}
[SerializeField] int sceneToLoad = -1;
[SerializeField] Transform spawnPoint;
[SerializeField] DestinationIdentifier destination;
[SerializeField] float fadeOutTime = 1f;
[SerializeField] float fadeInTime = 2f;
[SerializeField] float fadeWaitTime = 0.5f;
//private void Start()
//{
// transform.parent = null;
// DontDestroyOnLoad(gameObject);
//}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
StartCoroutine(Transition());
}
}
private IEnumerator Transition()
{
if (sceneToLoad < 0)
{
Debug.LogError("Scene to load not set.");
yield break;
}
transform.parent = null;
DontDestroyOnLoad(gameObject);
Fader fader = FindObjectOfType<Fader>();
SavingWrapper savingWrapper = FindObjectOfType<SavingWrapper>();
yield return fader.FadeOut(fadeOutTime);
savingWrapper.Save();
yield return SceneManager.LoadSceneAsync(sceneToLoad);
savingWrapper.Load();
Portal otherPortal = GetOtherPortal();
UpdatePlayer(otherPortal);
savingWrapper.Save(); //追加
yield return new WaitForSeconds(fadeWaitTime);
yield return fader.FadeIn(fadeInTime);
Destroy(gameObject);
}
private void UpdatePlayer(Portal otherPortal)
{
GameObject player = GameObject.FindWithTag("Player");
player.GetComponent<NavMeshAgent>().enabled = false;
player.GetComponent<NavMeshAgent>().Warp(otherPortal.spawnPoint.position);
player.transform.rotation = otherPortal.spawnPoint.rotation;
player.GetComponent<NavMeshAgent>().enabled = true;
}
private Portal GetOtherPortal()
{
foreach (Portal portal in FindObjectsOfType<Portal>())
{
if (portal == this) continue;
if (portal.destination != destination) continue;
return portal;
}
return null;
}
}
}
シーンロード前にフェードする
1, 各スクリプトに下記コードを記述する
Fader
using System.Collections;
using UnityEngine;
namespace RPG.SceneManagement
{
public class Fader : MonoBehaviour
{
CanvasGroup canvasGroup;
private void Awake()
{
canvasGroup = GetComponent<CanvasGroup>();
}
//追加
public void FadeOutImmediate()
{
canvasGroup.alpha = 1;
}
public IEnumerator FadeOut(float time)
{
while (canvasGroup.alpha < 1)
{
canvasGroup.alpha += Time.deltaTime / time;
yield return null;
}
}
public IEnumerator FadeIn(float time)
{
while (canvasGroup.alpha > 0)
{
canvasGroup.alpha -= Time.deltaTime / time;
yield return null;
}
}
}
}
SavingWrapper
using System.Collections;
using RPG.Saving;
using UnityEngine;
namespace RPG.SceneManagement
{
public class SavingWrapper : MonoBehaviour
{
const string defaultSaveFile = "save";
[SerializeField] float fadeInTime = 0.2f;
private IEnumerator Start()
{
Fader fader = FindObjectOfType<Fader>();
fader.FadeOutImmediate();
yield return GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile);
yield return fader.FadeIn(fadeInTime);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
Save();
}
if (Input.GetKeyDown(KeyCode.L))
{
Load();
}
}
public void Load()
{
GetComponent<SavingSystem>().Load(defaultSaveFile);
}
public void Save()
{
GetComponent<SavingSystem>().Save(defaultSaveFile);
}
}
}