前言
源代码来自M_Studio的《勇者传说》教程,以下是视频链接。
【存储点及画面效果|Unity2022.2 最新教程《勇士传说》入门到进阶|4K】
我在源代码基础上加入了json序列化保存到本地。
虽然这篇文章以记录为主,为了自己回顾使用,但也稍微加了一点注释方便大家阅读学习。
代码:
DataDefination.cs
为将要保存的对象生成一个uid
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DataDefination : MonoBehaviour
{
public PersistentType persistentType;
public string ID;
private void OnValidate()
{
if(persistentType==PersistentType.ReadWrite){
if(ID==string.Empty){
ID = System.Guid.NewGuid().ToString();
}
}else{
ID = string.Empty;
}
}
}
public enum PersistentType{
ReadWrite,DoNotPersist
}
ISavable.cs
接口,规范可保存数据脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISaveable
{
DataDefination GetDataId();
void RegisterSaveData()=>DataManager.instance.RegisterSaveData(this);
void UnRegisterSaveData()=>DataManager.instance.UnRegisterSaveData(this);
void GetSaveData(Data data);
void LoadData(Data data);
}
Data.cs
存储的数据(此处包括场景、角色当前的位置、一些数值信息)和辅助场景存储的方法。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Data
{
public string sceneToSave;
public Dictionary<string,Vector3> characterPosDict = new Dictionary<string, Vector3>();
public Dictionary<string,float> floatSavedData = new Dictionary<string, float>();
public string getGameSceneJson(GameSceneSO scene){
return JsonUtility.ToJson(scene);
}
public void SaveGameScene(GameSceneSO savedScene){
sceneToSave = JsonUtility.ToJson(savedScene);
}
public GameSceneSO GetSavedScene(){
var newScene = ScriptableObject.CreateInstance<GameSceneSO>();
JsonUtility.FromJsonOverwrite(sceneToSave, newScene);
return newScene;
}
}
DataManager.cs
存档的主要逻辑
数据会被存放在本地的Application.persistentDataPath\save.json中
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
[DefaultExecutionOrder(-100)]//保证在其他脚本之前执行
public class DataManager : MonoBehaviour
{
[Header("事件监听")]
public VoidEventSO saveDataEvent;//监听保存请求,调用save()进行保存
public VoidEventSO loadDataEvent;//监听加载请求,调用load()加载存档
public SceneLoadEventSO unloadSceneEvent;
public VoidEventSO afterSceneLoadEvent;
public SceneLoader sceneLoader;
private List<ISaveable> saveableList = new List<ISaveable>();//保存项列表,注册后的保存项会被存在这里
public Data saveData;//保存的数据
public static DataManager instance;//单例模式
// 自定义 JsonSerializerSettings,注册 Vector3Converter,用于vector3的序列化
private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings {
Formatting = Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Converters = new List<JsonConverter> { new Vector3Converter() }
};
void Update()//测试用(按L保存 按K加载)
{
if(Keyboard.current.lKey.wasPressedThisFrame){
save();
}
if(Keyboard.current.kKey.wasPressedThisFrame){
load();
}
}
void OnEnable()
{
saveDataEvent.OnEventRaised += save;
loadDataEvent.OnEventRaised += load;
//退出事件注册
Application.wantsToQuit += OnWantsToQuit;
Application.quitting += save;
}
void OnDisable()
{
saveDataEvent.OnEventRaised -= save;
loadDataEvent.OnEventRaised -= load;
//退出事件取消注册
Application.wantsToQuit -= OnWantsToQuit;
Application.quitting -= save;
}
public void Awake()
{
//单例模式
if (instance == null){
instance = this;
DontDestroyOnLoad(gameObject);
}else{
Destroy(gameObject);
}
saveData = new Data();//实例化一个Data
}
//退出前时存档
private bool OnWantsToQuit()
{
save();
return true;
}
public void RegisterSaveData(ISaveable saveData){//处理保存项的注册
if(!saveableList.Contains(saveData)){如果没在则加入
saveableList.Add(saveData);
}
}
public void UnRegisterSaveData(ISaveable saveData){//处理保存项的注销
if(saveableList.Contains(saveData)){//如果有则移除
saveableList.Remove(saveData);
}
}
//保存
public void save(){
//防止在主菜单场景保存(这个跟场景加载系统相关)
if(sceneLoader.currentLoadScene.sceneType==sceneType.Location){
//遍历每一个保存项,将他们需要保存的数据存放到saveData中
foreach(ISaveable savable in saveableList){
savable.GetSaveData(saveData);
}
//将saveData序列化存到本地
SaveToFile(saveData);
}
}
//读取存档
public void load()
{
//从文件中读取数据到saveData
saveData = LoadFromFile();
//这里默认如果存在角色位置信息,则saveData中有数据可读取
if(saveData.characterPosDict.Count>0){
Debug.Log("读取存档");
foreach(var savable in saveableList){//遍历每一个保存项,各自读取数据
savable.LoadData(saveData);
}
}
}
//处理文件操作
public void SaveToFile(Data data)
{
string path = Path.Combine(Application.persistentDataPath, "save.json");
string json = JsonConvert.SerializeObject(data, _jsonSettings);
File.WriteAllText(path, json);
Debug.Log($"存档已写入:{path}");
}
public Data LoadFromFile()
{
string path = Path.Combine(Application.persistentDataPath, "save.json");
if (!File.Exists(path))
{
Debug.LogWarning($"读取失败:文件不存在 ({path})");
return saveData;
}
string json = File.ReadAllText(path);
Data data = JsonConvert.DeserializeObject<Data>(json, _jsonSettings);
Debug.Log("存档读取完成");
return data;
}
}
VoidEventSO.cs
用于简单的事件通知
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu(menuName = "Event/VoidEventSO")]
public class VoidEventSO : ScriptableObject
{
public UnityAction OnEventRaised;
public void RaiseEvent()
{
OnEventRaised?.Invoke();
}
}
Vector3Converter.cs
我让GPT写的
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
// 1. 定义一个只处理 x,y,z 的 Vector3 转换器
public class Vector3Converter : JsonConverter<Vector3>
{
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("x"); writer.WriteValue(value.x);
writer.WritePropertyName("y"); writer.WriteValue(value.y);
writer.WritePropertyName("z"); writer.WriteValue(value.z);
writer.WriteEndObject();
}
public override Vector3 ReadJson(JsonReader reader, System.Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
float x = jo["x"].Value<float>();
float y = jo["y"].Value<float>();
float z = jo["z"].Value<float>();
return new Vector3(x, y, z);
}
}
加载项的写法
public class 含有需要保存数据的类 : MonoBehaviour,ISaveable
{
public VoidEventSO loadDataEvent;//如果需要用来在某个时机加载loadDataEvent?.RaiseEvent();广播事件来读取数据
private void OnEnable(){
//注册保存数据
ISaveable savable = this;
savable.RegisterSaveData();
}
private void OnDisable(){
//注销保存数据
ISaveable savable = this;
savable.UnRegisterSaveData();
}
//获取uid
public DataDefination GetDataId()
{
return GetComponent<DataDefination>();
}
public void GetSaveData(Data data)
{
//TODO:将数据存入data
}
public void LoadData(Data data)
{
//TODO:从data中读取数据,并完成加载处理
}
}
结尾
加油!ヾ(◍°∇°◍)ノ゙