/// <summary> /// Creates a new collection of cursors. /// </summary> /// <param name="footprint">The footprint of the building.</param> public void Create(bool[,] footprint) { if (_cursors != null) { GameLogger.FatalError("Can't recreate a footprint cursor while one already exists!"); } int xSize = footprint.GetLength(0); int zSize = footprint.GetLength(1); _cursors = new GridCursor[xSize, zSize]; for (int x = 0; x < xSize; ++x) { for (int z = 0; z < zSize; ++z) { if (footprint[x, z]) { _cursors[x, z] = GridCursor.Create(_terrain, _validMaterial); } else { _cursors[x, z] = null; } } } }
/// <summary> /// Link actions that reference other GameObjects. /// All other GameObjects in dependent stores must already be Instantiated. /// </summary> /// <param name="data">UI GameData</param> protected override void LinkData(UIData data) { var toolbar = MainMenu.GetComponent <Toolbar>(); // Link button children and actions foreach (var buttonGroupData in data.ButtonGroups) { var buttonGroup = _buttonGroups[buttonGroupData.Name]; for (int i = 0; i < buttonGroupData.Buttons.Count; ++i) { var buttonData = buttonGroupData.Buttons[i]; var button = buttonGroup.Buttons[i]; // Link OnSelect Action ----------------------- if (buttonData.OnSelect is OpenSubMenuAction) { var openSubMenuAction = buttonData.OnSelect as OpenSubMenuAction; var openSubMenuButtons = GameDataStore.Get <ButtonGroup>(GameDataType.ButtonGroup, openSubMenuAction.ButtonGroupName); if (openSubMenuButtons == null) { GameLogger.FatalError("OpenSubMenuAction could not link to non-existant button group '{0}'", openSubMenuAction.ButtonGroupName); } button.OnSelect = () => toolbar.OpenSubMenu(openSubMenuButtons); } else if (buttonData.OnSelect is OpenWindowAction) { var openWindowAction = buttonData.OnSelect as OpenWindowAction; if (!WindowManager.TryGetWindow(openWindowAction.WindowName, out Window _)) { GameLogger.FatalError("OpenWindowAction could not link to non-existant window '{0}'", openWindowAction.WindowName); } button.OnSelect = () => WindowManager.OpenWindow(openWindowAction.WindowName, null); } else if (buttonData.OnSelect is OpenWindowWithDataAction) { var openWindowAction = buttonData.OnSelect as OpenWindowWithDataAction; if (!WindowManager.TryGetWindow(openWindowAction.WindowName, out Window _)) { GameLogger.FatalError("OpenWindowWithDataAction could not link to non-existant window '{0}'", openWindowAction.WindowName); } button.OnSelect = () => WindowManager.OpenWindow(openWindowAction.WindowName, openWindowAction.DataType, openWindowAction.DataName); } // Link OnDeselect Action ----------------------- if (buttonData.OnDeselect is CloseSubMenuAction) { button.OnDeselect = () => toolbar.CloseSubMenu(); } else if (buttonData.OnDeselect is CloseWindowAction) { button.OnDeselect = () => WindowManager.CloseWindow(); } } } }
/// <summary> /// Gets the height of a mesh vertex in grid steps. /// (Remember, there is one more vertex than grid squares). /// </summary> /// <param name="x">X coordinate of the vertex.</param> /// <param name="z">Z coordinates of the vertex.</param> /// <returns>The height of the vertex in grid steps.</returns> public int GetVertexHeight(int x, int z) { if (x < 0 || x > CountX || z < 0 || z > CountZ) { GameLogger.FatalError("Attempted to GetVertexHeight out of range. ({0},{1})", x, z); } return(_vertexHeight[x, z]); }
/// <summary> /// Gets the height of a mesh vertex in world units. /// (Remember, there is one more vertex than grid squares). /// </summary> /// <param name="x">X coordinate of the vertex.</param> /// <param name="z">Z coordinates of the vertex.</param> /// <returns>The height of the vertex in Unity world units.</returns> public float GetVertexWorldHeight(int x, int z) { if (x < 0 || x > CountX || z < 0 || z > CountZ) { GameLogger.FatalError("Attempted to GetVertexWorldHeight out of range. ({0},{1})", x, z); } return(Convert.GridHeightToWorld(_vertexHeight[x, z])); }
/// <summary> /// Gets the submaterial id of the grid square. /// </summary> /// <param name="x">X coordinate of the grid square.</param> /// <param name="z">Z coordinate of the grid square.</param> /// <returns>Id of the submaterial used at the grid square.</returns> public int GetSubmaterial(int x, int z) { if (x < 0 || x >= CountX || z < 0 || z >= CountZ) { GameLogger.FatalError("Attempted to get square submaterial outside of range! ({0},{1}) is outside of ({2},{3})", x, z, CountX, CountZ); } return(_gridData[x, z].SubmaterialIndex); }
/// <summary> /// You must call this method on a GameDataLoader before it starts! /// The GameObject must be disabled while doing this step otherwise /// the Awake and Start methods will not be called correctly. /// </summary> /// <param name="config"></param> protected void SetConfig(TextAsset config) { if (gameObject.activeSelf) { GameLogger.FatalError("Setting game data configuration on an active GameObject!"); } Config = config; }
/// <summary> /// The state controller is starting. /// </summary> /// <param name="context">The construction to place.</param> public override void TransitionIn(object context) { _building = context as BuildingData; if (_building == null) { GameLogger.FatalError("PlacingConstructionController was not given a building data!"); } _cursors.Create(_building.Footprint); }
/// <summary> /// Gets the height of a grid square in world units. /// </summary> /// <param name="x">X coordinate of the grid square.</param> /// <param name="z">Y coordinate of the grid square.</param> /// <returns>The height of the grid square in Unity world units.</returns> public float GetSquareWorldHeight(int x, int z, int corner = Vertex.Center) { if (x < 0 || x >= CountX || z < 0 || z >= CountZ) { GameLogger.FatalError("Attempted to GetSquareWorldHeight out of range. ({0},{1})", x, z); } int centerIndex = _gridData[x, z].VertexIndex + corner; return(_vertices[centerIndex].y); }
/// <summary> /// Set the grid square to be anchored by construction. /// </summary> /// <param name="x">The x grid coordinate.</param> /// <param name="z">The z grid coordinate.</param> public void RemoveAnchor(int x, int z) { if (x < 0 || x > _terrain.CountX || z < 0 || z > _terrain.CountZ) { GameLogger.FatalError("Attempted to remove anchor from grid outside of range! ({0},{1}) is outside of ({2},{3})", x, z, _terrain.CountX, _terrain.CountZ); } Assert.IsFalse(!_gridAnchored[x, z], "Trying to remove anchor from grid that is already free!"); _gridAnchored[x, z] = false; _vertexAnchored[x, z] = _vertexAnchored[x, z + 1] = _vertexAnchored[x + 1, z] = _vertexAnchored[x + 1, z + 1] = false; }
/// <summary> /// Set the grid square to be anchored by construction. /// </summary> /// <param name="x">The x grid coordinate.</param> /// <param name="z">The z grid coordinate.</param> public void SetAnchored(int x, int z) { if (x < 0 || x > _terrain.CountX || z < 0 || z > _terrain.CountZ) { GameLogger.FatalError("Attempted to anchor grid outside of range! ({0},{1}) is outside of ({2},{3})", x, z, _terrain.CountX, _terrain.CountZ); } Assert.IsFalse(_gridAnchored[x, z], "Trying to anchor a grid that is already anchored!"); _gridAnchored[x, z] = true; _vertexAnchored[x, z] = _vertexAnchored[x, z + 1] = _vertexAnchored[x + 1, z] = _vertexAnchored[x + 1, z + 1] = true; }
/// <summary> /// Gets if the grid square is flat or not. /// </summary> /// <param name="x">X coordinates of the grid square.</param> /// <param name="z">Z coorindates of the grid square.</param> /// <returns>True if the square is flat, false otherwise.</returns> public bool IsGridFlat(int x, int z) { if (x < 0 || x >= CountX || z < 0 || z >= CountZ) { GameLogger.FatalError("Attempted to get IsGridFlat out of range. ({0},{1})", x, z); } return(_vertexHeight[x, z] == _vertexHeight[x + 1, z] && _vertexHeight[x, z] == _vertexHeight[x, z + 1] && _vertexHeight[x, z] == _vertexHeight[x + 1, z + 1]); }
/// <summary> /// Set the height of a grid square. /// </summary> /// <param name="x">X coordinate of the grid square.</param> /// <param name="z">Y coordinate of the grid square.</param> /// <param name="height">The square height in grid steps.</param> public void SetSquareHeight(int x, int z, int gridHeight) { if (x < 0 || x >= CountX || z < 0 || z >= CountZ) { GameLogger.FatalError("Attempted to set square height outside of range! ({0},{1}) is outside of ({2},{3})", x, z, CountX, CountZ); } SetVertexHeights(x, z, new int[, ] { { gridHeight, gridHeight }, { gridHeight, gridHeight } }); }
/// <summary> /// Boldly retrieve a color that you are certain exists. /// Throws exception if the color doesn't exist. /// </summary> /// <param name="name">Name of the color.</param> /// <returns>The color. Or an exception to the face.</returns> public static Color GetColor(string name) { Color color = Color.magenta; // default to a garish color if (!Palette.TryGetValue(name, out color)) { GameLogger.FatalError("Configuration attempted to load unrecognized color name '{0}'", name); } return(color); }
/// <summary> /// The state controller is starting. /// </summary> /// <param name="context">The construction to place.</param> public override void TransitionIn(object context) { var args = context as TerrainClickedArgs; if (args == null) { GameLogger.FatalError("EditingTerrainController was given incorrect context."); } _pathStart = _pathEnd = args.ClickLocation; _cursors.Place(_pathStart, _pathEnd, IsValidTerrainAlongLine()); }
public static UnityEngine.Object Load(ResourceType type, ResourceCategory category, string name, Type returnType) { string resourcePath = string.Format("{0}/{1}/{2}", type.ToString(), category.ToString(), name); UnityEngine.Object resource = Resources.Load(resourcePath, returnType); if (resource == null) { GameLogger.FatalError("ResourceLoader could not find resource {0}", resourcePath); } return(resource); }
/// <summary> /// Open the window to display the game data. /// </summary> /// <param name="data">The game data</param> public override void Open(object data) { var buildingData = data as BuildingData; if (buildingData == null) { GameLogger.FatalError("ConstructionInfoWindow was passed invalid data. Data = {0}", data == null ? "null" : data.GetType().Name); } Title = buildingData.Name; Description = WriteDescription(buildingData); ConstructionImage.sprite = buildingData.Icon; BuildButton.OnSelect = () => { Game.UI.WindowManager.OpenWindow("ConstructionPlacing", buildingData); }; }
/// <summary> /// Open the window to display the game data. /// </summary> /// <param name="data">The game data</param> public override void Open(object data) { var buildingData = data as BuildingData; if (buildingData == null) { GameLogger.FatalError("ConstructionPlacingWindow was passed invalid data. Data = {0}", data == null ? "null" : data.GetType().Name); } Game.Campus.Terrain.Selectable.SelectionParent = this; Game.State.StartDoing(GameState.PlacingConstruction, data); TitleText.text = string.Format("Constructing {0}", buildingData.Name); StopButton.OnSelect = () => { SelectionManager.UpdateSelection(SelectionParent.ToMainMenu()); }; }
/// <summary> /// Transition to SelectingTerrain state. /// </summary> /// <param name="context">The grid coordinate to edit.</param> public override void TransitionIn(object context) { var args = context as TerrainClickedArgs; if (args == null) { GameLogger.FatalError("EditingTerrainController was given incorrect context."); } _editingGridLocation = args.ClickLocation; _cursor.Activate(); _cursor.Place(_editingGridLocation.x, _editingGridLocation.z); _mouseDragStartY = Input.mousePosition.y; _mouseDragHeightChange = 0; }
/// <summary> /// Sets the submaterial on a grid square. /// </summary> /// <param name="x">X coordinate of the grid square.</param> /// <param name="z">Z coordinate of the grid square.</param> /// <param name="submaterialId">The id of the submaterial (the gridsheet on the material from left->right, top->bottom).</param> public void SetSubmaterial(int x, int z, int submaterialId, Rotation rotation = Rotation.deg0) { if (x < 0 || x >= CountX || z < 0 || z >= CountZ) { GameLogger.FatalError("Attempted to set square material outside of range! ({0},{1}) is outside of ({2},{3})", x, z, CountX, CountZ); } int submaterialOffsetX = submaterialId % _submaterialCountX; int submaterialOffsetZ = submaterialId / _submaterialCountX; if (submaterialOffsetZ >= _submaterialCountZ) { throw new InvalidOperationException(string.Format("Submaterial index '{0}' is out of range for material {1} ({2}x{3}).", submaterialId, _material.name, _submaterialCountX, _submaterialCountZ)); } float stepX = (1.0f / _submaterialCountX); float stepZ = (1.0f / _submaterialCountZ); int rotationOffset = 0; switch (rotation) { case Rotation.deg90: rotationOffset = 3; break; case Rotation.deg180: rotationOffset = 2; break; case Rotation.deg270: rotationOffset = 1; break; } var grid = _gridData[x, z]; _uv[grid.VertexIndex + (Vertex.BottomLeft + rotationOffset) % 4] = new Vector2(submaterialOffsetX * stepX + Constant.uvEpsilon, 1.0f - (submaterialOffsetZ + 1) * stepZ + Constant.uvEpsilon); _uv[grid.VertexIndex + (Vertex.BottomRight + rotationOffset) % 4] = new Vector2((submaterialOffsetX + 1) * stepX - Constant.uvEpsilon, 1.0f - (submaterialOffsetZ + 1) * stepZ + Constant.uvEpsilon); _uv[grid.VertexIndex + (Vertex.TopRight + rotationOffset) % 4] = new Vector2((submaterialOffsetX + 1) * stepX - Constant.uvEpsilon, 1.0f - submaterialOffsetZ * stepZ - Constant.uvEpsilon); _uv[grid.VertexIndex + (Vertex.TopLeft + rotationOffset) % 4] = new Vector2(submaterialOffsetX * stepX + Constant.uvEpsilon, 1.0f - submaterialOffsetZ * stepZ - Constant.uvEpsilon); _uv[grid.VertexIndex + Vertex.Center] = new Vector2(submaterialOffsetX * stepX + (stepX / 2), 1.0f - submaterialOffsetZ * stepZ - (stepZ / 2)); grid.SubmaterialIndex = submaterialId; _mesh.uv = _uv; }
/// <summary> /// Bootstrap the game state and data. /// </summary> protected void Awake() { InitLogging(); GameLogger.Info("Game started."); lock (_singletonLock) { if (_singleton != null) { GameLogger.FatalError("It appears there are multiple root Game objects."); } _singleton = this; } GameLogger.Info("Creating game objects."); InitGameObjects(); }
/// <summary> /// Unity's Awake method. /// </summary> protected virtual void Awake() { try { GameData = GameDataSerializer.Load <T>(Config); } catch (Exception e) { GameLogger.FatalError("Failed to load game data for {0}. Ex = {1}", GetType().Name, e); return; } if (GameData == null) { GameLogger.FatalError("Null game data for {0}.", GetType().Name); return; } LoadDataInternal(GameData); LoadData(GameData); }
/// <summary> /// Opens a window on the screen with custom (possibly null) data. /// </summary> /// <param name="name">Name of the window to open.</param> /// <param name="data">The data to pass to the window.</param> public void OpenWindow(string name, object data) { Window window = null; if (!_windows.TryGetValue(name, out window)) { GameLogger.FatalError("Attempted to open non-existant window '{0}'", name); return; } CloseWindow(); var selected = SelectionManager.Selected; if (selected != null) { window.SelectionParent = selected; } _openWindow = window; window.Open(data); window.gameObject.SetActive(true); }
/// <summary> /// Set grid squares as anchored by construction. /// </summary> /// <param name="xBase">The starting x coordinate.</param> /// <param name="zBase">The starting z coordinate.</param> /// <param name="anchorGrid">Array of grid squares to set as anchored.</param> public void SetAnchoredGrid(int xBase, int zBase, bool[,] anchorGrid) { int xSize = anchorGrid.GetLength(0); int zSize = anchorGrid.GetLength(1); if (xBase < 0 || xBase + xSize > _terrain.CountX || zBase < 0 || zBase + zSize > _terrain.CountZ) { GameLogger.FatalError("Attempted to anchor grid outside of range! ({0},{1}) + ({2},{3}) is outside of ({4},{5})", xBase, zBase, xSize, zSize, _terrain.CountX, _terrain.CountZ); } for (int x = 0; x < xSize; ++x) { for (int z = 0; z < zSize; ++z) { int gridX = xBase + x; int gridZ = zBase + z; if (anchorGrid[x, z]) { SetAnchored(gridX, gridZ); } } } }
/// <summary> /// Free anchored grid squares. /// </summary> /// <param name="xBase">The starting x coordinate.</param> /// <param name="zBase">The starting z coordinate.</param> /// <param name="freeGrids">Array of grid squares to set as free.</param> public void RemoveAnchorGrid(int xBase, int zBase, bool[,] freeGrids) { int xSize = freeGrids.GetLength(0); int zSize = freeGrids.GetLength(1); if (xBase < 0 || xBase + xSize > _terrain.CountX || zBase < 0 || zBase + zSize > _terrain.CountZ) { GameLogger.FatalError("Attempted to remove anchor from outside of range! ({0},{1}) + ({2},{3}) is outside of ({4},{5})", xBase, zBase, xSize, zSize, _terrain.CountX, _terrain.CountZ); } for (int x = 0; x < xSize; ++x) { for (int z = 0; z < zSize; ++z) { int gridX = xBase + x; int gridZ = zBase + z; if (freeGrids[x, z]) { RemoveAnchor(gridX, gridZ); } } } }
/// <summary> /// Set the heights of several vertices in the grid. /// </summary> /// <param name="xBase">The starting x coordinate.</param> /// <param name="zBase">The starting z coordinate.</param> /// <param name="heights">The square heights in grid steps.</param> public void SetVertexHeights(int xBase, int zBase, int[,] gridHeights) { // Reminder: vertices are one larger than square grid // the bounds of this method are [0, CountX] [0, CountZ] inclusive int xLength = gridHeights.GetLength(0); int zLength = gridHeights.GetLength(1); if (xBase < 0 || xBase + xLength > CountX + 1 || zBase < 0 || zBase + zLength > CountZ + 1) { GameLogger.FatalError("Attempted to set vertex height outside of range! ({0},{1}) + ({2},{3}) is outside of ({4},{5})", xBase, zBase, xLength, zLength, CountX + 1, CountZ + 1); } // 1st pass: set the corner vertices for (int x = 0; x < xLength; ++x) { for (int z = 0; z < zLength; ++z) { _vertexHeight[xBase + x, zBase + z] = gridHeights[x, z]; float worldHeight = Convert.GridHeightToWorld(gridHeights[x, z]); // there are potentially 4 overlaps for each corner vertex. // keep them all in sync! int leftX = xBase + x - 1; int rightX = xBase + x; int downZ = zBase + z - 1; int upZ = zBase + z; if (leftX >= 0 && downZ >= 0) { int index = _gridData[leftX, downZ].VertexIndex + Vertex.TopRight; _vertices[index].y = worldHeight; } if (leftX >= 0 && upZ < CountZ) { int index = _gridData[leftX, upZ].VertexIndex + Vertex.BottomRight; _vertices[index].y = worldHeight; } if (rightX < CountX && downZ >= 0) { int index = _gridData[rightX, downZ].VertexIndex + Vertex.TopLeft; _vertices[index].y = worldHeight; } if (rightX < CountX && upZ < CountZ) { int index = _gridData[rightX, upZ].VertexIndex + Vertex.BottomLeft; _vertices[index].y = worldHeight; } } } // 2nd pass: set the center vertices for (int x = -1; x < xLength + 1; ++x) { for (int z = -1; z < zLength + 1; ++z) { int gridX = xBase + x; int gridZ = zBase + z; if (gridX >= 0 && gridX < CountX && gridZ >= 0 && gridZ < CountZ) { int index = _gridData[gridX, gridZ].VertexIndex; float newHeight = Utils.GetMajorityOrAverage( _vertices[index + Vertex.BottomLeft].y, _vertices[index + Vertex.BottomRight].y, _vertices[index + Vertex.TopRight].y, _vertices[index + Vertex.TopLeft].y); _vertices[index + Vertex.Center].y = newHeight; } } } UpdateMesh(); }
/// <summary> /// Sets the material on a grid square. /// </summary> /// <param name="x">X coordinate of the grid square.</param> /// <param name="z">Z coordinate of the grid square.</param> /// <param name="materialId">The id of the material to use at the grid square.</param> /// <param name="submaterialId">The id of the submaterial (the gridsheet on the material).</param> public void SetMaterial(int x, int z, int materialId, int submaterialId = 0) { if (x < 0 || x >= CountX || z < 0 || z >= CountZ) { GameLogger.FatalError("Attempted to set square material outside of range! ({0},{1}) is outside of ({2},{3})", x, z, CountX, CountZ); } try { int oldMaterialId = _gridData[x, z].MaterialIndex; int oldTriangleIndex = _gridData[x, z].TriangleIndex; if (oldMaterialId != materialId) { List <int> oldSubmesh = _triangles[oldMaterialId]; List <int> newSubmesh = _triangles[materialId]; int newTriangleIndex = newSubmesh.Count; // Move the triangles out of the old material sub mesh and into the end of the new material sub mesh. int toMoveCount = Vertex.TrianglesPerSquare * 3; for (int i = 0; i < toMoveCount; ++i) { newSubmesh.Add(oldSubmesh[oldTriangleIndex + i]); } // Overwrite the old submesh triangle list with the last element. // Remove elements only from the end of the List. int oldSubmeshLength = oldSubmesh.Count; for (int i = 0; i < toMoveCount; ++i) { oldSubmesh[oldTriangleIndex + toMoveCount - 1 - i] = oldSubmesh[oldSubmeshLength - 1 - i]; oldSubmesh.RemoveAt(oldSubmeshLength - 1 - i); } // keep our pointers state consistent _gridDataTriLookup[materialId][newTriangleIndex] = _gridData[x, z]; _gridDataTriLookup[oldMaterialId].Remove(oldTriangleIndex); _gridData[x, z].MaterialIndex = materialId; _gridData[x, z].TriangleIndex = newTriangleIndex; int displacedTriangleIndex = oldSubmeshLength - toMoveCount; if (oldSubmesh.Count > 0 && displacedTriangleIndex != oldTriangleIndex) { GridData displacedGrid = _gridDataTriLookup[oldMaterialId][displacedTriangleIndex]; Assert.AreEqual(displacedTriangleIndex, displacedGrid.TriangleIndex, "Grid TriangleIndex reverse lookup is corrupt!!!"); displacedGrid.TriangleIndex = oldTriangleIndex; _gridDataTriLookup[oldMaterialId][oldTriangleIndex] = displacedGrid; _gridDataTriLookup[oldMaterialId].Remove(displacedTriangleIndex); } } SetSubmaterial(x, z, submaterialId); UpdateMesh(); } catch (Exception ex) { GameLogger.FatalError("Exception while updating terrain material! Ex = {0}", ex.ToString()); } }
/// <summary> /// Use reflection to load resources that have the resource attributes. /// </summary> /// <param name="gameData">The game data to load data on.</param> private void LoadDataInternal(object gameData) { PropertyInfo[] properties = gameData.GetType().GetProperties(); Dictionary <string, PropertyInfo> propertiesDictionary = properties.ToDictionary(info => info.Name); foreach (var property in properties) { var colorPalette = property.GetCustomAttribute <ColorPaletteAttribute>(); var resourceLoader = property.GetCustomAttribute <ResourceLoaderAttribute>(); // Case 1) The property has a ColorPaletteAttribute. Load the Color. if (colorPalette != null) { string colorName = string.Empty; if (!propertiesDictionary.TryGetValue(colorPalette.PropertyName, out PropertyInfo colorNameProperty)) { GameLogger.FatalError("Could not link ColorPaletteAttribute property name '{0}' to a PropertyInfo.", colorPalette.PropertyName); continue; } else { colorName = colorNameProperty.GetValue(gameData)?.ToString(); if (string.IsNullOrEmpty(colorName)) { GameLogger.FatalError("Could not load a value from ColorPaletteAttribute property '{0}'.", colorPalette.PropertyName); continue; } } Color color = ColorPalette.GetColor(colorName); property.SetValue(gameData, color); } // Case 2) The property has a ResourceLoaderAttribute. Load the Resource. else if (resourceLoader != null) { string resourceName = resourceLoader.ResourceName; if (string.IsNullOrEmpty(resourceName)) { if (!propertiesDictionary.TryGetValue(resourceLoader.PropertyName, out PropertyInfo resourceNameProperty)) { GameLogger.FatalError("Could not link ResourceLoaderAttribute property name '{0}' to a PropertyInfo.", resourceLoader.PropertyName); continue; } else { resourceName = resourceNameProperty.GetValue(gameData)?.ToString(); if (string.IsNullOrEmpty(resourceName)) { GameLogger.FatalError("Could not load a value from ResourceLoaderAttribute property '{0}'.", resourceLoader.PropertyName); continue; } } } UnityEngine.Object resource = ResourceLoader.Load(resourceLoader.Type, resourceLoader.Category, resourceName, property.PropertyType); property.SetValue(gameData, resource); } // Case 3) The type is from the GameData namespace. Recursively attempt to load data. else if (property.GetValue(gameData) != null && property.PropertyType.FullName.StartsWith("GameData")) { object propertyValue = property.GetValue(gameData); LoadDataInternal(propertyValue); } // Case 4) The type is an IEnumerable. Unspool the objects and recursively load data. else if (property.GetValue(gameData) != null && property.PropertyType.GetInterfaces().Contains(typeof(IEnumerable))) { // Case 2: Unspool an enumerable of GameData objects foreach (object item in (IEnumerable)property.GetValue(gameData, null)) { if (item.GetType().FullName.StartsWith("GameData")) { LoadDataInternal(item); } } } } }