private void Awake() { S = this; gridBackgroundSprite = transform.Find("GridBackground").GetComponent <SpriteRenderer>(); if (gridBackgroundSprite == null) { Debug.LogError("GridDisplayer.cs : gridBackgroundSprite couldn't be found. Is there a SpriteRenderer on a child of this object?"); } cellContainer = transform.Find("CellContainer"); if (cellContainer == null) { Debug.LogError("GridDisplayer.cs : GridContainer object couldn't be found in children of this object."); } gridElementContainer = transform.Find("GridElementContainer"); if (gridElementContainer == null) { Debug.LogError("GridManager.cs : GridElementContainer object couldn't be found in children of this object."); } poolContainer = transform.Find("PoolContainer"); if (poolContainer == null) { Debug.LogError("GridManager.cs : PoolContainer object couldn't be found in children of this object."); } if (gridCellBackgroundPrefab == null) { Debug.LogError("GridDisplayer.cs : The grid cell prefab is null. Has it been assigned in the inspector?"); } }
/// <summary> /// Called by Input.cs on the frame a primary touch stops being detected. /// </summary> /// <param name="worldTouchPos">The world-space position of the touch when it left the screen.</param> public static void TouchEnd(Vector3 worldTouchPos) { TouchInfo touchInfo = S.primaryTouchInfo; // world position and end world position touchInfo.worldPosition = touchInfo.endWorldPosition = worldTouchPos; // gridPosition and endGridPosition Vector2Int gridPos = GridDisplayer.WorldToGrid(worldTouchPos); if (gridPos.x >= GRID_WIDTH || gridPos.x < 0 || gridPos.y >= GRID_HEIGHT || gridPos.y < 0) { touchInfo.gridPosition = new Vector2Int(int.MinValue, int.MinValue); } else { touchInfo.gridPosition = touchInfo.endGridPosition = gridPos; } S.primaryTouchInfo = touchInfo; if (touchInfo.swipeDirection != Vector2.zero) { S.shouldGetSwipedCell = true; } }
/// <summary> /// Called by Input.cs starting every frame a primary touch is detected, starting from the frame after TouchBegin is called. /// </summary> /// <param name="worldTouchPos">The world-space position of the primary touch.</param> public static void TouchMove(Vector3 worldTouchPos) { TouchInfo touchInfo = S.primaryTouchInfo; //world position touchInfo.worldPosition = worldTouchPos; // gridPosition Vector2Int gridPos = GridDisplayer.WorldToGrid(worldTouchPos); if (gridPos.x >= GRID_WIDTH || gridPos.x < 0 || gridPos.y >= GRID_HEIGHT || gridPos.y < 0) { touchInfo.gridPosition = new Vector2Int(int.MinValue, int.MinValue); } else { touchInfo.gridPosition = gridPos; } S.primaryTouchInfo = touchInfo; //if ((touchInfo.gridPosition - touchInfo.startGridPosition).magnitude >= 1 && !S.swipeHappenedThisTouch) //{ // S.shouldGetSwipedCell = true; // S.swipeHappenedThisTouch = true; //} }
/// <summary> /// Called by Input.cs on the frame a primary touch is detected. /// </summary> /// <param name="worldTouchPos">The world-space position of the primary touch.</param> public static void TouchBegin(Vector3 worldTouchPos) { // touchInfo is a struct, so it's passed by value. // when we operate on primtouch, we're not actually updating S.primaryTouchInfo. TouchInfo primTouch = S.primaryTouchInfo; primTouch.touching = true; // worldPosition and startWorldPosition primTouch.worldPosition = primTouch.startWorldposition = worldTouchPos; // gridPosition and startGridPosition Vector2Int gridPos = GridDisplayer.WorldToGrid(worldTouchPos); if (gridPos.x >= GRID_WIDTH || gridPos.x < 0 || gridPos.y >= GRID_HEIGHT || gridPos.y < 0) { primTouch.gridPosition = new Vector2Int(int.MinValue, int.MinValue); } else { primTouch.gridPosition = primTouch.startGridPosition = gridPos; } // end positions primTouch.endWorldPosition = new Vector2(float.MinValue, float.MinValue); primTouch.endGridPosition = new Vector2Int(int.MinValue, int.MinValue); S.primaryTouchInfo = primTouch; }
public override void OnFall(int fallHeight) { DeRegisterMethodsFromCell(x, y); RegisterMethodsOnCell(x, y + fallHeight); y += fallHeight; MasterSequence.Append(transform.DOMove(GridDisplayer.GridToWorld(x, y), fallTweenDuration).SetEase(Ease.OutBounce)); }
void Awake() { Instance = this; m_NoColor = new Color(0, 0, 0, 0); m_MoveRangeColor = new Color(0, 0, 255, 0.5f); m_DestinationColor = new Color(0, 255, 0, 0.5f); m_AttackRangeColor = new Color(255, 222, 0, 0.5f); m_UnderMouseColor = new Color(0, 0, 255, 0.5f); m_SelectionColor = new Color(0, 255, 0, 0.5f); }
public override void OnSwap(Vector2Int newCellPos) { // deregister methods from the original cell DeRegisterMethodsFromCell(x, y); // register methods on the new cell RegisterMethodsOnCell(newCellPos); // set x and y to new cell x = newCellPos.x; y = newCellPos.y; // tween position MasterSequence.Append(transform.DOMove(GridDisplayer.GridToWorld(x, y), swapTweenDuration)); wasSwapped = true; }
public override void OnPop() { // if this cell is part of a match but isn't the one that was swapped this frame, it // needs to wait for the swap tween to end before disappearing if (!wasSwapped) { MasterSequence.AppendInterval(swapTweenDuration); } MasterSequence.AppendCallback(() => popParticleSystem.Play()); MasterSequence.Append(transform.DOScale(0, popTweenDuration).SetEase(Ease.InBack)); if (!returningToPool) { MasterSequence.AppendCallback(() => GridDisplayer.ReturnCandyToPool(this)); returningToPool = true; } // scale the game object back to 1 after returning it so that it's scaled properly when we get it back from the pool again MasterSequence.AppendCallback(() => transform.localScale = new Vector3(1, 1, 1)); }
public override void OnSwapFail(Vector2Int newCellPos) { // move to the new position then back to the old one MasterSequence.Append(transform.DOMove(GridDisplayer.GridToWorld(newCellPos), swapTweenDuration)); MasterSequence.Append(transform.DOMove(GridDisplayer.GridToWorld(x, y), swapTweenDuration)); }
private void Update() { // if we're currently animating, don't update grid state until the display of the grid is caught up ith the current grid state. if (GridDisplayer.TweenInProgress()) { return; } if (primaryTouchInfo.touching && !cellSelected) { // user is touching inside the grid if (primaryTouchInfo.startGridPosition != new Vector2Int(int.MinValue, int.MinValue)) { selectedCell = primaryTouchInfo.startGridPosition; cellSelected = true; Debug.Log(grid[selectedCell.x, selectedCell.y].cellContent); } } if (!primaryTouchInfo.touching && cellSelected) { cellSelected = false; } #region Candy Swapping if (shouldGetSwipedCell) { // user has touched inside the grid if (primaryTouchInfo.startGridPosition != new Vector2Int(int.MinValue, int.MinValue)) { int x = primaryTouchInfo.startGridPosition.x + primaryTouchInfo.swipeDirection.x; int y = primaryTouchInfo.startGridPosition.y - primaryTouchInfo.swipeDirection.y; // user is swiping inside the grid if (x < GRID_WIDTH && y < GRID_HEIGHT && x >= 0 && y >= 0) { // the cell the player is swiping swipedCell = new Vector2Int(x, y); // swap the cellContent variables of the cells being swapped CellContents temp = grid[swipedCell.x, swipedCell.y].cellContent; grid[swipedCell.x, swipedCell.y].cellContent = grid[selectedCell.x, selectedCell.y].cellContent; grid[selectedCell.x, selectedCell.y].cellContent = temp; // check for matches List <Vector2Int> matches = new List <Vector2Int>(); matches.AddRange(GetMatchesAtCell(swipedCell)); matches.AddRange(GetMatchesAtCell(selectedCell)); if (matches.Count != 0) { // this is kind of a hack : // Basically, when a GridElement swaps, it deregisters its swap method from its // current cell and registers it on the new cell. however, since the new // cell's swap method gets called right after, the first GridElement's swap // method was being called twice. // We "solve" this by taking a "snapshot" of the swiped gridcell before the // first GridElement registers its swap method on it, and calling the swap // method of this "snapshot". The first GridElement's swap method is only // called once and still registers itself on the right cell. GridCell swipedCellCopy = grid[swipedCell.x, swipedCell.y]; if (grid[selectedCell.x, selectedCell.y].Swap != null) { grid[selectedCell.x, selectedCell.y].Swap(swipedCell); } if (swipedCellCopy.Swap != null) { swipedCellCopy.Swap(selectedCell); } foreach (Vector2Int cell in matches) { if (grid[cell.x, cell.y].Pop != null) { grid[cell.x, cell.y].Pop(); } grid[cell.x, cell.y].cellContent = CellContents.empty; } return; } else { // call swap fail event (no need for the above hack since GridElement_Candy's // SwapFail method doesn't do any registering/deregistering) if (grid[selectedCell.x, selectedCell.y].SwapFail != null) { grid[selectedCell.x, selectedCell.y].SwapFail(swipedCell); } if (grid[swipedCell.x, swipedCell.y].SwapFail != null) { grid[swipedCell.x, swipedCell.y].SwapFail(selectedCell); } // swap cell contents back CellContents temp2 = grid[swipedCell.x, swipedCell.y].cellContent; grid[swipedCell.x, swipedCell.y].cellContent = grid[selectedCell.x, selectedCell.y].cellContent; grid[selectedCell.x, selectedCell.y].cellContent = temp2; } } } //shouldGetSwipedCell = false; } #endregion #region Candy Falling if (GridHasEmptyCells()) { // check for cells i which new candies should be spawned for (int i = GRID_WIDTH - 1; i >= 0; --i) { int topColumnCellYCoord = 0; // if the top cell of the column is a hole, go down until we find one that's not if (grid[i, topColumnCellYCoord].cellContent == CellContents.hole) { for (int j = 0; j < GRID_HEIGHT; ++j) { if (grid[i, j].cellContent == CellContents.hole) { ++topColumnCellYCoord; } else { break; } } } if (grid[i, topColumnCellYCoord].cellContent == CellContents.empty) { // get a random color for the new candy // FIND A BETTER WAY TO DO THIS CellContents color = (CellContents)Random.Range(1, 6); // Call a GridDisplayer Function to spawn in a new candy in the top row cell GridDisplayer.SpawnNewCandy(color, new Vector2Int(i, topColumnCellYCoord)); grid[i, topColumnCellYCoord].cellContent = color; } } bool candiesFellThisFrame = false; // go backwards throught the grid, starting 1 line above the bottom because we're checking if cells below are emtpy for (int j = GRID_HEIGHT - 2; j >= 0; --j) { for (int i = GRID_WIDTH - 1; i >= 0; --i) { if (grid[i, j].cellContent == CellContents.hole) { continue; } // if we find a non empty cell above an empty cell or a hole if ((grid[i, j + 1].cellContent == CellContents.empty || grid[i, j + 1].cellContent == CellContents.hole) && grid[i, j].cellContent != CellContents.empty) { int dist = 0; int holeCounter = 0; // go down until we find a cell that's above a non empty cell or at the bottom of the grid for (int y = j + 1; y < GRID_HEIGHT; ++y) { if (grid[i, y].cellContent == CellContents.hole) { // if the cell we're checking is a hole, we don't want to add to dist just yet in // case its holes all the way down, in which case the candy should not drop, so // we keep a separate counter for holes holeCounter++; } else { dist++; // if we've hit a cell that's not a hole and the holeCounter is greater than 0, // that means the ccandy can fall down through the holes. we add the holecounter // to dist and we reset it in case we find more holes lower in the grid. if (holeCounter > 0) { dist += holeCounter; holeCounter = 0; } } if (y < GRID_HEIGHT - 1) { // break if the cell below the one we're checking is not empty and not a hole if (grid[i, y + 1].cellContent != CellContents.empty && grid[i, y + 1].cellContent != CellContents.hole) { break; } } } // dist is now the number of empty cells between the cell we're at and the next non-empty cell under it // we call Fall on grid[i,j] and pass it dist if (dist > 0) { if (grid[i, j].Fall != null) { grid[i, j].Fall(dist); } grid[i, j + dist].cellContent = grid[i, j].cellContent; grid[i, j].cellContent = CellContents.empty; candiesFellThisFrame = true; } } } } if (candiesFellThisFrame) { return; } //for each grid column i, check if grid[i, 0] is empty. if yes, new candies should be spawned. //for (int i = GRID_WIDTH - 1; i >= 0; --i) //{ // int topColumnCellYCoord = 0; // // // if the top cell of the column is a hole, go down until we find one that's not // if (grid[i, topColumnCellYCoord].cellContent == CellContents.hole) // { // for (int j = 0; j < GRID_HEIGHT; ++j) // { // if (grid[i, j].cellContent == CellContents.hole) // { // ++topColumnCellYCoord; // } // else // { // break; // } // } // } // // if (grid[i, topColumnCellYCoord].cellContent == CellContents.empty) // { // // "probe" down to see how far down the next non-empty cell is // int dist = 0; // int holeCounter = 0; // // for (int j = topColumnCellYCoord+1; j < GRID_WIDTH; ++j) // { // if (grid[i, j].cellContent == CellContents.empty ) // { // ++dist; // // if (holeCounter > 0) // { // dist += holeCounter; // holeCounter = 0; // } // } // else if (grid[i, j].cellContent == CellContents.hole) // { // ++holeCounter; // } // else // { // break; // } // } // // // get a random color for the new candy // // FIND A BETTER WAY TO DO THIS // CellContents color = (CellContents)Random.Range(1, 6); // // // Call a GridDisplayer Function to spawn in a new candy in the top row cell // GridDisplayer.SpawnNewCandy(color, new Vector2Int(i, topColumnCellYCoord)); // // // call Fall on this top row cell if dist > 0 // if (dist > 0) // { // if (grid[i, 0].Fall != null) // grid[i, 0].Fall(dist); // // grid[i, dist].cellContent = color; // } // else // { // grid[i, 0].cellContent = color; // } // // } //} } #endregion if (!GridHasEmptyCells()) { // check the whole grid for matches and pop them // for some reason this doesn't detect 4+ matches sometimes ? for (int i = 0; i < GRID_WIDTH; ++i) { for (int j = 0; j < GRID_HEIGHT; ++j) { foreach (Vector2Int cellPos in GetMatchesAtCell(new Vector2Int(i, j))) { if (grid[cellPos.x, cellPos.y].Pop != null) { grid[cellPos.x, cellPos.y].Pop(); } grid[cellPos.x, cellPos.y].cellContent = CellContents.empty; } } } } }
public void SetupGrid() { GridDisplayer.TeardownGridDisplay(); if (level == null) { // create random grid grid = new GridCell[GRID_WIDTH, GRID_HEIGHT]; for (int i = 0; i < GRID_WIDTH; ++i) { for (int j = 0; j < GRID_HEIGHT; ++j) { grid[i, j] = new GridCell() { visible = true, empty = false, x = i, y = j, // FIND A BETTER WAY TO DO THIS cellContent = (CellContents)Random.Range(1, 6) }; } } } else { // load info from level GRID_WIDTH = level.gridWidth; GRID_HEIGHT = level.gridHeight; grid = new GridCell[GRID_WIDTH, GRID_HEIGHT]; // go through level.grid, a 1D array for (int i = 0; i < GRID_WIDTH * GRID_HEIGHT; ++i) { // find x and y 2D grid positions of the cell at i with the formula : i = x * gridHeight + y int gridX = i / gridHeight; int gridY = i % gridHeight; grid[gridX, gridY] = new GridCell() { visible = true, empty = false, x = gridX, y = gridY }; // cell contents if (level.grid[i].hole) { grid[gridX, gridY].cellContent = CellContents.hole; } else { // if the current cell is empty in the level SO, it hasn't been defined and we should fill it with a random color if (level.grid[i].content == CellContents.empty) { grid[gridX, gridY].cellContent = level.GetRandomColor(); } else { grid[gridX, gridY].cellContent = level.grid[i].content; } } } } // INITIAL MATCH DETECTION FOR CLEAN GRID List <Vector2Int> allMatches = new List <Vector2Int>(); do { // we first remove the matches if there are any. for (int i = 0; i < allMatches.Count; i++) { Vector2Int cell = allMatches[i]; if (level != null) { grid[cell.x, cell.y].cellContent = level.GetRandomColor(); } else { grid[cell.x, cell.y].cellContent = (CellContents)Random.Range(1, 6); } allMatches.Remove(cell); } // then we check the grid for matches. The above operation might have created new matches. for (int i = 0; i < GRID_WIDTH; ++i) { for (int j = 0; j < GRID_HEIGHT; ++j) { foreach (Vector2Int cellPos in GetMatchesAtCell(new Vector2Int(i, j))) { if (!allMatches.Contains(cellPos)) { allMatches.Add(cellPos); } } } } // finally we loop back as long as the above nested for loop has found at least one match } while (allMatches.Count != 0); GridDisplayer.InitializeGridDisplay(); }