/// <summary> /// Creates a new tile in the given direction. /// </summary> /// <param name="tile">The parallax tile object.</param> /// <param name="direction">The direction we're creating a new tile in.</param> /// <param name="historyList">The history list for that layer.</param> void CreateNewTile(OSPObject tile, TileDirection direction, OSPRandomHistory historyList) { // Grab the object pool for this layer OSPPool pool = ParallaxPools.SingleOrDefault(p => p.ObjectID == tile.ObjectID); // Sometimes, a large image will have a slight gap that shows up on camera movement. This isn't the best fix, but it works. float gapOffset = 0.05f; // Figure out the scale of the sprite so we can figure out how far to move it in order to tile float spriteScaleX = tile.transform.localScale.x; if (tile.transform.parent != null) { spriteScaleX *= tile.transform.parent.localScale.x; } float spriteScaleY = tile.transform.localScale.y; if (tile.transform.parent != null) { spriteScaleY *= tile.transform.parent.localScale.y; } if (direction == TileDirection.LEFT || direction == TileDirection.RIGHT) { #region Left/Right Tiles float posOffset = (tile.SpriteWidth * (direction == TileDirection.LEFT ? -1 : 1) * spriteScaleX); gapOffset *= direction == TileDirection.LEFT ? 1 : -1; Vector3 newPosition = new Vector3( tile.transform.position.x + posOffset + gapOffset, tile.transform.position.y, tile.transform.position.z); if (direction == TileDirection.RIGHT) { // If we're randomizing the horizontal distance, get the offset and add it to the position if (tile.RandomizeHorizontalDistance) { newPosition.x += historyList.GetXOffsetForColumn(tile.col, tile.UseRandomHistory); // If we're randomizing the Y offset, get the offset and add it to the position if (tile.RandomizeHorizontalYAxis) { newPosition.y += historyList.GetYOffsetForColumn(tile.col, tile.UseRandomHistory); } } else { newPosition.x += tile.HorizontalDistance; } } else if (direction == TileDirection.LEFT) { // If we're randomizing the horizontal distance, get the offset and add it to the position if (tile.RandomizeHorizontalDistance) { newPosition.x -= historyList.GetXOffsetForColumn(tile.col - 1, tile.UseRandomHistory); // If we're randomizing the Y offset, get the offset and add it to the position if (tile.RandomizeHorizontalYAxis) { newPosition.y += historyList.GetYOffsetForColumn(tile.col - 1, tile.UseRandomHistory); } } else { newPosition.x -= tile.HorizontalDistance; } } // Get the new tile from the object pool. Abandon ship if it couldn't get a tile. var newTile = pool.GetPooledObject(); if (newTile == null) { return; } newTile.transform.position = newPosition; newTile.transform.rotation = tile.transform.rotation; // Set the new column for this tile if (direction == TileDirection.RIGHT) { newTile.row = tile.row; newTile.col = tile.col + 1; } else { newTile.row = tile.row; newTile.col = tile.col - 1; } // Set the left, right, top, and bottom tiles that connect to this one pool.SetSiblings(newTile); // Now that we're in position, set this tile active newTile.gameObject.SetActive(true); #endregion } else { #region Up/Down Tiles float posOffset = (tile.SpriteHeight * (direction == TileDirection.DOWN ? -1 : 1) * spriteScaleY); gapOffset *= direction == TileDirection.DOWN ? 1 : -1; Vector3 newPosition = new Vector3( tile.transform.position.x, tile.transform.position.y + posOffset + gapOffset, tile.transform.position.z); if (direction == TileDirection.UP) { // If we're randomizing the vertical distance, get the offset and add it to the position if (tile.RandomizeVerticalDistance) { newPosition.y += historyList.GetYOffsetForRow(tile.row, tile.UseRandomHistory); // If we're randomizing the X offset, get the offset and add it to the position if (tile.RandomizeVerticalXAxis) { newPosition.x += historyList.GetXOffsetForRow(tile.row, tile.UseRandomHistory); } } else { newPosition.y += tile.VerticalDistance; } } else if (direction == TileDirection.DOWN) { // If we're randomizing the vertical distance, get the offset and add it to the position if (tile.RandomizeVerticalDistance) { newPosition.y -= historyList.GetYOffsetForRow(tile.row - 1, tile.UseRandomHistory); // If we're randomizing the X offset, get the offset and add it to the position if (tile.RandomizeVerticalXAxis) { newPosition.x += historyList.GetXOffsetForRow(tile.row - 1, tile.UseRandomHistory); } } else { newPosition.y -= tile.VerticalDistance; } } // Get the new tile from the object pool. Abandon ship if it couldn't get a tile. var newTile = pool.GetPooledObject(); if (newTile == null) { return; } newTile.transform.position = newPosition; newTile.transform.rotation = tile.transform.rotation; // Set the new row for this tile if (direction == TileDirection.DOWN) { newTile.row = tile.row + 1; newTile.col = tile.col; } else { newTile.row = tile.row - 1; newTile.col = tile.col; } // Set the left, right, top, and bottom tiles that connect to this one pool.SetSiblings(newTile); // Now that we're in position, set this tile active newTile.gameObject.SetActive(true); #endregion } }
/// <summary> /// Initializes everything. /// </summary> void Start() { prevCameraPosition = MainCamera.transform.position; TilesToRemove = new List <OSPObject>(); PlacementHistory = new List <OSPRandomHistory>(); ParallaxPools = new List <OSPPool>(); // Grab the camera's bounds CamBounds = MainCamera.OrthographicBounds(); // Loops through the parallax layers and assigns an ID to each one, finds the maximum Z distance from the camera, // and sets the object pool amount. // This loop is mainly to determine the maximum Z distance because our parallax speed doe each layer depends on it. for (int i = 0; i < ParallaxObjects.Count; ++i) { var obj = ParallaxObjects[i]; obj.ObjectID = i; if (GetDistance(obj.transform.position.z, MainCamera.transform.position.z) > maxZ) { maxZ = obj.transform.position.z - MainCamera.transform.position.z; } obj.DeterminePoolAmount(CamBounds); } // Store the previous random state so we can go back to it after setting our seed in case your game has another seeded random state. Random.State oldState = Random.state; if (UseSeedForRandomization) { Random.InitState(RandomSeed); } // Loop through the parallax objects again foreach (var obj in ParallaxObjects) { // Set the parallax speed for the layer obj.ParallaxScalar = GetParallaxScalar(obj); // If we're randomizing anything, we create a new history object and initialize the offset values if (obj.RandomizeHorizontalDistance || obj.RandomizeVerticalDistance) { var newHist = new OSPRandomHistory { ID = obj.ObjectID, MaxHistorySize = obj.MaxPlacementHistorySize, UseRandomX = obj.RandomizeHorizontalDistance, UseRandomY = obj.RandomizeVerticalDistance, UseRandomHorizontalY = obj.RandomizeHorizontalYAxis, UseRandomVerticalX = obj.RandomizeVerticalXAxis, MinXOffset = obj.HorizontalDistanceMin, MaxXOffset = obj.HorizontalDistanceMax, MinYOffset = obj.VerticalDistanceMin, MaxYOffset = obj.VerticalDistanceMax, MinVerticalXOffset = obj.VerticalXMin, MaxVerticalXOffset = obj.VerticalXMax, MinHorizontalYOffset = obj.HorizontalYMin, MaxHorizontalYOffset = obj.HorizontalYMax }; // Only initialize the offset lists if we're actually storing history if (obj.UseRandomHistory) { newHist.InitializeOffsetHistory(); } PlacementHistory.Add(newHist); } // Create object pools for each layer and add them to the pool list if (obj.AutoTileX || obj.AutoTileY) { var pool = new OSPPool(obj); pool.CanGrow = false; ParallaxPools.Add(pool); } } // Restore the original random state Random.state = oldState; // Remove the pooled objects from the parallax list since we don't need to perform the same functionality on tiled and non-tiled objects foreach (var pool in ParallaxPools) { ParallaxObjects.Remove(pool.PooledObject); } }
/// <summary> /// Updates all of the parallax layers and objects. /// </summary> void LateUpdate() { // Set the camera bounds every frame so we know where the min and max are CamBounds = MainCamera.OrthographicBounds(); // Apply parallax movement to each layer not being tiled foreach (var obj in ParallaxObjects) { ApplyParallax(obj); } // Apply parallax movement to each pooled object foreach (var pool in ParallaxPools) { foreach (var obj in pool.Pool) { if (!obj.gameObject.activeInHierarchy) { continue; } ApplyParallax(obj); } } TilesToRemove.Clear(); // Loop over each tiled object and check if a tile needs to be added or removed foreach (var pool in ParallaxPools) { foreach (var obj in pool.Pool) { // If it's an inactive object, don't waste any more time here if (!obj.gameObject.activeInHierarchy) { continue; } // Get the random offset history for this object's layer if it exists OSPRandomHistory historyList = null; if (obj.RandomizeHorizontalDistance || obj.RandomizeVerticalDistance) { if (PlacementHistory.Any(h => h.ID == obj.ObjectID)) { historyList = PlacementHistory.Single(h => h.ID == obj.ObjectID); } } #region AutoTileX if (obj.AutoTileX) { var sb = obj.SpriteBounds; // Don't do anything if we already have a left or right tile set if (!obj.hasLeftTile || !obj.hasRightTile) { // Add a tile to the right if it doesn't already have one and either it's visible by the camera or could be seen by the camera soon if ((sb.min.x < CamBounds.max.x && !obj.hasRightTile) || (GetDistance(sb.max.x, CamBounds.max.x) < sb.size.x && sb.min.x <= CamBounds.max.x && !obj.hasRightTile)) { int rowCheck = obj.row; int colCheck = obj.col + 1; // Check to see if an object already exists at this column/row combination before trying to add a new one if (!pool.Pool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy) && !pool.TempPool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy)) { CreateNewTile(obj, TileDirection.RIGHT, historyList); } } // Add a tile to the left if it doesn't already have one and either it's visible by the camera or could be seen by the camera soon if ((sb.max.x > CamBounds.min.x && !obj.hasLeftTile) || (GetDistance(sb.min.x, CamBounds.min.x) < sb.size.x && sb.max.x >= CamBounds.min.x && !obj.hasLeftTile)) { int rowCheck = obj.row; int colCheck = obj.col - 1; // Check to see if an object already exists at this column/row combination before trying to add a new one if (!pool.Pool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy) && !pool.TempPool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy)) { CreateNewTile(obj, TileDirection.LEFT, historyList); } } } // Removes items too far from camera if (obj.gameObject.activeInHierarchy) { bool removeLeft = obj.hasRightTile ? (sb.min.x < CamBounds.min.x && sb.max.x < CamBounds.min.x) && (obj.RightTile.SpriteBounds.min.x < CamBounds.min.x && obj.RightTile.SpriteBounds.max.x < CamBounds.min.x) : false; bool removeRight = obj.hasLeftTile ? (sb.min.x > CamBounds.max.x && sb.max.x > CamBounds.max.x) && (obj.LeftTile.SpriteBounds.min.x > CamBounds.max.x && obj.LeftTile.SpriteBounds.max.x > CamBounds.max.x) : false; bool removeUnlinked = !obj.hasLeftTile && !obj.hasRightTile && !obj.hasTopTile && !obj.hasBottomTile; if (removeLeft || removeRight || removeUnlinked) { if (!TilesToRemove.Contains(obj)) { TilesToRemove.Add(obj); } } } } #endregion #region AutoTileY if (obj.AutoTileY) { var sb = obj.SpriteBounds; // Don't do anything if we already have a top or bottom tile set if (!obj.hasTopTile || !obj.hasBottomTile) { // Add a tile to the top if it doesn't already have one and either it's visible by the camera or could be seen by the camera soon if ((sb.min.y < CamBounds.max.y && !obj.hasTopTile) || (GetDistance(sb.max.y, CamBounds.max.y) < sb.size.y && sb.min.y <= CamBounds.max.y && !obj.hasTopTile)) { int rowCheck = obj.row - 1; int colCheck = obj.col; // Check to see if an object already exists at this column/row combination before trying to add a new one if (!pool.Pool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy) && !pool.TempPool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy)) { CreateNewTile(obj, TileDirection.UP, historyList); } } // Add a tile to the bottom if it doesn't already have one and either it's visible by the camera or could be seen by the camera soon if ((sb.max.y > CamBounds.min.y && !obj.hasBottomTile) || (GetDistance(sb.min.y, CamBounds.min.y) < sb.size.y && sb.max.y >= CamBounds.min.y && !obj.hasBottomTile)) { int rowCheck = obj.row + 1; int colCheck = obj.col; // Check to see if an object already exists at this column/row combination before trying to add a new one if (!pool.Pool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy) && !pool.TempPool.Any(o => o.row == rowCheck && o.col == colCheck && o.gameObject.activeInHierarchy)) { CreateNewTile(obj, TileDirection.DOWN, historyList); } } } // Removes items too far from camera if (obj.gameObject.activeInHierarchy) { bool removeBottom = obj.hasTopTile ? (sb.min.y < CamBounds.min.y && sb.max.y < CamBounds.min.y) && (obj.TopTile.SpriteBounds.min.y < CamBounds.min.y && obj.TopTile.SpriteBounds.max.y < CamBounds.min.y) : false; bool removeTop = obj.hasBottomTile ? (sb.min.y > CamBounds.max.y && sb.max.y > CamBounds.max.y) && (obj.BottomTile.SpriteBounds.min.y > CamBounds.max.y && obj.BottomTile.SpriteBounds.max.y > CamBounds.max.y) : false; bool removeUnlinked = !obj.hasLeftTile && !obj.hasRightTile && !obj.hasTopTile && !obj.hasBottomTile; if (removeBottom || removeTop || removeUnlinked) { if (!TilesToRemove.Contains(obj)) { TilesToRemove.Add(obj); } } } } #endregion } } foreach (var pool in ParallaxPools) { pool.AddTemporaryObjectsToPool(); } foreach (var tile in TilesToRemove) { //remove tiles and update their connected tiles if (tile.hasLeftTile) { tile.LeftTile.RightTile = null; } if (tile.hasRightTile) { tile.RightTile.LeftTile = null; } if (tile.hasTopTile) { tile.TopTile.BottomTile = null; } if (tile.hasBottomTile) { tile.BottomTile.TopTile = null; } tile.LeftTile = null; tile.RightTile = null; tile.TopTile = null; tile.BottomTile = null; tile.gameObject.SetActive(false); } prevCameraPosition = MainCamera.transform.position; }