public virtual async UniTask <GameStateMap> LoadGameAsync(string slotId) { if (string.IsNullOrEmpty(slotId) || !GameStateSlotManager.SaveSlotExists(slotId)) { throw new Exception($"Slot '{slotId}' not found when loading '{typeof(GameStateMap)}' data."); } var quick = slotId.EqualsFast(LastQuickSaveSlotId); OnGameLoadStarted?.Invoke(new GameSaveLoadArgs(slotId, quick)); if (Configuration.LoadStartDelay > 0) { await UniTask.Delay(TimeSpan.FromSeconds(Configuration.LoadStartDelay)); } Engine.Reset(); await Resources.UnloadUnusedAssets(); var state = await GameStateSlotManager.LoadAsync(slotId); await LoadAllServicesFromStateAsync <IStatefulService <GameStateMap>, GameStateMap>(state); // All the serialized snapshots are expected to allow player rollback. RollbackStack?.OverrideFromJson(state.RollbackStackJson, s => s.AllowPlayerRollback()); for (int i = onGameDeserializeTasks.Count - 1; i >= 0; i--) { await onGameDeserializeTasks[i](state); } OnGameLoadFinished?.Invoke(new GameSaveLoadArgs(slotId, quick)); return(state); }
public async UniTask <GameStateMap> SaveGameAsync(string slotId) { if (rollbackStack.Count == 0 || rollbackStack.Peek() is null) { PushRollbackSnapshot(false); } var quick = slotId.StartsWithFast(Configuration.QuickSaveSlotMask.GetBefore("{")); OnGameSaveStarted?.Invoke(new GameSaveLoadArgs(slotId, quick)); var state = new GameStateMap(rollbackStack.Peek()); state.SaveDateTime = DateTime.Now; state.Thumbnail = Engine.GetService <ICameraManager>().CaptureThumbnail(); var lastZero = rollbackStack.FirstOrDefault(s => s.PlaybackSpot.InlineIndex == 0); // Required when changing locale. bool filter(GameStateMap s) => (Configuration.EnableStateRollback && Configuration.SavedRollbackSteps > 0 && s.PlayerRollbackAllowed) || s == lastZero; state.RollbackStackJson = rollbackStack.ToJson(Configuration.SavedRollbackSteps, filter); await GameStateSlotManager.SaveAsync(slotId, state); // Also save global state on every game save. await SaveGlobalStateAsync(); OnGameSaveFinished?.Invoke(new GameSaveLoadArgs(slotId, quick)); return(state); }
public StateManager(StateConfiguration config, EngineConfiguration engineConfig, InputManager inputManager) { this.config = config; this.inputManager = inputManager; var allowUserRollback = config.StateRollbackMode == StateRollbackMode.Full || (config.StateRollbackMode == StateRollbackMode.Debug && Debug.isDebugBuild); var rollbackCapacity = allowUserRollback ? Mathf.Max(1, config.StateRollbackSteps) : 1; // One step is reserved for game save operations. rollbackStateStack = new StateRollbackStack(rollbackCapacity); var savesFolderPath = PathUtils.Combine(engineConfig.GeneratedDataPath, config.SaveFolderName); GameStateSlotManager = new GameStateSlotManager(savesFolderPath, config.SaveSlotMask, config.QuickSaveSlotMask, config.SaveSlotLimit, config.QuickSaveSlotLimit, config.BinarySaveFiles); GlobalStateSlotManager = new GlobalStateSlotManager(savesFolderPath, config.DefaultGlobalSlotId, config.BinarySaveFiles); SettingsSlotManager = new SettingsSlotManager(engineConfig.GeneratedDataPath, config.DefaultSettingsSlotId, false); }
public virtual async UniTask <GameStateMap> SaveGameAsync(string slotId) { var quick = slotId.StartsWithFast(Configuration.QuickSaveSlotMask.GetBefore("{")); OnGameSaveStarted?.Invoke(new GameSaveLoadArgs(slotId, quick)); var state = new GameStateMap(); await scriptPlayer.SynchronizeAndDoAsync(DoSaveAfterSync); OnGameSaveFinished?.Invoke(new GameSaveLoadArgs(slotId, quick)); return(state); async UniTask DoSaveAfterSync() { state.SaveDateTime = DateTime.Now; state.Thumbnail = cameraManager.CaptureThumbnail(); SaveAllServicesToState <IStatefulService <GameStateMap>, GameStateMap>(state); for (int i = onGameSerializeTasks.Count - 1; i >= 0; i--) { onGameSerializeTasks[i](state); } if (RollbackStack != null) { // Closest spot with zero inline index is used when changing locale (UI/GameSettings/LanguageDropdown.cs). var nearestStartLineSpot = RollbackStack.FirstOrDefault(s => s.PlaybackSpot.InlineIndex == 0); bool SelectSerializedSnapshots(GameStateMap s) => s.PlayerRollbackAllowed || s == nearestStartLineSpot; state.RollbackStackJson = RollbackStack.ToJson(Configuration.SavedRollbackSteps, SelectSerializedSnapshots); } await GameStateSlotManager.SaveAsync(slotId, state); // Also save global state on every game save. await SaveGlobalStateAsync(); } }
public async UniTask <GameStateMap> QuickSaveAsync() { // Free first quick save slot by shifting existing ones by one. for (int i = Configuration.QuickSaveSlotLimit; i > 0; i--) { var curSlotId = Configuration.IndexToQuickSaveSlotId(i); var prevSlotId = Configuration.IndexToQuickSaveSlotId(i + 1); GameStateSlotManager.RenameSaveSlot(curSlotId, prevSlotId); } // Delete the last slot in case it's out of the limit. var outOfLimitSlotId = Configuration.IndexToQuickSaveSlotId(Configuration.QuickSaveSlotLimit + 1); if (GameStateSlotManager.SaveSlotExists(outOfLimitSlotId)) { GameStateSlotManager.DeleteSaveSlot(outOfLimitSlotId); } var firstSlotId = string.Format(Configuration.QuickSaveSlotMask, 1); return(await SaveGameAsync(firstSlotId)); }