Unity/로봇 체스 개발 일지

[Unity] 스킬 구현(3) : 광선총 발사 알고리즘

suppresswisely 2025. 1. 14. 15:54

요약

광선총 알고리즘을 만들기 위해 레이저와 총, 레이저 포인터를 만들어 코드를 작성할 때 포인터를 기준으로 광선총을 회전시키며 레이저를 늘렸다. 레이저를 늘릴 때 이상한 식을 사용하는데, 이는 Unit 단위와 Scale을 1대 1로 대응시키기 위함이다. 이 방법은 레이저의 Unit 단위를 Inspector에서 대응시키지 않아 발생한 문제로, 이상한 식을 사용하는 것보다 이미지 픽셀 단위를 Unit 단위와 대응하게 만드는 것이 좋다.


광선총 발사 알고리즘

광선총은 레이저과 총부분으로 광선이 지나간 곳에는 데미지 판을 놓아둬 데미지를 입히는 방식으로 동작한다.

이를 구현하기 위한 알고리즘은 다음과 같다.

1. 총을 지정된 좌표의 반대편 맵 끝으로 향하게 하면서 총구 방향을 시작 지점에 둔다.

2. 지정된 좌표 만큼 광선의 길이를 늘린다.

3. 총구를 맵끝까지 천천히 각도를 돌리면서 광선을 늘린다.

4. 광선이 지나간 좌표에 몬스터가 존재하면 데미지 판을 생성한다.


LaserGun.cs

public class LaserGun : Skill, IState
{
    ... 생략 ...
    public void Entry()
    {
        laserPoint = GameObject.Find("LaserPoint");
        UnifiedAttackRange(Instance.MapSizeX, AttackType.LaserGun);
    }

시작 부분에는 배치 알고리즘과 레이저 포인트가 있다. 레이저 포인트는 광선총을 실제로 돌리는 것보다 바라보게 하여 회전하도록 하기 위함이다.

 

    public void IStateUpdate()
    {
        string direction = "";
        if (ready)
        {
            transform.position = new Vector3(
                Instance.player.transform.position.x + (Instance.player.transform.position.x - Instance.MousePos.x),
                Instance.player.transform.position.y + (Instance.player.transform.position.y - Instance.MousePos.y), 0);
            Instance.action.UpdateLookAtTarget(Instance.MousePos, transform, 0.001f, 30f);
        }
        if (!start && UpdateSelectionCheck())
        {
            direction = DirectionCheck(Instance.MyHit.positionInt);
            if (direction == "Right")
            {
                targetPos = new Vector3(0.5f, 10.5f, 0);
            }
            if (direction == "Left")
            {
                targetPos = new Vector3(10.5f, 0.5f, 0);
            }
            if (direction == "Up")
            {
                targetPos = new Vector3(0.5f, 0.5f, 0);
            }
            if (direction == "Down")
            {
                targetPos = new Vector3(10.5f, 10.5f, 0);
            }
            ready = false;
            start = true;
        }

DirectionCheck()를 통해 사분면을 나누고, 어느 사분면을 클릭했는지에 따라 총을 어디에 둘지 좌표를 가져온다.

 

        if (start)
        {
            bool move = !Instance.action.UpdateMove(transform, targetPos, 0.001f, 30f);
            bool look = !Instance.action.UpdateLookAtTarget(Instance.MyHit.positionInt, transform, 0.001f, 30f);
            laserPoint.transform.position = Instance.MyHit.positionInt;
            if (move && look)
            {
                direction = DirectionCheck(Instance.MyHit.positionInt);
                if (direction == "Right")
                {
                    targetPos = new Vector3(Instance.MapSizeX - 2, Instance.MyHit.positionInt.y, 0);
                }
                if (direction == "Left")
                {
                    targetPos = new Vector3(1, Instance.MyHit.positionInt.y, 0);
                }
                if (direction == "Up")
                {
                    targetPos = new Vector3(Instance.MyHit.positionInt.x, Instance.MapSizeY - 2, 0);
                }
                if (direction == "Down")
                {
                    targetPos = new Vector3(Instance.MyHit.positionInt.x, 1, 0);
                }
                Usage++;
                skillUse = true;
                start = false;
            }
        }

움직임과 바라보기가 끝나면, 사분면에 따라 레이저 포인트를 위치시킬 좌표를 가져온다.

 

        if (skillUse)
        {
            Instance.action.UpdateMove(laserPoint.transform, targetPos, 0.001f, 10f);
            float line = Vector2.Distance(transform.position, laserPoint.transform.position);
            transform.GetChild(0).transform.localScale = new Vector3(line /2 + 0.5f, 0.15f, 0);
            Instance.action.UpdateLookAtTarget(laserPoint.transform.position, transform,0.001f, 10f);

            Vector3Int laserPointrPosInt = new Vector3Int(
                (int)Mathf.Round(laserPoint.transform.position.x),
                (int)Mathf.Round(laserPoint.transform.position.y),
                (int)Mathf.Round(transform.position.z)
            );
            if (laserPointPos != laserPointrPosInt)
            {
                laserPointPos = laserPointrPosInt;
                if (Instance.Map2D[laserPointrPosInt.x, laserPointrPosInt.y] == (int)MapObject.moster)
                {
                    Instance.poolManager.SelectPool(PoolManager.Prefabs.CrashBoxObject).Get().transform.position
                        = laserPointPos;
                }
            }
            if (Vector2.Distance(laserPoint.transform.position, targetPos) < 0.001f)
            {
                transform.GetChild(0).transform.localScale = new Vector3(0, 0, 0);
                Instance.playerState = State.Idle;
                ready = true;
                CheckUsage();
            }
        }
    }
    ... 생략 ...
}

가져온 좌표를 통해광선을 적절하게 늘리면서 레이저 포인트는 앞으로 나간다. 이때 적절하게 늘린다는 것이 의문일 수 있는데 적절하게는 line /2 + 0.5f, 0.15f이다. 이러한 식이 나온 이유는 다음과 같다.


광선 늘리기

광선은 특별한 것이 있는 것은 아니다. 단순히 빨간색 정사각형을 늘린 것에 불과하다. 이러한 정사각형을 유니티에 넣게 되면 Pivot을 설정할 수 있는데, 이 설정을 왼쪽으로 두면 Scale을 늘릴 때 왼쪽 끝을 기준으로 늘어나 편리하게 사용할 수 있다.

그런데 레이저는 정확하게 유니티의 Unit 단위가 Scale.x크기만큼 정확하게 맞지 않는다.

이를 해결하는 방법은 두 가지 정도를 생각하였는데, 한 가지는 위와 같이 이상한 식을 사용하는 것이고, 다른 하나는 Unit 단위를 맞추는 것이다.


Unit 단위 맞추기

레이저 이미지의 속성에서 크기 정보를 보면 320픽셀임을 알 수 있었다.

픽셀을 Pixels Per Unit에 넣어 적용하면 N 픽셀당 1 Unit으로 바꿔준다. 이를 통해 Unit과 Scale.x이 1대 1로 대응되는 것을 볼 수 있다. (x 좌표가 0인 경우)

그렇다면 간단하게 Unit을 맞추면 되는것을 굳이  line /2 + 0.5f, 0.15f을 사용하는 이유는 이미 다른 이미지는 Unit을 맞추지 않고 Scale이 늘어난 상태로 사용하였기에 레이저만 Unit을 맞추기 곤란하였다.

 

실제로 다른 오브젝트들을 보여주면 Scale이 늘어난 상태로 사용된다. 이는 코드를 작성할 때 큰 혼란이 오게 하므로 권장하지 않는 방법이라 생각한다. 필자는 잘 몰라서 사용했던 방법임을 알린다.