Unity中能否保存Play Mode内设置/修改的变量?
嘿,这个问题我之前帮不少做模拟经营类游戏的开发者踩过坑——Unity默认情况下,Play Mode里对脚本变量的修改只会在运行时生效,退出后就会打回你在Inspector面板里设置的初始值,这就是你遇到的核心问题。要让日期修改在退出Play Mode后也保留,本质是要把运行时的变量变更同步到编辑器的持久化数据里,或者直接做本地数据存储。下面给你几个实用的方案,按复杂度从低到高排序:
方案1:用Editor脚本同步变量(适合开发阶段快速调试)
这个方法能让你在Play Mode里改完日期,退出后自动把值同步回Inspector的初始配置,不用额外写文件存储逻辑。
步骤:
- 给你的日期管理器脚本加上
[ExecuteInEditMode]属性(注意:这个属性会让脚本在编辑模式下也运行,所以要给运行时专属的逻辑加Application.isPlaying判断,避免编辑时的意外行为) - 在Assets目录下创建一个
Editor文件夹(必须叫这个名字,Unity才会识别为编辑器脚本目录),然后写一个Editor脚本监听Play Mode退出事件,同步变量值。
代码示例:
首先是你的核心日期管理器脚本:
using UnityEngine; [ExecuteInEditMode] public class DateManager : MonoBehaviour { public int currentDay = 1; public int currentSeason = 0; // 比如0=春,1=夏,以此类推 public int currentYear = 1; // 与床交互的日期修改逻辑 public void ChangeDate(int day, int season, int year) { // 只在运行时执行修改 if (Application.isPlaying) { currentDay = day; currentSeason = season; currentYear = year; } } }
然后是Editor目录下的同步脚本:
using UnityEditor; using UnityEngine; [CustomEditor(typeof(DateManager))] public class DateManagerEditor : Editor { private DateManager _dateManager; void OnEnable() { _dateManager = target as DateManager; // 监听Play Mode状态变化 EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } void OnDisable() { // 移除监听,避免内存泄漏 EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; } void OnPlayModeStateChanged(PlayModeStateChange state) { // 当Play Mode退出到编辑模式时 if (state == PlayModeStateChange.ExitingPlayMode) { // 先缓存运行时的变量值 int cachedDay = _dateManager.currentDay; int cachedSeason = _dateManager.currentSeason; int cachedYear = _dateManager.currentYear; // 延迟执行,确保已经回到编辑模式 EditorApplication.delayCall += () => { // 把缓存的值赋值回Inspector的初始变量 _dateManager.currentDay = cachedDay; _dateManager.currentSeason = cachedSeason; _dateManager.currentYear = cachedYear; // 标记对象为已修改,让Unity保存这些变更 EditorUtility.SetDirty(_dateManager); AssetDatabase.SaveAssets(); }; } } }
这个方案的优势是零文件读写,开发阶段调试超方便;缺点是Editor脚本不会被打包进游戏,所以只适合编辑器内测试。
方案2:本地文件持久化(适合最终打包版本)
如果你的游戏需要在打包后让玩家退出再进也能保留日期进度,就必须把数据写到本地文件里。这里给你两种常用的实现方式:
方式A:用PlayerPrefs(简单快捷)
PlayerPrefs是Unity提供的轻量级键值对存储,适合存储简单的数值型数据:
using UnityEngine; public class DateManager : MonoBehaviour { public int currentDay = 1; public int currentSeason = 0; public int currentYear = 1; void Awake() { // 游戏启动时读取保存的日期 LoadSavedDate(); } public void ChangeDate(int day, int season, int year) { currentDay = day; currentSeason = season; currentYear = year; // 修改后立即保存到本地 SaveDate(); } void SaveDate() { PlayerPrefs.SetInt("Game_Day", currentDay); PlayerPrefs.SetInt("Game_Season", currentSeason); PlayerPrefs.SetInt("Game_Year", currentYear); PlayerPrefs.Save(); // 强制写入磁盘,避免数据丢失 } void LoadSavedDate() { // 如果有保存的数据就读取,没有就用默认值 currentDay = PlayerPrefs.GetInt("Game_Day", 1); currentSeason = PlayerPrefs.GetInt("Game_Season", 0); currentYear = PlayerPrefs.GetInt("Game_Year", 1); } // 编辑器内退出Play Mode时也保存 #if UNITY_EDITOR void OnApplicationQuit() { SaveDate(); } #endif }
这个方案代码极简,打包后也能正常工作;缺点是存储的数据是明文的,如果需要隐私性可以给数值加个简单的加密(比如异或运算)。
方式B:用JSON文件(灵活扩展)
如果之后要扩展更多保存内容(比如玩家金钱、物品),JSON文件会更适合:
using UnityEngine; using System.IO; // 定义日期数据结构 [System.Serializable] public class SaveDateData { public int day; public int season; public int year; } public class DateManager : MonoBehaviour { public int currentDay = 1; public int currentSeason = 0; public int currentYear = 1; private string _saveFilePath; void Awake() { // 保存路径用Unity的持久化目录,跨平台都能用 _saveFilePath = Path.Combine(Application.persistentDataPath, "game_date_save.json"); LoadSavedDate(); } public void ChangeDate(int day, int season, int year) { currentDay = day; currentSeason = season; currentYear = year; SaveDate(); } void SaveDate() { SaveDateData data = new SaveDateData(); data.day = currentDay; data.season = currentSeason; data.year = currentYear; // 序列化为JSON字符串 string jsonString = JsonUtility.ToJson(data); // 写入文件 File.WriteAllText(_saveFilePath, jsonString); } void LoadSavedDate() { if (File.Exists(_saveFilePath)) { string jsonString = File.ReadAllText(_saveFilePath); SaveDateData data = JsonUtility.FromJson<SaveDateData>(jsonString); currentDay = data.day; currentSeason = data.season; currentYear = data.year; } // 没有保存文件就用默认值 } #if UNITY_EDITOR void OnApplicationQuit() { SaveDate(); } #endif }
这个方案扩展性强,后续加新的保存字段只需要修改SaveDateData类就行。
方案3:用ScriptableObject存储(编辑器+打包通用)
ScriptableObject是Unity的专属数据容器,既可以在编辑器里创建资产文件存储数据,运行时修改后能直接保存到资产里,退出Play Mode也不会丢,打包后也能正常读取,是我最推荐的Unity式数据管理方式。
步骤:
- 创建ScriptableObject类:
using UnityEngine; // 右键菜单创建资产 [CreateAssetMenu(fileName = "GameDateData", menuName = "Custom/Game Date Data")] public class GameDateData : ScriptableObject { public int currentDay = 1; public int currentSeason = 0; public int currentYear = 1; }
- 在Project窗口右键,选择
Custom > Game Date Data,创建一个资产文件(比如叫DefaultDateData)。 - 修改你的日期管理器脚本:
using UnityEngine; public class DateManager : MonoBehaviour { public GameDateData dateData; // 在Inspector里拖入你创建的DateData资产 public void ChangeDate(int day, int season, int year) { dateData.currentDay = day; dateData.currentSeason = season; dateData.currentYear = year; // 编辑器内标记资产为已修改,保存变更 #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(dateData); UnityEditor.AssetDatabase.SaveAssets(); #endif } // 启动时可以读取数据(如果需要) void Awake() { currentDay = dateData.currentDay; currentSeason = dateData.currentSeason; currentYear = dateData.currentYear; } }
这个方案的优势是数据和代码完全分离,编辑器内调试方便,打包后也能正常工作;如果需要多存档,只需要创建多个GameDateData资产就行。
最后给你个选择建议:
- 开发阶段快速调试:用方案1或方案3;
- 打包后玩家进度保存:用方案2或方案3;
- 追求Unity最佳实践:优先选方案3。
内容的提问来源于stack exchange,提问作者PolyRose

