
Note: you need to know Unity and the C # language. If you want to increase your skill in C #, then you can start with a series of video courses Beginning C # with Unity Screencast .
Vector3 points.GameManager contains a 2D array of pieces that stores the positions of pieces on the board. Explore AddPiece , PieceAtGrid and GridForPiece to understand how it works.
TileSelector will help you choose a moving shape, and MoveSelector will allow you to choose a place to move.Start : for initial setup.EnterState : performs customization for the current shape activation.Update : performs ray tracking as you move the mouse.ExitState : resets the current state and calls EnterState next state.Note: when creating new scripts, take time to move them to the appropriate folder to maintain order in the Assets folder.
public GameObject tileHighlightPrefab; private GameObject tileHighlight; Start : Vector2Int gridPoint = Geometry.GridPoint(0, 0); Vector3 point = Geometry.PointFromGrid(gridPoint); tileHighlight = Instantiate(tileHighlightPrefab, point, Quaternion.identity, gameObject.transform); tileHighlight.SetActive(false); Start gets the source row and column for the selected cell, turns them to a point and creates a game object from the prefab. This object is initially deactivated, so it will not be visible until it is needed.Vector2Int and are Vector2Int to as GridPoint . Vector2Int has two integer values: x and y. When we need to place an object in a scene, we need a Vector3 point. Vector3 has three floating point values: x, y, and z.GridPoint(int col, int row) : gives us a GridPoint for a given column and row.PointFromGrid(Vector2Int gridPoint) : converts a GridPoint to a real point of the Vector3 scene.GridFromPoint(Vector3 point) : gives us a GridPoint for the x and z values ​​of this 3D point, and the y value is ignored.EnterState : public void EnterState() { enabled = true; } Update : Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { Vector3 point = hit.point; Vector2Int gridPoint = Geometry.GridFromPoint(point); tileHighlight.SetActive(true); tileHighlight.transform.position = Geometry.PointFromGrid(gridPoint); } else { tileHighlight.SetActive(false); } Ray from the camera, passing through the mouse pointer and on, to infinity.Physics.Raycast checks if this ray intersects with any physical colliders of the system. Since the board is the only object with a collider, we do not need to worry that the figures will overlap each other.RaycastHit recorded in RaycastHit , including the intersection point. Using an auxiliary method, we turn this intersection point into a GridPoint , and then use this method to set the position of the selected cell.
if block, right after the place where we turn on the cell selection: if (Input.GetMouseButtonDown(0)) { GameObject selectedPiece = GameManager.instance.PieceAtGrid(gridPoint); if(GameManager.instance.DoesPieceBelongToCurrentPlayer(selectedPiece)) { GameManager.instance.SelectPiece(selectedPiece); // 1: ExitState } } GameManager sends us a figure in the current position. We need to check whether this piece belongs to the current player, because players should not be able to move the opponent’s pieces.Note: in such complex games, it is useful to clearly define the limits of responsibility of the components.Boarddeals only with the display and selection of figures.GameManagertracks the values ​​of theGridPointpositions of the shapes. It also contains helper methods that answer questions about where the pieces are and to which player they belong.

TileSelector did all its work. It is time for another component: MoveSelector .TileSelector . As before, select the Board object in the hierarchy, add a new component to it and name it MoveSelector .TileSelector to the TileSelector component. To do this, you can use ExitState . Add the following method to TileSelector.cs : private void ExitState(GameObject movingPiece) { this.enabled = false; tileHighlight.SetActive(false); MoveSelector move = GetComponent<MoveSelector>(); move.EnterState(movingPiece); } TileSelector component. In Unity, you cannot call the Update method of disabled components. Since we now want to call the Update method of the new component, then by disabling the old component, it will not disturb us.Update immediately after 1 : ExitState(selectedPiece); MoveSelector and add these instance variables at the top of the class: public GameObject moveLocationPrefab; public GameObject tileHighlightPrefab; public GameObject attackLocationPrefab; private GameObject tileHighlight; private GameObject movingPiece; this.enabled = false; tileHighlight = Instantiate(tileHighlightPrefab, Geometry.PointFromGrid(new Vector2Int(0, 0)), Quaternion.identity, gameObject.transform); tileHighlight.SetActive(false); TileSelector . Then we load the selection overlay just as we did before.EnterState method: public void EnterState(GameObject piece) { movingPiece = piece; this.enabled = true; } Update method of the MoveSelector component: Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { Vector3 point = hit.point; Vector2Int gridPoint = Geometry.GridFromPoint(point); tileHighlight.SetActive(true); tileHighlight.transform.position = Geometry.PointFromGrid(gridPoint); if (Input.GetMouseButtonDown(0)) { // 2: if (GameManager.instance.PieceAtGrid(gridPoint) == null) { GameManager.instance.Move(movingPiece, gridPoint); } // 3: ExitState(); } } else { tileHighlight.SetActive(false); } Update similar to the method from TileSelector and uses the same Raycast check to find out which cell a mouse is on. However, this time, when you click the mouse button, we call GameManager to move the shape to a new square.ExitState method to reset everything and prepare for the next move: private void ExitState() { this.enabled = false; tileHighlight.SetActive(false); GameManager.instance.DeselectPiece(movingPiece); movingPiece = null; TileSelector selector = GetComponent<TileSelector>(); selector.EnterState(); } GameManager to remove the selection from the shape. Then we call EnterState from TileSelector to start the process from the beginning.MoveSelector slots:Selection-BlueSelection-Yellow .Selection-Red .

EnterState component of the MoveSelector . Here we generate overlay cells showing where the player can go, so this is the most reasonable.GameManager list of allowable cells (i.e. moves). GameManager will use a shape subclass to generate a list of possible cells. It will then filter out positions that are occupied or outside the board.MoveSelector , which highlights the allowed moves and waits for the player to choose.MoveLocations to look like this: public override List MoveLocations(Vector2Int gridPoint) { var locations = new List<Vector2Int>(); int forwardDirection = GameManager.instance.currentPlayer.forward; Vector2Int forward = new Vector2Int(gridPoint.x, gridPoint.y + forwardDirection); if (GameManager.instance.PieceAtGrid(forward) == false) { locations.Add(forward); } Vector2Int forwardRight = new Vector2Int(gridPoint.x + 1, gridPoint.y + forwardDirection); if (GameManager.instance.PieceAtGrid(forwardRight)) { locations.Add(forwardRight); } Vector2Int forwardLeft = new Vector2Int(gridPoint.x - 1, gridPoint.y + forwardDirection); if (GameManager.instance.PieceAtGrid(forwardLeft)) { locations.Add(forwardLeft); } return locations; } Player object contains a value that determines the direction of the pawn's movement. For the first player this value is +1, for the opponent it is -1.Move method: public List MovesForPiece(GameObject pieceObject) { Piece piece = pieceObject.GetComponent(); Vector2Int gridPoint = GridForPiece(pieceObject); var locations = piece.MoveLocations(gridPoint); // locations.RemoveAll(tile => tile.x < 0 || tile.x > 7 || tile.y < 0 || tile.y > 7); // locations.RemoveAll(tile => FriendlyPieceAt(tile)); return locations; } Piece component of the game piece, as well as its current position.GameManager list of positions for this shape and filter out invalid values.RemoveAll is a useful function that uses a callback expression (callback mechanism) . This method looks at each value in the list, passing it to the expression as a tile . If this expression is true , then the value is removed from the list. private List<Vector2Int> moveLocations; private List<GameObject> locationHighlights; GridPoint values ​​for move positions; the second contains a list of overlay cells indicating whether the player can move to this position.EnterState method: moveLocations = GameManager.instance.MovesForPiece(movingPiece); locationHighlights = new List<GameObject>(); foreach (Vector2Int loc in moveLocations) { GameObject highlight; if (GameManager.instance.PieceAtGrid(loc)) { highlight = Instantiate(attackLocationPrefab, Geometry.PointFromGrid(loc), Quaternion.identity, gameObject.transform); } else { highlight = Instantiate(moveLocationPrefab, Geometry.PointFromGrid(loc), Quaternion.identity, gameObject.transform); } locationHighlights.Add(highlight); } GameManager and creates an empty list for storing overlay cell objects. It then loops through each position in the list. If there is already a figure in the current position, then it must be an opponent figure, because the player’s figures have already been filtered.2 , inside the if construct that checks the mouse button: if (!moveLocations.Contains(gridPoint)) { return; } ExitState in ExitState : foreach (GameObject highlight in locationHighlights) { Destroy(highlight); } 
GameManager is responsible for all the rules of the game, it is most reasonable to insert the switching code into it.GameManager has variables for current and other players, so we just need to swap these values.ExitState in MoveSelector is called after moving the selected shape, so it seems that it is best to perform a switch here. public void NextPlayer() { Player tempPlayer = currentPlayer; currentPlayer = otherPlayer; otherPlayer = tempPlayer; } ExitState , just before the EnterState call: GameManager.instance.NextPlayer(); ExitState and EnterState already take care of their own cleaning.
GameManager , open it and add the following method: public void CapturePieceAt(Vector2Int gridPoint) { GameObject pieceToCapture = PieceAtGrid(gridPoint); currentPlayer.capturedPieces.Add(pieceToCapture); pieces[gridPoint.x, gridPoint.y] = null; Destroy(pieceToCapture); } GameManager checks which piece is in the target position. This figure is added to the list of taken pieces for the current player. Then it is removed from the cell record of the GameManager board, and the GameObject destroyed, which removes it from the scene.Update method, locate the comment 3 and replace it with the following construction: else { GameManager.instance.CapturePieceAt(gridPoint); GameManager.instance.Move(movingPiece, gridPoint); } if construct checks if there is a figure in the target position. Since at the stage of generating moves, the player’s figures were filtered, then the opponent’s figure should be on the cage containing the figure.
TileSelector and MoveSelector from the board.CapturePieceAt method of the CapturePieceAt script , add the following lines before deleting the taken shape: if (pieceToCapture.GetComponent<Piece>().type == PieceType.King) { Debug.Log(currentPlayer.name + " wins!"); Destroy(board.GetComponent<TileSelector>()); Destroy(board.GetComponent<MoveSelector>()); } ExitStateand EnterStateturn on one of them, so the game will continue.GameObject; You can also use it to remove a component attached to an object.
Pieceand its individual subclasses are a great tool for encapsulating special relocation rules.Pawn. Figures moving one cell in different directions, such as the king and knight, are configured in the same way. Try to implement these rules of moves independently.Pieceare pre-prepared lists of directions in which the bishop and the rook can move from the starting point. These are all directions from the current position of the shape.MoveLocationsfollowing code: public override List<Vector2Int> MoveLocations(Vector2Int gridPoint) { List<Vector2Int> locations = new List<Vector2Int>(); foreach (Vector2Int dir in BishopDirections) { for (int i = 1; i < 8; i++) { Vector2Int nextGridPoint = new Vector2Int(gridPoint.x + i * dir.x, gridPoint.y + i * dir.y); locations.Add(nextGridPoint); if (GameManager.instance.PieceAtGrid(nextGridPoint)) { break; } } } return locations; } foreachgoes around each direction. For each direction, there is a second cycle that generates a sufficient number of new positions to move the piece off the board. Since the position list will filter out positions outside the board, we just need so many of them so that we don’t miss any cells.GridPointfor the position and add it to the list. Then check if there is a figure in this position. If so, stop the inner loop to go in the next direction.breakadded because the standing figure will block further movement. Again, down the chain, we remove the filter position with the player's figures, so that we no longer interfere with their presence.Note: if you need to distinguish the direction forward from the direction backwards, or left from the right, then you need to take into account that the black and white pieces move in opposite directions.

MoveLocationsfollowing code: public override List<Vector2Int> MoveLocations(Vector2Int gridPoint) { List<Vector2Int> locations = new List<Vector2Int>(); List<Vector2Int> directions = new List<Vector2Int>(BishopDirections); directions.AddRange(RookDirections); foreach (Vector2Int dir in directions) { for (int i = 1; i < 8; i++) { Vector2Int nextGridPoint = new Vector2Int(gridPoint.x + i * dir.x, gridPoint.y + i * dir.y); locations.Add(nextGridPoint); if (GameManager.instance.PieceAtGrid(nextGridPoint)) { break; } } } return locations; } List.Listis that we can add directions from another array, creating one Listwith all directions. The rest of the method is the same as in the elephant code.
GameManagervariables and methods that track these situations and their possibility when moving the figure. If they are possible, then you need to add corresponding positions in this figure MoveLocations.Source: https://habr.com/ru/post/359356/
All Articles