본문 바로가기

Unity/루빅스 큐브 개발 일지

[Unity] 루빅스 큐브 층 회전 알고리즘

요약

알고리즘 작성 전에 고려 사항으로 색상 블록을 합쳐 하나의 큐브를 완성한 것과 공전 시 이동 경로가 서로 다른 것을 확인하고 작성해야 한다. 코드를 보면 temp가 특이한 역할을 하고 있는 것이 보이는데, 이는 이전 루빅스 큐브 층 확인 알고리즘에서 마우스를 클릭하는 동안 회전하는 값을 활용하여 정렬 시 보정을 주는 역할을 한다.


알고리즘

알고리즘 작성 전에 몇 가지 고려해야 할 사항이 있다. 먼저 큐브 위치 확인 알고리즘으로 들어온 정보는 정확하게 해당 큐브가 아닌 큐브 색상을 가져오는 것이다.

 

위 그림을 보면 색상 블록을 합쳐 하나의 큐브를 완성한 것을 알 수 있다. 그렇기 때문에 정확한 층을 파악할 수 있는 것이지만, 큐브 위치를 이동시킬 때에는 해당 블록의 부모인 검은색 큐브를 움직여야 한다.

검은색 큐브를 움직일 때 또한 고려해야 할 사항이 있다. 내부의 4개의 큐브는 그림과 같이 빨간 선을 따라 움직이지만, 외부의 4개의 큐브는 파란 선을 따라 움직인다.

CubeMovement.cs

public class CubeMovement : MonoBehaviour
{
    private List<GameObject> nowCube;
    private CubeState cubeState;
    public int floor;
    float angle = 0;
    bool auto = false;

    void Start()
    {
        cubeState = FindObjectOfType<CubeState>();
    }

    void Update()
    {
        // 마우스 우측 클릭이 올라가면 자동으로 90도 각도로 맞춰 지도록 함
        if (Input.GetMouseButtonUp(0))
        {
            auto = true;
            StartRotate(nowCube, 0);
            angle = 0;
            auto = false;
        }
    }

필요한 클래스나 변수를 초기화해야 한다. 특이한 점은 auto인데, auto는 큐브가 돌아가도 정렬된 상태를 유지하기 위해 사용한다.

 

 

    // 마우스 좌클릭이 되는 순간과 현재 감지되고 있는 값을 뺀 값을 받는중
    public void StartRotate(List<GameObject> side, float inAngle)
    {
        float temp = 0;
        nowCube = side;
        if (!auto)
        {
            angle += inAngle;
        }
        else
        {
            temp = -angle + RotateToRightAngle(angle);
            angle = RotateToRightAngle(angle);
        }

        if (side == cubeState.front || side == cubeState.back)
        {
            for (int i = 0; i < 8; i++)
            {
                if (i % 2 == 0)
                {
                    side[i].transform.parent.localPosition = new Vector3(floor, Sin(angle, i), Cos(angle, i));
                }
                else
                {
                    side[i].transform.parent.localPosition = new Vector3(floor, SinSqrt(angle, i), CosSqrt(angle, i));
                }
            }
            
            for (int i = 0; i < 9; i++)
            {
                Debug.DrawRay(-side[8].transform.parent.transform.parent.position, -side[i].transform.parent.transform.parent.right*1000, Color.red, 0.1f);
                side[i].transform.parent.rotation = Quaternion.AngleAxis(inAngle + temp, -side[i].transform.parent.transform.parent.right) * side[i].transform.parent.rotation;
            }
        }
        if (side == cubeState.right|| side == cubeState.left)
        {
            for (int i = 0; i < 8; i++)
            {
                if (i % 2 == 0)
                {
                    side[i].transform.parent.localPosition = new Vector3(Sin(angle, i), Cos(angle, i), floor);
                }
                else
                {
                    side[i].transform.parent.localPosition = new Vector3(SinSqrt(angle, i), CosSqrt(angle, i), floor);
                }
            }
            for (int i = 0; i < 9; i++)
            {
                Debug.DrawRay(-side[i].transform.parent.transform.parent.position, side[i].transform.parent.transform.parent.forward*1000, Color.red, 0.1f);
                side[i].transform.parent.rotation = Quaternion.AngleAxis(inAngle + temp, -side[i].transform.parent.transform.parent.forward) * side[i].transform.parent.rotation;
            }
        }
        
        if (side == cubeState.up || side == cubeState.down)
        {
            for (int i = 0; i < 8; i++)
            {
                if (i % 2 == 0)
                {
                    side[i].transform.parent.localPosition = new Vector3(Cos(angle, i), floor, Sin(angle, i));
                }
                else
                {
                    side[i].transform.parent.localPosition = new Vector3(CosSqrt(angle, i), floor, SinSqrt(angle, i));
                }
            }
            for (int i = 0; i < 9; i++)
            {
                Debug.DrawRay(-side[i].transform.parent.transform.parent.position, -side[i].transform.parent.transform.parent.up*1000, Color.red);
                side[i].transform.parent.rotation = Quaternion.AngleAxis(inAngle + temp, -side[i].transform.parent.transform.parent.up) * side[i].transform.parent.rotation;
            }
        }
    }

앞서 설명한 것처럼 transform.parent를 통해 검은색 큐브의 위치를 적절한 위치의 삼각함수를 활용하여 공전시킨 후, 각 큐브를 자전시킨다.

이때 temp가 특이한 역할을 하고 있는 것이 보이는데, 이는 이전 루빅스 큐브 층 확인 알고리즘에서 보내온 정보 중 하나인 마우스를 클릭하는 동안 회전하는 값을 측정하여 층을 정렬시킨다. 식은 단순하게 돌려야 할 각도에서 돌아간 각도를 빼서 남은 부분을 돌리도록 하였다.

 

    public float SinSqrt(float inAngle, int count)
    {
        return Mathf.Sin(Mathf.Deg2Rad * (inAngle + 45 * count)) * Mathf.Sqrt(2);
    }
    public float CosSqrt(float inAngle, int count)
    {
        return Mathf.Cos(Mathf.Deg2Rad * (inAngle + 45 * count)) * Mathf.Sqrt(2);
    }
    public float Sin(float inAngle, int count)
    {
        return Mathf.Sin(Mathf.Deg2Rad * (inAngle + 45 * count));
    }
    public float Cos(float inAngle, int count)
    {
        return Mathf.Cos(Mathf.Deg2Rad * (inAngle + 45 * count));
    }
    public float RotateToRightAngle(float inAngle)
    {
        return Mathf.Round(inAngle / 90) * 90;
    }
}

해당 함수들은 삼각함수를 계산하기 위해 작성한 함수들이다.