본문 바로가기

Unity/로봇 체스 개발 일지

[Unity] 스킬 구현(4) : 공중 지원 스킬 알고리즘

요약

공중 지원 스킬을 구현하기 위해 표지판 배치와 발사 알고리즘을 스킬에 맞게 새로 만들었다. 배치 알고리즘은 마우스 위치를 따라 다니며 원하는 위치를 지정할 수 있게 하였고, 발사 알고리즘은 전투기 그림자가 지나가면서 가짜 총알을 5구역에 랜덤으로 발사하며 데미지 판을 생성하게 하였다..


표지판 배치 알고리즘

공격 선택 표지판은 마우스를 따라가므로 그 주변에는 데미지 범위와 사거리 표기를 한다. 이후 마우스를 클릭하면 발사 알고리즘이 작동한다.


발사 알고리즘

공중 지원인 만큼 전투기가 기관총으로 지원하는 형태를 가지려고 한다. 그렇기에 전투기 그림자가 지나간 후 총알이 날아가는 형식으로 구현할 예정이다.

1. 45도 기울어진 전투기 그림자를 생성한다.

2. 전투기 그림자를 클릭한 좌표보다 아래에 위치하는 1차 함수를 구한다.

3. 구한 1차 함수를 통해 시작점에 전투기 그림자를 위치시키고 이동시킨다.

4. 구한 1차 함수를 통해 가짜 총알을 위치시키고, 랜덤으로 목적지에 벗어나지 않는 범위에 값을 지정한다.

5. 랜덤으로 생성된 목적지와 가짜 총알의 위치를 통해 각도를 구한다.

6. 0.8초후에 다량의 가짜 총알을 발사 시킨다.

7. 데미치 판을 소환하에 로봇을 제거한다.


배치 코드

RangedAttack.cs

public class RangedAttack : MonoBehaviour, IState
{
    ... 생략 ...
    bool mousePosUp, mousePosDown, mousePosLeft, mousePosRight, mousePosMiddle;
    ... 생략 ...
    public void IStateUpdate()
    {
        ... 생략 ...
        AttackRange(1, CurrentMousPos, Vector2Int.zero, new Vector2Int(Instance.MapSizeX - 1, Instance.MapSizeY - 1));
    }
    ... 생략 ...
    public void AttackRange(int size, Vector3Int targetPos, Vector2Int startPos, Vector2Int endPos)
    {
        bool downSide = targetPos.y > startPos.y;
        bool upSide = targetPos.y < endPos.y;
        bool leftSide = targetPos.x > startPos.x;
        bool rightSide = targetPos.x < endPos.x;

        if (downSide && upSide && rightSide && leftSide)
        {
            Instance.poolManager.SelectPool(PoolManager.Prefabs.Selection).Get().transform.position
                = new Vector3(targetPos.x, targetPos.y, -1);
            mousePosMiddle = false;
            if (Instance.Map2D[targetPos.x, targetPos.y] == (int)MapObject.moster || Instance.Map2D[targetPos.x, targetPos.y] == (int)MapObject.player)
            {
                Instance.poolManager.SelectPool(PoolManager.Prefabs.DamagedArea).Get().transform.position
                = new Vector3(targetPos.x, targetPos.y, 0);
                mousePosMiddle = 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;


            if (downPosSide && leftSide && rightSide)
            {
                if (Instance.Map2D[targetPos.x, downPos] == (int)MapObject.moster || Instance.Map2D[targetPos.x, downPos] == (int)MapObject.player)
                {
                    Instance.poolManager.SelectPool(PoolManager.Prefabs.DamagedArea).Get().transform.position
                        = new Vector3(targetPos.x, downPos, -1);
                    mousePosDown = true;
                }
                else
                {
                    Instance.poolManager.SelectPool(PoolManager.Prefabs.UnSelection).Get().transform.position
                        = new Vector3(targetPos.x, downPos, -1);
                    mousePosDown = false;
                }
            }
            ... 생략 ...
        }
    }
}

코드를 보면 리팩토링이 시급해 보인다. 그럼에도 설명을 하자면, 배치 방법은 다른 스킬 배치 알고리즘과 유사하지만 중간에만 공격 선택을 할 수 있어서 for문 이전에만 공격 선택을 생성하는 함수가 보인다. 특이하게도 mousePosUp, mousePosDown, mousePosLeft, mousePosRight, mousePosMiddle 변수가 보이는데, 이는 데미지 범위 안에 몬스터가 있는 경우 true로 저장되며, 나중에 총알을 발사할 때 데미지 판을 생성할 위치만 지정해 주는 방식이다.


발사 코드

RangedAttack.cs

public class RangedAttack : MonoBehaviour, IState
{
    ... 생략 ...
    int numZ, numY = - 4;
    bool mousePosUp, mousePosDown, mousePosLeft, mousePosRight, mousePosMiddle;
    ... 생략 ...
    public void IStateUpdate()
    {
        ... 생략 ...
        if (Instance.MyHit != null && Instance.MyHit.name.StartsWith("Selection"))
        {
            Instance.MyHit.name = "";
            start = true;
            numZ = mousePosInt.y + mousePosInt.x;
            fighterPlaneShadow = Instance.poolManager.SelectPool(PoolManager.Prefabs.FighterPlaneShadow).Get();
            fighterPlaneShadow.gameObject.transform.position = new Vector3Int(-100, -100, 0);
            fighterPlaneShadow.transform.position = new Vector3Int(-numY + numZ - 4, numY, 0);

            Instance.poolManager.AllDistroyMyObject(PoolManager.Prefabs.Selection);
            Instance.poolManager.AllDistroyMyObject(PoolManager.Prefabs.DamagedArea);
            Instance.poolManager.AllDistroyMyObject(PoolManager.Prefabs.UnSelection);
            StartCoroutine(Shoot(mousePosInt));
        }
        ... 생략 ...
    }
    ... 생략 ...
    IEnumerator Shoot(Vector3Int mousePosInt)
    {
        yield return new WaitForSeconds(0.8f);
        int index = 0;
        float time = 0.02f;
        for (index = 0; index < 10; index++)
        {
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f) + 1, mousePosInt.y + Random.Range(-0.5f, 0.5f), 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f), mousePosInt.y + Random.Range(-0.5f, 0.5f) - 1, 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f), mousePosInt.y + Random.Range(-0.5f, 0.5f), 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f) - 1, mousePosInt.y + Random.Range(-0.5f, 0.5f), 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f), mousePosInt.y + Random.Range(-0.5f, 0.5f) + 1, 0));
            yield return new WaitForSeconds(time);
        }
        if (mousePosMiddle)
        {
            Instance.poolManager.SelectPool(PoolManager.Prefabs.CrashBoxObject).Get().transform.position
                = new Vector3Int(mousePosInt.x, mousePosInt.y, 0);
        }
        if (mousePosUp)
        {
            Instance.poolManager.SelectPool(PoolManager.Prefabs.CrashBoxObject).Get().transform.position 
                = new Vector3Int(mousePosInt.x, mousePosInt.y + 1, 0);
        }
        if (mousePosRight)
        {
            Instance.poolManager.SelectPool(PoolManager.Prefabs.CrashBoxObject).Get().transform.position
                = new Vector3Int(mousePosInt.x + 1, mousePosInt.y, 0);
        }
        if (mousePosLeft)
        {
            Instance.poolManager.SelectPool(PoolManager.Prefabs.CrashBoxObject).Get().transform.position
                = new Vector3Int(mousePosInt.x - 1, mousePosInt.y, 0);
        }
        if (mousePosDown)
        {
            Instance.poolManager.SelectPool(PoolManager.Prefabs.CrashBoxObject).Get().transform.position
                = new Vector3Int(mousePosInt.x, mousePosInt.y - 1, 0);
        }
        for (index = 0; index < 10; index++)
        {
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f) + 1, mousePosInt.y + Random.Range(-0.5f, 0.5f), 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f), mousePosInt.y + Random.Range(-0.5f, 0.5f) - 1, 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f), mousePosInt.y + Random.Range(-0.5f, 0.5f), 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f) - 1, mousePosInt.y + Random.Range(-0.5f, 0.5f), 0));
            yield return new WaitForSeconds(time);
            SpawnFakeBullet(new Vector3(mousePosInt.x + Random.Range(-0.5f, 0.5f), mousePosInt.y + Random.Range(-0.5f, 0.5f) + 1, 0));
            yield return new WaitForSeconds(time);
        }
    }
    public void SpawnFakeBullet(Vector3 targetPos)
    {
        fakeBullet = Instance.poolManager.SelectPool(PoolManager.Prefabs.FakeBullet).Get();
        fakeBullet.GetComponent<FakeBullet>().targetPos = targetPos;
        fakeBullet.transform.position = new Vector3Int(-numY + numZ + 2, numY, 0);
        fakeBullet.transform.rotation = Quaternion.Euler(new Vector3(
            0, 0, TargetToAngle(targetPos)));
    }
    ... 생략 ...
}

Shoot()를 보면 코루틴을 통해 가짜 총알이 발사될 때 0.2초 간격으로 5군데 구역에 발사하는 것을 볼 수 있다. 이후 mousePos 변수들을 통해 데미지 판을 생성하고, 가짜 총알을 발사하면서 마무리된다.