public static void ResizeLevel(int width, int height, ref LevelConfig config) { var resizedLayout = new GridNodeLayout[width * height]; var defaultGridItem = GameConfig.GetDefaultLayoutGridItem(config.category); var openNode = GameConfig.AllGridNodes.First(node => node.IsOpen); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (config.IsInBounds(x, y)) { resizedLayout[GetLinearIndex(x, y, width)] = config.GetNodeLayout(x, y); } else { resizedLayout[GetLinearIndex(x, y, width)] = new GridNodeLayout() { nodeId = openNode.ID, itemId = defaultGridItem.ID } }; } } config.width = width; config.height = height; config.layout = resizedLayout; }
public static void ExportLevelFile(int levelIndex, LevelConfig config) { string resourceName = GetLevelResourceName(levelIndex); #if UNITY_EDITOR // the editor exports to Resources string pathToAsset = Path.Combine("Data", "Resources", $"{resourceName}.txt"); string pathToFile = Path.Combine(Application.dataPath, pathToAsset); #else // device exports to persistent data, so the file can be retrieved later and imported into the Unity project string pathToFile = Path.Combine(Application.persistentDataPath, $"{resourceName}.txt"); #endif try { string json = JsonUtility.ToJson(config); Logger.LogEditor($"[EXPORT] Level={levelIndex}, Data={json}"); // adb logcat, copy and paste ;) using (var writer = new StreamWriter(pathToFile)) { writer.Write(json); Logger.LogEditor($"[EXPORT] Level '{resourceName}' was exported to file: {pathToFile}"); } } catch (System.Exception e) { Logger.LogError($"Error while exporting '{resourceName}' to PersistentDataPath '{pathToFile}': {e.Message}"); } #if UNITY_EDITOR // reimport, else the imported asset is stale UnityEditor.AssetDatabase.ImportAsset(Path.Combine("Assets", pathToAsset)); #endif }
private void LoadLevel(Level.LoadLevelEvent loadLevelEvent) { _levelIndex = loadLevelEvent.LevelIndex; _levelConfig = loadLevelEvent.LevelConfig; Logger.LogEditor($"Load level={_levelIndex}, size={_levelConfig.width}x{_levelConfig.height}"); _levelScoreView.SetGoals(_levelIndex, _levelConfig); // clear the playfield ClearGridNodeInstances(); ClearGridItemInstances(); // if there is no grid state then auto-open the level editor if (loadLevelEvent.InitialGridState == null) { MessagePopup.ShowMessage("This level is empty. Edit mode is enabled."); _levelConfig = LevelConfig.CreateDefaultLevel(5, 5); EnableEditMode(); return; } // calculate the playfield dimensions Vector3 playfieldSize = _playfieldTransform.sizeDelta; _tileSize = CalculateTileSize(playfieldSize, new Vector2(_levelConfig.width, _levelConfig.height)); _playfieldOrigin = new Vector3( // [0,0] = lower-left corner (_levelConfig.width - 1) * _tileSize * -0.5f, (_levelConfig.height - 1) * _tileSize * -0.5f); // instantiate the level layout for (int y = 0; y < _levelConfig.height; y++) { for (int x = 0; x < _levelConfig.width; x++) { var nodeLayout = _levelConfig.GetNodeLayout(x, y); Assert.IsNotNull(nodeLayout.nodeId, $"Level layout is corrupted at ({x},{y})"); var pos = CalculateGridNodePosition(x, y); // load the node from the config CreateGridNodeView(x, y, nodeLayout.nodeId, pos); // if in edit mode then load the item from the config, else load from the state string itemType = _state == State.EditMode ? nodeLayout.itemId : loadLevelEvent.InitialGridState.FirstOrDefault(item => item.Index.x == x && item.Index.y == y)?.ItemId; // if (itemType != null) // HACK: load all items, even if they are invalid. this makes the level editor easier to use { CreateGridItemView(x, y, itemType, pos); } } } }
public LoadLevelEvent(int levelIndex, LevelConfig levelConfig, GridNodeState[,] gridState) { // Payload.SetField((int)Fields.LevelIndex, levelIndex); LevelIndex = levelIndex; LevelConfig = levelConfig; if (gridState != null) { InitialGridState = new List <GridNodeState>(); foreach (var item in gridState) { InitialGridState.Add(item); } } }
public void SetGoals(int levelIndex, LevelConfig levelConfig) { _levelConfig = levelConfig; // TODO: support other goal and challenge types //var sliderSize = _sliderTransform.sizeDelta; var sliderSize = _sliderTransform.rect; _goal1Transform.anchoredPosition = new Vector2(sliderSize.width * ((float)levelConfig.goal1 / (float)levelConfig.goal3) + _goalCompletedOffset, _goal1Transform.anchoredPosition.y); _goal2Transform.anchoredPosition = new Vector2(sliderSize.width * ((float)levelConfig.goal2 / (float)levelConfig.goal3) + _goalCompletedOffset, _goal2Transform.anchoredPosition.y); _slider.value = 0; _pointsText.text = "0"; _movesText.text = levelConfig.challengeValue.ToString(); _levelText.text = levelIndex.ToString(); }
public static void ExecuteCommand(Command command) { switch (command.CommandType) { case CommandType.LoadLevel: { int levelIndex = (command as LoadLevelCommand).LevelIndex; LevelConfig.LoadAsync(levelIndex, (index, config) => { // TODO: continue loading even if the config is null. this would normally be an error, but we use this to trigger the level editor. if (config == null) { config = new LevelConfig(); } _levelState = new LevelState(index, config); BroadcastEvent(new LoadLevelEvent(index, config, _levelState.Grid)); }); } break; case CommandType.SubmitMatch: { var matchEvents = _levelState.TryMatchItems((command as SubmitMatchCommand)?.SelectedItems); BroadcastEvents(matchEvents); } break; case CommandType.ShuffleGrid: { var swappedEvent = _levelState.ShuffleGridItems(); BroadcastEvent(swappedEvent); } break; case CommandType.Debug_Win: { BroadcastEvent(_levelState.Debug_WinLevel()); } break; case CommandType.Debug_Lose: { BroadcastEvent(_levelState.Debug_LoseLevel()); } break; } }
public void OnClick_EditMode_Resize() { if (!int.TryParse(_editModeInputWidth.text, out var width)) { return; } if (!int.TryParse(_editModeInputHeight.text, out var height)) { return; } LevelConfig.ResizeLevel(width, height, ref _levelConfig); LevelConfig.ExportLevelFile(_levelIndex, _levelConfig); // reload the level Level.ExecuteCommand(new Level.LoadLevelCommand(_levelIndex)); }
public static LevelConfig CreateDefaultLevel(int width, int height) { string defaultCategory = GameConfig.GetAllGridItemCategories().First(); var defaultMatchRule = GameConfig.AllMatchRules.First(rule => rule.Category == defaultCategory && rule.IsDefault); var config = new LevelConfig { width = width, height = height, layout = new GridNodeLayout[width * height], category = defaultCategory, excludeItemIds = new string[] {}, matchRules = defaultMatchRule.ID, goalType = LevelGoalType.Points, goalItemId = null, goal1 = 3000, goal2 = 7000, goal3 = 10000, challengeType = LevelChallengeType.Moves, challengeValue = 10 }; var defaultGridItem = GameConfig.GetDefaultLayoutGridItem(config.category); var openNode = GameConfig.AllGridNodes.First(node => node.IsOpen); for (int y = 0; y < config.height; y++) { for (int x = 0; x < config.width; x++) { config.SetNodeLayout(x, y, new GridNodeLayout() { nodeId = openNode.ID, itemId = defaultGridItem.ID }); } } return(config); }
public LevelState(int levelIndex, LevelConfig config) { LevelIndex = levelIndex; Config = config; Grid = null; var includedItems = GameConfig.GetAllGridItemsInCategory(config.category, config.excludeItemIds); GridItemDrops = new GridItemDropDistribution(config.category, includedItems); if (config.width > 0 && config.height > 0) { var defaultGridItem = GameConfig.GetDefaultLayoutGridItem(config.category); // initialize the grid Grid = new GridNodeState[config.width, config.height]; for (int y = 0; y < config.height; y++) { for (int x = 0; x < config.width; x++) { var layout = config.GetNodeLayout(x, y); string itemId = null; if (!string.IsNullOrEmpty(layout.itemId)) { if (layout.itemId != defaultGridItem.ID) { itemId = layout.itemId; } else { itemId = GridItemDrops.Next(); } } Grid[x, y] = new GridNodeState(x, y, layout.nodeId, itemId); } } } }
public void ShowScore(Level.LevelWinEvent winEvent, LevelConfig levelConfig) { _numStars = winEvent.Stars; _scoreText.text = $"{winEvent.Points:n0}"; _movesText.text = $"{winEvent.Moves}"; _bestScore.SetActive(winEvent.Points >= winEvent.BestPoints); _bestMoves.SetActive(winEvent.Moves < winEvent.BestMoves || (winEvent.Moves == winEvent.BestMoves && winEvent.Moves < levelConfig.challengeValue)); _starsAnimator.SetInteger(StarsInteger, winEvent.Stars); if (winEvent.Stars == 1) { _winText.text = "NO MOVES LEFT!"; } else if (winEvent.Stars == 2) { _winText.text = "AMAZING!"; } else { _winText.text = "YOU WIN!"; } }
public void OnClick_EditMode_Save() { if (!GameConfig.AllMatchRules.Any(rule => rule.ID == _editModeTextMatchRules.text)) { MessagePopup.ShowMessage($"Invalid Match Rules: '{_editModeTextMatchRules.text}'"); return; } var allCategories = GameConfig.GetAllGridItemCategories(); if (!allCategories.Contains(_editModeTextCategory.text)) { MessagePopup.ShowMessage($"Invalid Category: '{_editModeTextCategory.text}'"); return; } if (!int.TryParse(_editModeInputMaxItemTypes.text, out var maxItemTypes)) { MessagePopup.ShowMessage($"Invalid Max Item Types: '{_editModeInputMaxItemTypes.text}'"); return; } if (!Enum.TryParse(_editModeTextGoalType.text, out LevelGoalType goalType)) { MessagePopup.ShowMessage($"Invalid Goal Type: '{_editModeTextGoalType.text}'"); return; } string goalItemId = null; if (!string.IsNullOrEmpty(_editModeInputGoalItem.text)) { goalItemId = _editModeInputGoalItem.text; if (GameConfig.GetGridItem(goalItemId).ID == null) { MessagePopup.ShowMessage($"Invalid Goal Item ID: '{_editModeInputGoalItem.text}'"); return; } } if (!int.TryParse(_editModeInputGoal1.text, out var goal1)) { MessagePopup.ShowMessage($"Invalid Goal #1: '{_editModeInputGoal1.text}'"); return; } if (!int.TryParse(_editModeInputGoal2.text, out var goal2)) { MessagePopup.ShowMessage($"Invalid Goal #2: '{_editModeInputGoal2.text}'"); return; } if (!int.TryParse(_editModeInputGoal3.text, out var goal3)) { MessagePopup.ShowMessage($"Invalid Goal #3: '{_editModeInputGoal3.text}'"); return; } if (!Enum.TryParse(_editModeTextChallengeType.text, out LevelChallengeType challengeType)) { MessagePopup.ShowMessage($"Invalid Challenge Type: '{_editModeTextChallengeType.text}'"); return; } if (!int.TryParse(_editModeInputChallengeValue.text, out var moves) || moves < 1) { MessagePopup.ShowMessage($"Invalid Max Moves: '{_editModeInputChallengeValue.text}'"); return; } _levelConfig.category = _editModeTextCategory.text; _levelConfig.excludeItemIds = EditMode_ConvertMaxItemTypesToExcludedItemIds(_editModeTextCategory.text, Mathf.Max(0, maxItemTypes)); _levelConfig.matchRules = _editModeTextMatchRules.text; _levelConfig.goalType = goalType; _levelConfig.goalItemId = goalItemId; _levelConfig.goal1 = Mathf.Max(0, goal1); _levelConfig.goal2 = Mathf.Max(0, goal2); _levelConfig.goal3 = Mathf.Max(0, goal3); _levelConfig.challengeType = challengeType; _levelConfig.challengeValue = moves; foreach (var gridNode in _gridNodeInstances) { var gridItem = TryGetGridItem(gridNode.Index); var nodeLayout = _levelConfig.GetNodeLayout(gridNode.Index); nodeLayout.nodeId = gridNode.ID; nodeLayout.itemId = (gridItem != null && gridNode.GridNodeConfig.IsOpen) ? gridItem.ID : null; _levelConfig.SetNodeLayout(gridNode.Index, nodeLayout); } LevelConfig.ExportLevelFile(_levelIndex, _levelConfig); }