본문 바로가기

유니티와 놀기

체스말을 움직이기

 최근에 체스가 재밌어서, 재미삼아 체스와 비슷한 것을 만들어보기로 했다. 처음에는 가장 기본적인 말을 움직이는 기능을 만들었다.

 

 우선 스프라이트를 배치해 8x8 크기의 보드를 만든다. 이때 칸의 좌표는 칸의 중심을 가리키게 했다.

 

 

 체스 판을 담당하는 Board스크립트를 만들고 칸의 개수와 한 칸의 크기를 나타내는 상수를 만든다. 앞으로 칸 위의 위치는 Board의 CoordinateToWorld()를 통해 얻게 하여 다른 좌표와 혼동되는 일이 없게 한다.

 

public class Board : MonoBehaviour
{
    private static readonly int xLength = 8;
    private static readonly int yLength = 8;
    private static readonly float xSize = 1.0f;
    private static readonly float ySize = 1.0f;

    static public Vector2 CoordinateToWorld (Vector2Int pos)
    {
        return Vector2.up * pos.y * ySize + Vector2.right * pos.x * xSize;
    }

    static public Vector2Int WorldToCoordinate (Vector2 pos)
    {
        Vector2Int vec = Vector2Int.zero;
        vec.x = Mathf.CeilToInt(pos.x + 0.5f) - 1;
        vec.y = Mathf.CeilToInt(pos.y + 0.5f) - 1;
        return vec;
    }
}

 

 다음으로 모든 말을 담당하는 Chessman 스크립트와 그것을 상속 받아 플레이어가 움직일 수 있는 말을 담당하는 PlayerMoveableChessman 스크립트를 만든다. Chessman은 말이 특정 좌표로 이동하는 기능, PlayerMoveableChessman은 말을 클릭할 때 이동할 수 있는 칸을 표시하고, 해당 칸으로 움직이게 하는 기능을 맡는다.

 

public abstract class Chessman : MonoBehaviour
{
    public Board board;
    public Vector2Int pos;

    public void MoveToCoordinate(Vector2Int destination)
    {
        board.DeplaceChessman(pos);
        board.PlaceChessman(this, destination);
        pos = destination;
        transform.position = (Board.CoordinateToWorld(destination));
    }

    public abstract void BoardSelect(Vector2Int coord);
}

 

public abstract class PlayerMoveableChessman : Chessman
{
    public GameObject coordPointer; // 이동 가능 좌표 표시용
    private List<GameObject> coordPointers = new List<GameObject>();
    private bool selected = false;

    public abstract List<Vector2Int> MoveableCoord();

    public override void BoardSelect(Vector2Int coord)
    {
        if (coord == pos && selected == false) // 말이 선택됨
        {
            selected = true;
            MakeMovableCoordPointer();
        }
        else if (selected == true && MoveableCoord().Contains(coord)) // 말이 선택되고, 말의 이동 칸이 선택됨
        {
            MoveToCoordinate(coord);
            selected = false;
            DeleteMoveableCoordPointer();
        }
        else if (coord != pos) // 이외의 칸이 선택됨
        {
            selected = false;
            DeleteMoveableCoordPointer();
        }
    }

    public void MakeMovableCoordPointer()
    {
        List<Vector2Int> coords = MoveableCoord();
        foreach (Vector2Int coord in coords)
        {
            GameObject obj = Instantiate(coordPointer);
            obj.transform.position = Board.CoordinateToWorld(coord);
            coordPointers.Add(obj);
        }
    }

    public void DeleteMoveableCoordPointer()
    {
        foreach (GameObject obj in coordPointers)
        {
            Destroy(obj);
        }
        coordPointers.Clear();
    }
}

 

 Board에 2차원 배열 chessmans를 만들어 보드 위에 있는 chessman들을 담게 한다. 보드에 클릭이 일어날 경우 Board가 chessman들을 모은 뒤 클릭한 좌표를 넘겨 처리하게 한다.

 

public class Board : MonoBehaviour
{
    private Chessman[,] chessmans;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Vector2Int coord = WorldToCoordinate(mousePos);
            List<Chessman> li = new List<Chessman>();
            for (int i = 0; i < xLength; i++)
                for (int j = 0; j < yLength; j++)
                    if (chessmans[i, j] != null)
                        li.Add(chessmans[i, j]);
            // 말이 움직이며 생기는 중복 선택을 막기 위해 말을 고르고 후에 호출한다.
            li.ForEach((chessman) => chessman.BoardSelect(coord)); 
        }
    }
}

 

 말이 움직일 수 있는 칸은 말마다 이동 방식이 크게 달라(나이트는 말을 뛰어 넘지만, 비숍은 가로막히는 것처럼) 말에 따라 각각의 함수를 만들기로 했다. 새로운 말을 만들 때는 PlayerMovableChessman을 상속 받고 말이 움직일 수 있는 칸을 반환하는 MoveableCoord()를 오버라이드하면 된다.

 

public class King : PlayerMoveableChessman
{
    private void Start()
    {
        MoveToCoordinate(new Vector2Int(3, 2));
        board.PlaceChessman(this, pos);
    }

    public override List<Vector2Int> MoveableCoord()
    {
        List<Vector2Int> li = new List<Vector2Int>();
        int[] x = new int[3] { 1, 0, -1 };
        int[] y = new int[3] { 1, 0, -1 };
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                if (board.IsCoordAvailable(pos + Vector2Int.right * x[i] + Vector2Int.up * y[j]))
                    li.Add(pos + Vector2Int.right * x[i] + Vector2Int.up * y[j]);
        return li;
    }
}

 

public class Bishop : PlayerMoveableChessman
{
    private void Start()
    {
        MoveToCoordinate(Vector2Int.one);
        board.PlaceChessman(this, pos);
    }

    public override List<Vector2Int> MoveableCoord()
    {
        List<Vector2Int> li = new List<Vector2Int>();
        for (int i = 1; board.IsCoordAvailable(pos + new Vector2Int(1, 1) * i); i++)
            li.Add(pos + new Vector2Int(1, 1) * i);
        for (int i = 1; board.IsCoordAvailable(pos + new Vector2Int(-1, 1) * i); i++)
            li.Add(pos + new Vector2Int(-1, 1) * i);
        for (int i = 1; board.IsCoordAvailable(pos + new Vector2Int(1, -1) * i); i++)
            li.Add(pos + new Vector2Int(1, -1) * i);
        for (int i = 1; board.IsCoordAvailable(pos + new Vector2Int(-1, -1) * i); i++)
            li.Add(pos + new Vector2Int(-1, -1) * i);
        return li;
    }
}

 

 킹은 주변의 8칸을 조사하고 비숍은 4방향의 대각선으로 뻗어가면서 보드에 남는 자리가 있는지 확인한다. 이동가능한 좌표를 리스트에 저장하고 반환하면 PlayerMoveableChessman에서 처리한다. 이런식으로 다른 이동방식의 말도 쉽게 추가할 수 있다.

 

<결과물>

 체스판 위에서 말을 움직이는 기능을 간단히 만들어보았다. 다음에는 말을 잡는 기능을 만들어 봐야겠다.

'유니티와 놀기' 카테고리의 다른 글

delegate 사용하기  (0) 2023.04.04
간단한 단색 그래픽 만들기  (0) 2023.03.05
코루틴 뒤에 코루틴을 실행시키기  (0) 2022.11.10
체스말을 잡기  (0) 2022.11.04