본문 바로가기

Unity/로봇 체스 개발 일지

[Unity] 스킬 구현(1) : 표지판 디자인, 배치 알고리즘, Skill

요약

표지판을 디자인한 후 표지판 배치 알고리즘 작성하였다. 표지판을 배치하기 위해 해당 공간을 사용할 수 있는지, 어떤 표지판을 배치하는지 판단하기 위해 CheckAndPlaceSelection()을 만들었지만 매개변수가 많아 간략화한 오버 로딩 함수를 작성하였다. 이를 통해 각 스킬의 배치 알고리즘을 작성하였다. 추가로 스킬을 사용횟수를 제한하는 함수를 통해 스킬을 제거할 수 있다.


표지판 디자인

*몬스터 소환 구역은 추후 글 작성예정


표지판 배치 알고리즘

칼, 권총, 저격총: 사거리 표기를 통해 사거리를 알린 후 공격 선택을 통해 위치를 선택하면 가장 앞의 몬스터를 공격
산탄총: 공격 선택을 통해 위치를 선택하면 좌우 혹은 상하에 피해를 줌
광선총: 상하좌우 중 한 곳을 선택하면 그곳을 기준으로 맵 끝까지 피해를 줌


표지판 배치 코드

표지판 배치를 하기 위해 배치 공간에 경우에 따라 배치할 표지판 종류를 고르는 함수를 작성하였다.

Skill.cs

public class Skill : MonoBehaviour
{
    ... 생략 ...
    // 물체가 앞에 있어 막히는 경우 사용
    private bool CheckAndPlaceSelection(bool condition, int x, int y, bool isDamagedArea, bool isSelection = true)
    {
        if (condition)
        {
            if (Instance.Map2D[x, y] == (int)MapObject.moster)
            {
                if (isDamagedArea)
                {
                    PlaceSelection(x, y, PoolManager.Prefabs.DamagedArea);
                }
                if (isSelection)
                {
                    PlaceSelection(x, y, PoolManager.Prefabs.Selection);
                }
                return false;
            }
            else
            {
                PlaceSelection(x, y, PoolManager.Prefabs.UnSelection);
            }
        }
        return true;
    }

CheckAndPlaceSelection()의 매개변수는 배치할 수 있는 공간인지, 공간 좌표 x, y, 데미지 공간 인지, 선택 공간 인지 선택을 할 수 있다. 그러나 이는 매개변수가 많다고 생각해 오버 로딩하여 다음과 같은 함수를 만들었다.

 

    // 직선 범위공격시
    private void CheckAndPlaceSelection(bool condition, int x, int y, int index)
    {
        if (condition)
        {
            if (index == 1)
            {
                if (Instance.Map2D[x, y] == (int)MapObject.moster)
                {
                    PlaceSelection(x, y, PoolManager.Prefabs.DamagedArea);
                }
                PlaceSelection(x, y, PoolManager.Prefabs.Selection);
            }
            else if(Instance.Map2D[x, y] == (int)MapObject.moster)
            {
                PlaceSelection(x, y, PoolManager.Prefabs.DamagedArea);
            }
            else
            {
                PlaceSelection(x, y, PoolManager.Prefabs.UnSelection);
            }
        }
    }

    private void PlaceSelection(int x, int y, PoolManager.Prefabs prefabType)
    {
        Instance.poolManager.SelectPool(prefabType).Get().transform.position = new Vector3(x, y, -1);
    }
}

CheckAndPlaceSelection()을 오버 로딩한 매개변수는 배치할 수 있는 공간인지, 공간 좌표 x, y, 플레이어 근처인지(index == 1) 와 같은 선택을 할 수 있다.

PlaceSelection()는 그저 오브젝트를 소환할 때 좌표 지정을 편리하게 하려고 만들었다.


배치 공간 판단

표지판 배치 코드는 알고리즘을 보면 3가지로 분류하여 작성할 수 있다. 이를 위해 AttackType을 열거형으로 3가지의 종류를 만들고 그에 따른 알고리즘을 작성하였다.

Skill.cs

public class Skill : MonoBehaviour
{
    public enum AttackType
    {
        Normal,
        Schrotflinte,
        LaserGun
    }
    ... 생략 ...
    public void UnifiedAttackRange(int size, AttackType attackType, Vector3Int targetPos = new Vector3Int(), Vector2Int startPos = new Vector2Int(), Vector2Int endPos = new Vector2Int())
    {
        targetPos = Instance.PlayerPositionInt;
        startPos = Vector2Int.zero;
        endPos = new Vector2Int(Instance.MapSizeX - 1, Instance.MapSizeY - 1);

        bool downSide = targetPos.y > startPos.y;
        bool upSide = targetPos.y < endPos.y;
        bool leftSide = targetPos.x > startPos.x;
        bool rightSide = targetPos.x < endPos.x;

        // Up, Down, Left, Right
        bool[] isDamagedArea = new bool[4] { true, true, true, true };
        for (int index = 1; index <= size; index++)
        {
            int downPos = targetPos.y - index;
            int upPos = targetPos.y + index;
            int leftPos = targetPos.x - index;
            int rightPos = targetPos.x + index;

            bool downPosSide = downPos > startPos.y && downPos < endPos.y;
            bool upPosSide = upPos > startPos.y && upPos < endPos.y;
            bool leftPosSide = leftPos > startPos.x && leftPos < endPos.x;
            bool rightPosSide = rightPos > startPos.x && rightPos < endPos.x;

up, down, left, rightSide: 주어진 위치가 맵 안인지 확인하기 위한 변수
up, down, left, rightPos: 주어진 위치의 상하좌우의 값을 저장하기 위한 변수
up, down, left, rightPosSide: 주어진 위치의 상하좌우가 맵 안인지 확인하기 위한 변수
isDamagedArea[]: 칼, 권총, 저격총이 앞에서 데미지가 들어갔다는 것을 확인하기 위한 배열

위 변수들을 통해 배치할 수 있는 공간인지 판단한다.

switch (attackType)
{
    case AttackType.Normal:
        {
            if (isDamagedArea[0])
            {
                isDamagedArea[0] = CheckAndPlaceSelection(upPosSide && leftSide && rightSide, targetPos.x, upPos, isDamagedArea[0]);
            }
            else
            {
                CheckAndPlaceSelection(upPosSide && leftSide && rightSide, targetPos.x, upPos, isDamagedArea[0]);
            }
            if (isDamagedArea[1])
            {
                isDamagedArea[1] = CheckAndPlaceSelection(downPosSide && leftSide && rightSide, targetPos.x, downPos, isDamagedArea[1]);
            }
            else
            {
                CheckAndPlaceSelection(downPosSide && leftSide && rightSide, targetPos.x, downPos, isDamagedArea[1]);
            }
            if (isDamagedArea[2])
            {
                isDamagedArea[2] = CheckAndPlaceSelection(leftPosSide && upSide && downSide, leftPos, targetPos.y, isDamagedArea[2]);
            }
            else
            {
                CheckAndPlaceSelection(leftPosSide && upSide && downSide, leftPos, targetPos.y, isDamagedArea[2]);
            }
            if (isDamagedArea[3])
            {
                isDamagedArea[3] = CheckAndPlaceSelection(rightPosSide && upSide && downSide, rightPos, targetPos.y, isDamagedArea[3]);
            }
            else
            {
                CheckAndPlaceSelection(rightPosSide && upSide && downSide, rightPos, targetPos.y, isDamagedArea[3]);
            }
            break;
        }

칼, 권총, 저격총이 사용하는 알고리즘으로 상하좌우에 공격 범위 만큼 배치 할 수 있다. 특이한 점은 isDamagedArea[]을 통해 미리 앞에 판정이 들어가면 뒤에는 데미지 범위 판정이 안되도록 하였다.

 

case AttackType.LaserGun:
    {
        CheckAndPlaceSelection(upPosSide && leftSide && rightSide, targetPos.x, upPos, index);
        CheckAndPlaceSelection(downPosSide && leftSide && rightSide, targetPos.x, downPos, index);
        CheckAndPlaceSelection(leftPosSide && upSide && downSide, leftPos, targetPos.y, index);
        CheckAndPlaceSelection(rightPosSide && upSide && downSide, rightPos, targetPos.y, index);
        break;
    }

광선총은 사용하면 단순히 플레이어 주위만 공격 선택 표지판을 배치하고 나머지는 사거리 표기랑 공격 범위 표기를 하도록 하였다.

 

case AttackType.Schrotflinte:
    {
        CheckAndPlaceSelection(upPosSide && leftSide && rightSide, targetPos.x, upPos, index);
        CheckAndPlaceSelection(downPosSide && leftSide && rightSide, targetPos.x, downPos, index);
        CheckAndPlaceSelection(leftPosSide && upSide && downSide, leftPos, targetPos.y, index);
        CheckAndPlaceSelection(rightPosSide && upSide && downSide, rightPos, targetPos.y, index);

        CheckAndPlaceSelection(leftPosSide && targetPos.y + 1 < endPos.y && downSide, leftPos, targetPos.y + 1, true, false);

        CheckAndPlaceSelection(leftPosSide && upSide && targetPos.y - 1 > startPos.y, leftPos, targetPos.y - 1, true, false);

        CheckAndPlaceSelection(rightPosSide && targetPos.y + 1 < endPos.y && downSide, rightPos, targetPos.y + 1, true, false);

        CheckAndPlaceSelection(rightPosSide && upSide && targetPos.y - 1 > startPos.y, rightPos, targetPos.y - 1, true, false);

        break;
    }
default:
    break;
... 생략 ...

산탄총은 특이하게도 UnifiedAttackRange()를 오버 로딩된 것과 혼용하여 사용해야 한다. 특히 대각선에도 공격 범위가 해당하므로 판정 또한 상하좌우로 하였다.


스킬 사용 횟수 확인

public void CheckUsage()
{
    if(Usage >= UsageLimit)
    {
        transform.parent = null;
        if(myObject.transform.localScale.x < 0)
        {
            myObject.transform.localScale = new Vector3(-myObject.transform.localScale.x, myObject.transform.localScale.y, 0);
        }
        myObject.transform.rotation = Quaternion.Euler(Vector3Int.zero);
        Usage = 0;
        myObject.Destroy();
    }
}

실제로 스킬이 사용된 횟수를 판단하고 지정된 제한 횟수를 지나면 파괴되도록 설정되어 있다. 파괴될 때는 재사용 되기 위해 Pool Manager로 돌아감으로 부모 오브젝트를 Null로 하여 재사용에 문제가 없도록 하였다.