Unity3D 序列化与动态加载
注:本实例开发环境为Unity3D 5.3.4,开发语言为C#
这次的任务是修改上次的ShootThatUFO打飞碟小游戏,使其具有数据驱动(data driven)游戏的特性。
- 游戏启动后,如果它发现 远程控制目录 有文件且版本与当前游戏不同,提示并升级。程序首页应显示升级后版本,并展示预定义的行为.
- 如果没有升级,用户可以选择从上次 Round 和 Turn 开始,或者 从头开始。
首先区分数据:有些数据是静态数据,有些数据是运行时数据。对他们处理的方法不一样。
游戏计划 - 为静态的游戏参数,或者称为规则
运行时数据 - 随游戏进行而不断更新的数据
定义两者的数据结构,标记为可序列化:
[System.Serializable] public class GamePlan { public int gameVersion = 1; public float[] intervalPerRound = {2f, 2f, 1f, 1f, 0.5f}; public float[] tossRange = {10f, 10f, 15f, 20f, 20f}; public float[] tossSpeed = {20f, 20f, 25f, 25f, 30f}; public float[] ufoScale = {0.5f, 0.8f, 1f}; public Color[] ufoColor = {Color.green, Color.yellow, Color.red}; public float[] tossMaxAngle = {0.6f,0.6f,0.6f,0.6f,0.7f}; } [System.Serializable] public class RunTimeGameData { public int gameStatus = 0; public float gameStartCountDown = 3f; public bool okToShoot = true; public int round = 0; public int point = 0; public int trials = 0; public int ufoToToss = 20; public int highscore = 0; }
游戏初始化的思路是:获取本地游戏计划->获取远程游戏计划(等待)->比较游戏版本,是否有新版本?
(Y)-> 使用全新的运行时数据,加载新游戏计划,复写本地版本计划,提示用户有新版本
(N)-> 加载本地运行时数据,使用本地游戏计划,询问用户是否需要继续上一次游戏
BaseCode是游戏初始化,版本控制,游戏数据的控制器,在BaseCode中,更新Start()方法:
注:这里考虑到有可能会降级?并没有严格控制新版本号要大于老版本号
IEnumerator Start () { string gamePlan = File.ReadAllText (Application.dataPath + "/Resources/GamePlan.json"); gp_local = JsonUtility.FromJson<GamePlan> (gamePlan); string url = "file:///Users/WangYinghao/Documents/Unity%20Playground/ShootThatUFOData/GamePlan.json"; WWW gamePlanFromJSon = new WWW(url); yield return gamePlanFromJSon; gp_remote = JsonUtility.FromJson<GamePlan>(gamePlanFromJSon.text); if (gp_remote.gameVersion != gp_local.gameVersion) { gameUpdated = true; string newGPLocal = JsonUtility.ToJson(gp_remote, true); File.WriteAllText(Application.dataPath + "/Resources/GamePlan.json", newGPLocal); rt = new RunTimeGameData(); rt.gameStatus = -1; } else { string runTimeData = File.ReadAllText(Application.dataPath + "/Resources/RuntimeGameData.json"); rt = JsonUtility.FromJson<RunTimeGameData>(runTimeData); Debug.Log(runTimeData); if (rt.gameStatus == 1 || rt.gameStatus == 2) { Debug.Log("gameStatus == 1 or 2 (ingame or midgame) Continue? or start Again?"); rt.gameStatus = 5; } else { rt = new RunTimeGameData(); Debug.Log("Start Again"); } } sceneController = SceneController.GetInstance (); sceneController.setBaseCode (this); if (!gameUpdated) { sceneController.init(gp_local, rt); } else { sceneController.init(gp_remote, rt); } gameModel = UnityEngine.Camera.main.gameObject.AddComponent <GameModel> (); judge = UnityEngine.Camera.main.gameObject.AddComponent <Judge> (); ui = UnityEngine.Camera.main.gameObject.AddComponent <UserInterface> (); }
游戏状态新增两个状态:-1 代表有新版本,5 代表等待用户决定是否继续上一次游戏
据此,更新UserInterface类:
(用的仍然是旧GUI。。。)
void OnGUI () { // Make a background box if (userAction.getGameStatus() == 5) { GUI.Label(new Rect(20, 20, 300, 20), "Shoot That UFO! Last Time Game Unfinished! Continue?"); if (GUI.Button(new Rect(20, 40, 80, 20), "Continue")) { userAction.gameStart(); } if (GUI.Button(new Rect(120, 40, 80, 20), "Start Again")) { userAction.startOver(); } } else if (userAction.getGameStatus() == -1) { GUI.Label(new Rect(20, 20, 300, 20), "Shoot That UFO! New Updates! New Version: " + userAction.getGameVersion()); if (GUI.Button(new Rect(20, 40, 80, 20), "Cool!")) { userAction.startOver(); } } else { if (userAction.getGameStatus() == 1) { GUI.Label(new Rect(20, 20, 80, 20), "Round " + (userAction.getRound() + 1)); GUI.Label(new Rect(20, 40, 80, 20), userAction.getUFONum() + " to Go"); } if (userAction.getGameStatus() == 0 || userAction.getGameStatus() == 2) { GUI.Label(new Rect(20, 20, 300, 20), "Shoot That UFO! Hit Space Bar to Begin!"); GUI.Label(new Rect(20, 40, 100, 20), "Time to start: " + gameStartTimer.ToString("0.0")); GUI.Label(new Rect(20, 100, 100, 20), "Next Round: " + (userAction.getRound() + 1)); } if (userAction.getGameStatus() == 3) { GUI.Label(new Rect(20, 20, 200, 20), "Failed! Hit Spacebar to Restart."); GUI.Label(new Rect(20, 40, 100, 20), "Time to start: " + gameStartTimer.ToString("0.0")); } if (userAction.getGameStatus() != 4) { GUI.Label(new Rect(20, 80, 150, 20), "You have " + (10f - userAction.getTrial()) + " bullets left."); GUI.Label(new Rect(700, 20, 100, 20), "Highscore: " + userAction.getHighscore()); } if (userAction.getGameStatus() == 4) { GUI.Label(new Rect(20, 20, 150, 20), "Game Cleared!"); } GUI.Label (new Rect (20, 60, 150, 20), "Your score is " + userAction.getPoint()); GUI.Label (new Rect(700, 40, 300, 20), "Ver: " + userAction.getGameVersion()); } }
BaseCode中,为了保存运行时数据,更新Update()代码,设定为每1秒自动保存一次
不希望IO影响游戏,利用协程
void Update (){ gameSaveInterval++; if (gameSaveInterval > Application.targetFrameRate){ StartCoroutine(saveRunTimeData()); gameSaveInterval = 0; } } IEnumerator saveRunTimeData() { Debug.Log("Here!"); RunTimeGameData rt = sceneController.getRunTimeGameData(); File.WriteAllText(Application.dataPath + "/Resources/RunTimeGameData.json", JsonUtility.ToJson(rt, true)); yield return null; }
在远程目录放新的游戏计划,需要改变游戏计划时,将其他version的游戏计划改名为GamePlan.json
实际效果:
有更新:
无更新: