/// <summary> /// Function to select a target for the tank. /// It requires at least 2 buildings from a neighbourhood to be eligible. /// </summary> /// <returns></returns> private IVisualizedObject SelectTarget() { List <NeighbourhoodModel> randomNeighbourhoodQuery = CityManager.Instance.GameModel.Neighbourhoods .Where(neighbourhoodModel => neighbourhoodModel.VisualizedObjects.Count(visualizedObject => visualizedObject is VisualizedBuildingModel) >= _minimumBuildings).ToList(); if (TeamManager.Instance.SelectedTeam != null) { randomNeighbourhoodQuery = randomNeighbourhoodQuery.Where(x => x.Team == TeamManager.Instance.SelectedTeam).ToList(); } // No target found. Tank is probably going in standby mode now. if (randomNeighbourhoodQuery.Count == 0) { _foundTargets = false; return(null); } // Pick a random neighbourhood from the list NeighbourhoodModel randomNeighbourhoodModel = randomNeighbourhoodQuery.PickRandom(); // We found a target and it's a building. Return the target. IVisualizedObject obj = randomNeighbourhoodModel.VisualizedObjects .First(visualizedObject => visualizedObject.GameObject != null && visualizedObject is VisualizedBuildingModel); _foundTargets = true; return(obj); }
/// <summary> /// Function to spawn a new building /// </summary> /// <param name="tile"></param> /// <param name="size"></param> /// <param name="age"></param> /// <param name="neighbourhood"></param> /// <param name="staging"></param> /// <returns></returns> public GameObject SpawnBuilding(Tile tile, int size, int age, NeighbourhoodModel neighbourhood, bool staging) { // Default rotation Quaternion buildingRotation = Quaternion.Euler(0, 90f, 0); GameObject building; if (staging) { building = AssetsManager.Instance.GetPredefinedPrefab(AssetsManager.PrefabType.StagingBuilding); } else { // Extract the tuple with the building prefab and the rotation (GameObject buildingObject, float rotation) = AssetsManager.Instance.GetBuildingPrefab(size, age); buildingRotation = Quaternion.Euler(0, rotation, 0); building = buildingObject; } // Spawn the prefab with the correct location and rotation building = Instantiate(building, new Vector3(tile.X, 0.5f, tile.Z), buildingRotation); // Set the name of the object so we can use it later as a reference building.name = $"neighbourhood-{neighbourhood.Name}"; return(building); }
/// <summary> /// Function to add a new visualized object to the game. /// </summary> /// <param name="neighbourhoodName"></param> /// <param name="newVisualizedObject"></param> private void AddBuilding(string neighbourhoodName, IVisualizedObject newVisualizedObject) { // We only care about buildings in this manager if (!(newVisualizedObject is IVisualizedBuilding)) { return; } NeighbourhoodModel neighbourhood = GameModel.Neighbourhoods.SingleOrDefault(x => x.Name == neighbourhoodName); if (neighbourhood == null) { Debug.LogWarning($"Adding object went wrong! Neighbourhood {neighbourhoodName} does not exists!"); return; } // Check if there are buildings with the same identifier. List <IVisualizedBuilding> existingVisualizedObjects = neighbourhood.VisualizedObjects .OfType <IVisualizedBuilding>().Where(x => x.Identifier == newVisualizedObject.Identifier) .ToList(); // Destroy every building that has the same identifier. foreach (IVisualizedBuilding visualizedObject in existingVisualizedObjects) { GridManager.Instance.DestroyBuilding(visualizedObject); neighbourhood.VisualizedObjects.Remove(visualizedObject); } // TODO: We actually need to check if there are destroyed grass tiles, but haven't found a solution for it yet. neighbourhood.VisualizedObjects.Add(newVisualizedObject); GridManager.Instance.DestroyBlock(neighbourhood, false); GridManager.Instance.SpawnNeighbourhood(neighbourhood); }
/// <summary> /// Function to parse all the JSON data and convert it into a GameModel object. /// If API structure changes happen it should be adjusted in here. /// </summary> /// <param name="jsonString"></param> /// <returns></returns> public static GameModel ParseGameModel(string jsonString) { HashSet <NeighbourhoodModel> neighbourhoodModels = new HashSet <NeighbourhoodModel>(); HashSet <string> teams = new HashSet <string>(); JSONNode jsonOBj = JSON.Parse(jsonString); // Loop through all layers List <VisualLayerModel> visualLayerModels = jsonOBj["layers"].Children .Select(layer => new VisualLayerModel { Icon = layer["icon"], Name = layer["layerType"] }).ToList(); // Loop through neighboorhoods foreach (JSONNode neighbourhoodJsonObj in jsonOBj["neighbourhoods"].Children) { NeighbourhoodModel neighbourhood = ParseNeighbourhoodModel(neighbourhoodJsonObj); neighbourhoodModels.Add(neighbourhood); } // Loop through teams foreach (JSONNode team in jsonOBj["teams"]) { teams.Add(team); } GameModel gameModel = new GameModel { Neighbourhoods = neighbourhoodModels, Layers = visualLayerModels, Teams = teams }; return(gameModel); }
/// <summary> /// Function to highlight vehicles when clicked. /// </summary> /// <param name="clickedObject"></param> public void HighlightVehicle(GameObject clickedObject) { InfoPanel.gameObject.SetActive(true); try { string infoText = string.Empty; if (clickedObject.CompareTag("Tank")) { TankNavigator tankNavigator = clickedObject.GetComponent <TankNavigator>(); if (tankNavigator.IsStandby || tankNavigator.Target == null) { infoText = "Tank is in stand-by mode"; } else { NeighbourhoodModel neighbourhoodModel = CityManager.Instance.GameModel.Neighbourhoods .Select(x => x).SingleOrDefault(x => x.VisualizedObjects.Contains(tankNavigator.Target)); string firingEnabled = tankNavigator.IsFiringEnabled ? "yes" : "no"; if (neighbourhoodModel != null) { infoText = $"Target: {neighbourhoodModel.Name}\r\nFiring enabled: {firingEnabled}"; } } } else { TrafficManager.Vehicle v = TrafficManager.Instance.Vehicles.Single(x => x.VehicleGameObject == clickedObject); // Check if vehicle is visible if (clickedObject.GetComponentsInChildren <Renderer>().Any(x => !x.enabled)) { return; } infoText = v.NeighbourhoodModel == null ? "Vehicle driving towards deleted neighbourhood" : $"Vehicle driving towards {v.NeighbourhoodModel.Name}"; } InfoPanel.GetComponentInChildren <TMP_Text>().text = infoText; NavMeshAgent agent = clickedObject.GetComponent <NavMeshAgent>(); if (agent != null) { CameraController.Instance.FollowTarget(clickedObject, agent.speed * 0.1f); } Renderer[] renderComponents = clickedObject.GetComponentsInChildren <Renderer>(); foreach (Renderer renderComponent in renderComponents) { _clickedObjects.Add(renderComponent, renderComponent.material.color); renderComponent.material.color = Color.green; } } catch (Exception e) { Debug.LogError($"[OnObjectClickManager] {e.Message}"); } }
public Vehicle(GameObject vehicleGameObject, VisualizedVehicleModel vehicleModel, NeighbourhoodModel neighbourhoodModel, string vehicleName) { VehicleGameObject = vehicleGameObject; VehicleModel = vehicleModel; NeighbourhoodModel = neighbourhoodModel; VehicleName = vehicleName; }
private void SetMaterials(NeighbourhoodModel neighbourhood, bool fade) { foreach (IVisualizedObject visualizedObject in neighbourhood.VisualizedObjects.Where(visualizedObject => visualizedObject.GameObject != null)) { SetMaterials(visualizedObject.GameObject, fade); } }
/// <summary> /// Function to add a new neighbourhood to the game and spawn them. /// </summary> /// <param name="newNeighbourhood"></param> private void AddNeighbourhood(NeighbourhoodModel newNeighbourhood) { if (GameModel.Neighbourhoods.Any(neighbourhoodModel => neighbourhoodModel.Name != newNeighbourhood.Name)) { GridManager.Instance.SpawnNeighbourhood(newNeighbourhood); GameModel.Neighbourhoods.Add(newNeighbourhood); } }
/// <summary> /// Function to highlight a neighbourhood when clicked. /// </summary> /// <param name="clickedObject"></param> /// <param name="showInfoPanel"></param> public void HighlightNeighbourhood(GameObject clickedObject, bool showInfoPanel = true) { if (showInfoPanel) { string neighbourhoodName = clickedObject.name.Replace("neighbourhood-", ""); string infoText = $"Neighbourhood: {neighbourhoodName}"; try { if (clickedObject.CompareTag("Building")) { NeighbourhoodModel neighboorhoud = CityManager.Instance.GameModel.Neighbourhoods.SingleOrDefault( (x => x.Name == neighbourhoodName)); if (neighboorhoud != null) { IVisualizedObject visualizedObject = neighboorhoud.VisualizedObjects.SingleOrDefault(x => x.GameObject == clickedObject); _selectedObject = visualizedObject; string days = neighboorhoud.Age == 1 ? "day" : "days"; infoText = $"Neighbourhood: {neighbourhoodName}\r\nAge: {neighboorhoud.Age} {days}"; VisualLayerModel selectedLayer = LayerManager.Instance.SelectedLayer; if (visualizedObject is IVisualizedBuilding visualizedBuilding) { DestroyButton.SetActive(true); OpenButton.SetActive(true); if (selectedLayer != null) { // Only enable destroy button for buildings infoText = $"Neighbourhood: {neighbourhoodName}\r\n{selectedLayer.Name.Replace("Layer", "")}: {visualizedBuilding.LayerValues[selectedLayer.Name]} / {neighboorhoud.LayerValues.Single(x => x.LayerType == selectedLayer.Name).MaxValue} "; } } } } } catch (Exception e) { Debug.LogError($"[OnObjectClickManager] {e.Message}"); } finally { InfoPanel.GetComponentInChildren <TMP_Text>().text = infoText; InfoPanel.gameObject.SetActive(true); } } Renderer[] renderComponents = clickedObject.GetComponentsInChildren <Renderer>(); foreach (Renderer renderComponent in renderComponents) { _clickedObjects.Add(renderComponent, renderComponent.material.color); renderComponent.material.color = Color.green; } }
/// <summary> /// Function to bind a neighbourhood to a button /// </summary> /// <param name="neighbourhoodModel"></param> /// <param name="button"></param> private static void SetButton(NeighbourhoodModel neighbourhoodModel, Button button) { TMP_Text text = button.GetComponentInChildren <TMP_Text>(); text.text = neighbourhoodModel.Name; button.gameObject.SetActive(true); // Remove all onclick listeners and create a new one button.onClick.RemoveAllListeners(); button.onClick.AddListener(() => { OnButtonClick(neighbourhoodModel); }); }
/// <summary> /// Function to remove the first building of a neighbourhood. This only happens in-game, no API calls involved! /// </summary> /// <param name="serviceName"></param> public void RemoveBuilding(string serviceName) { NeighbourhoodModel neighbourhood = CityManager.Instance.GameModel.Neighbourhoods.FirstOrDefault(x => x.Name == serviceName); IVisualizedBuilding building = (IVisualizedBuilding)neighbourhood?.VisualizedObjects.FirstOrDefault(x => x is IVisualizedBuilding); if (building != null) { ApiManager.Instance.ApiUpdateEvent?.Invoke(new UpdateEventModel { RemovedVisualizedObject = building.Identifier }); } }
/// <summary> /// Function to search for a neighbourhood. /// </summary> /// <param name="serviceName"></param> public void SearchNeighbourhood(string serviceName) { NeighbourhoodModel search = CityManager.Instance.GameModel.Neighbourhoods .FirstOrDefault(x => x.Name.Contains(serviceName)); IVisualizedObject randomContainer = search?.VisualizedObjects.FirstOrDefault(x => x.GameObject != null); if (randomContainer == null || randomContainer.GameObject == null) { return; } CameraController.Instance.FocusOnTarget(randomContainer.GameObject.transform.position); OnObjectClickManager.Instance.ResetHighlighting(); OnObjectClickManager.Instance.HighlightNeighbourhood(randomContainer.GameObject); }
/// <summary> /// Function that is executed when a button is pressed. /// The player will focus with the camera on the first building of a neighbourhood /// </summary> /// <param name="neighbourhoodModel"></param> private static void OnButtonClick(NeighbourhoodModel neighbourhoodModel) { IVisualizedObject visualizedBuilding = neighbourhoodModel.VisualizedObjects.FirstOrDefault(x => x is IVisualizedBuilding && x.GameObject != null); if (visualizedBuilding == null) { return; } CameraController.Instance.FocusOnTarget(visualizedBuilding.GameObject.transform.position); OnObjectClickManager.Instance.ResetHighlighting(); OnObjectClickManager.Instance.HighlightNeighbourhood(visualizedBuilding.GameObject); }
public void OpenSelectedObjectUrl() { if (!(_selectedObject is IVisualizedBuilding)) { return; } NeighbourhoodModel neighbourhoodModel = CityManager.Instance.GameModel.Neighbourhoods .Select(x => x).SingleOrDefault(x => x.VisualizedObjects.Contains(_selectedObject)); if (neighbourhoodModel != null) { StartCoroutine(ApiManager.Instance.OpenNeighbourhoodUrl(neighbourhoodModel.Name)); } }
/// <summary> /// Function to de-spawn and remove a complete neighbourhood. /// </summary> /// <param name="neighbourhoodName"></param> private void RemoveNeighbourhood(string neighbourhoodName) { NeighbourhoodModel removedNeighbourhood = GameModel.Neighbourhoods.SingleOrDefault(neighbourhoodModel => neighbourhoodModel.Name == neighbourhoodName); if (removedNeighbourhood != null) { // We could find one! Now let's destroy it GridManager.Instance.DestroyBlock(removedNeighbourhood, true); GameModel.Neighbourhoods.Remove(removedNeighbourhood); } else { Debug.LogWarning( $"Removing a neighbourhood went wrong! Neighbourhood {neighbourhoodName} does not exists"); } }
/// <summary> /// Function to update an existing neighbourhood. /// </summary> /// <param name="neighbourhoodName"></param> /// <param name="updatedNeighbourhood"></param> private void UpdateNeighbourhood(string neighbourhoodName, NeighbourhoodModel updatedNeighbourhood) { NeighbourhoodModel neighbourhood = GameModel.Neighbourhoods.SingleOrDefault(neighbourhoodModel => neighbourhoodModel.Name == neighbourhoodName); if (neighbourhood != null) { // Update the age and layer values of the neighbourhood neighbourhood.Age = updatedNeighbourhood.Age; neighbourhood.LayerValues = updatedNeighbourhood.LayerValues; } else { Debug.LogWarning( $"Updating neighbourhood went wrong! Neighbourhood {neighbourhoodName} does not exists!"); } }
private static NeighbourhoodModel ParseNeighbourhoodModel(JSONNode jsonNode) { NeighbourhoodModel neighbourhood = new NeighbourhoodModel(); neighbourhood.Name = jsonNode["name"]; neighbourhood.Age = jsonNode["daysOld"]; neighbourhood.Team = jsonNode["team"]; List <LayerValueModel> visualLayers = jsonNode["layerValues"].Children.Select(visualLayer => new LayerValueModel { MinValue = visualLayer["minValue"], LayerType = visualLayer["layerType"], MaxValue = visualLayer["maxValue"] }).ToList(); neighbourhood.LayerValues = visualLayers; // Loop through all visualized objects (buildings, traffic for later, etc) foreach (JSONNode visualizedObjectJson in jsonNode["visualizedObjects"].Children) { Dictionary <string, double> layerValues = new Dictionary <string, double>(); foreach (KeyValuePair <string, JSONNode> layerValue in visualizedObjectJson["layerValues"]) { layerValues.Add(layerValue.Key, layerValue.Value); } IVisualizedObject visualizedObject = VisualizedObjectFactory.Build( visualizedObjectJson["type"], visualizedObjectJson["size"], layerValues, visualizedObjectJson["identifier"]); neighbourhood.VisualizedObjects.Add(visualizedObject); } return(neighbourhood); }
/// <summary> /// Function to destroy a complete neighbourhood/block /// </summary> /// <param name="neighbourhood"></param> /// <param name="destroyEffect"></param> public void DestroyBlock(NeighbourhoodModel neighbourhood, bool destroyEffect) { foreach (IVisualizedObject visualizedObject in neighbourhood.VisualizedObjects .Where(x => !(x is VisualizedVehicleModel)).ToList()) { if (visualizedObject.GameObject == null) { continue; } // Find all grid objects that equal to the visualized object List <KeyValuePair <Tile, TileObject> > gridObjects = Grid.Where(x => x.Value != null && x.Value.GameObject != null && x.Value.GameObject.Equals(visualizedObject.GameObject)).ToList(); foreach (KeyValuePair <Tile, TileObject> gridObject in gridObjects) { if (destroyEffect) { visualizedObject.GameObject.AddComponent <DestroyGridTile>().Tile = gridObject.Key; } else { Destroy(visualizedObject.GameObject); visualizedObject.GameObject = null; Grid[gridObject.Key] = null; } } // Remove all grass tiles from a neighbourhood that is being destroyed if (visualizedObject is VisualizedGrassTileModel) { neighbourhood.VisualizedObjects.Remove(visualizedObject); } } Debug.Log($"[GridManager] Removed block of neighbourhood: {neighbourhood.Name}"); }
/// <summary> /// Function to remove a visualized object from the game. Vehicles are excluded. /// </summary> /// <param name="neighbourhoodName"></param> /// <param name="identifier"></param> private void RemoveVisualizedBuildings(string neighbourhoodName, string identifier) { NeighbourhoodModel neighbourhood = GameModel.Neighbourhoods.SingleOrDefault(neighbourhoodModel => neighbourhoodModel.Name == neighbourhoodName); // Make a list of buildings we need to remove. We don't want to remove vehicles in this manager. List <IVisualizedBuilding> removedBuildings = neighbourhood?.VisualizedObjects.OfType <IVisualizedBuilding>().Where( x => x.Identifier == identifier).ToList(); if (removedBuildings != null) { foreach (IVisualizedBuilding removedBuilding in removedBuildings) { GridManager.Instance.DestroyBuilding(removedBuilding); neighbourhood.VisualizedObjects.Remove(removedBuilding); } } else { Debug.LogWarning($"Removing object went wrong! Neighbourhood {neighbourhoodName} does not exists."); } }
/// <summary> /// Function to build a neighbourhood /// </summary> /// <param name="neighbourhoodModel"></param> public void SpawnNeighbourhood(NeighbourhoodModel neighbourhoodModel) { // If there are no visualized objects, destroy the block if (neighbourhoodModel.VisualizedObjects.Count < 1) { DestroyBlock(neighbourhoodModel, false); return; } // Make a list of buildings List <IVisualizedBuilding> visualizedObjects = neighbourhoodModel.VisualizedObjects.OfType <IVisualizedBuilding>().ToList(); // Calculate total tile count we need to spawn this building (if building is wider than the TileSize) int tileCount = GetTotalTilesRequired(visualizedObjects, neighbourhoodModel.Age) + 1; // Find the amount of tiles we need to spawn this neighbourhood List <Tile> tiles = FindTiles(tileCount); if (tiles.Count != tileCount) { // Can't spawn the neighbourhood. Not enough tiles per street. Debug.LogError( "Building block went wrong. Not enough tiles!\r\nIncrease the TilesPerStreet in the config.json"); return; } // Set spawn offset and tile index to 0 float spawnOffsetX = 0; int tileIndex = 0; // Loop through all the visualized objects for (int index = 0; index < visualizedObjects.Count; index++) { // Get the correct tile Tile tile = tiles[tileIndex]; // If the tile is already filled, destroy it. if (Grid[tile] != null && Grid[tile].GameObject != null) { Destroy(Grid[tile].GameObject); } // Spawn a building at this street GameObject building = SpawnBuilding(tile, visualizedObjects[index].Size, neighbourhoodModel.Age, neighbourhoodModel, visualizedObjects[index] is VisualizedStagingBuildingModel); // Set the correct tile information Grid[tile] = new TileObject { GameObject = building, ObjectType = ObjectType.Building }; tileIndex++; // Check how many tiles we need to spawn this building int tilesRequired = GetTilesRequiredForBuilding(building); // Check if we need more than 1 tile for this building if (tilesRequired > 1) { if (Physics.Raycast(building.transform.position, Vector3.left, out RaycastHit hit, 5)) { // Check if there is grass or a road next to us and at the distance so we can align our building if (hit.transform.gameObject.CompareTag("Grass") || hit.transform.gameObject.CompareTag("Road")) { spawnOffsetX += hit.distance; } } // We might need to move the building to align perfectly like calculated in the raycast. building.transform.position = new Vector3(building.transform.position.x + spawnOffsetX, building.transform.position.y, building.transform.position.z); for (int i = 1; i < tilesRequired; i++) { // If we need more than 1 tile fill the next tiles as well // This will only happen if the building is wider than the TileSize Tile tile2 = tiles[tileIndex]; // Destroy existing object if it's not the building we are trying to spawn if (Grid[tile2] != null && !Grid[tile2].GameObject.Equals(building)) { Destroy(Grid[tile2].GameObject); } // Set the correct tile information Grid[tile2] = new TileObject { GameObject = building, ObjectType = ObjectType.Building }; tileIndex++; } } // Set the correct game object in the visualized object of the neighbourhood so we can use it as reference neighbourhoodModel .VisualizedObjects[neighbourhoodModel.VisualizedObjects.IndexOf(visualizedObjects[index])] .GameObject = building; } // Spawn a grass patch as a divider between buildings GameObject grassPatch = Instantiate( AssetsManager.Instance.GetPredefinedPrefab(AssetsManager.PrefabType.Grass), new Vector3(tiles.Last().X, 0.5f, tiles.Last().Z), Quaternion.Euler(0, 90f, 0)); // Add the grass patch to the list of visualized objects neighbourhoodModel.VisualizedObjects.Add(new VisualizedGrassTileModel { GameObject = grassPatch }); Grid[tiles.Last()] = new TileObject { GameObject = grassPatch, ObjectType = ObjectType.Grass }; }