호우동의 개발일지

Today :

article thumbnail

이 글은 포스팅 #6과 포스팅 #7과 연결된 마지막 포스팅이다.
광폭화패턴에서 플레이어를 실질적으로 공격하는 액션이다.


구현 목표


구현 목표

일정 시간마다 플레이어가 있는 곳에 낙뢰를 떨어뜨린다.


낙뢰를 떨어뜨리기 전에
그 포인트에 낙뢰의 범위만큼 빨간색으로 경고를 준다. 

그리고 1초 후 그 포인트에 번개를 떨어뜨린다.
이러한 액션을 보스가 죽을 때까지 반복한다.

 

 


구현 과정


1. 낙뢰 이펙트 세팅

스크립트 작업에 들어가기 앞서 낙뢰처럼 보이기 위한
적절한 이펙트를 찾고 파티클 시스템을 설정해야 한다.

낙뢰 이펙트

해당 파티클이 적당해 보인다.
근데 너무 재생시간이 짧아서 좀 더 길게 늘여줬다.

 

파티클 인스펙터

Duration과 StartLifeTime을
2배로 늘려주면 재생시간만 두배로 늘어나게 된다.

 

색 수정
색 수정

 

Collision 인스펙터

핵심적으로 Particle System에서 Collision을 켜줘야 한다.
왜냐하면 해당 이펙트로 대미지를 받을 수 있게 할 것이기 때문이다.

플레이어와 이펙트 간의 충돌을 켜주려면
Send Collision messages를 켜줘야 한다.

그리고 Type을 World로 바꿔주고
Collides With를 Player 레이어로 연결해 준다.

레이어 조절

그리고 Order in Layer 값을 Player보다 높게 잡아줘서
낙뢰 이펙트가 플레이어를 가릴 수 있게 해 준다.

이렇게 하면 대략적인 낙뢰 이펙트 세팅은 끝났다.

 


2. 플레이어 추적 및 이펙트 활성화

하위 오브젝트 설정

위에서 세팅을 끝낸 낙뢰 이펙트들을
Thunder이라는 빈 오브젝트에 모아뒀다.그리고 보스인 Black Bishop의 하위 오브젝트로 설정했다.

낙뢰가 치게 하는 방법은

1. MagicCloud.cs에서 Player의 좌표를 주기적으로 찾는다.
2. 찾은 Player의 좌표에 Thunder 오브젝트를 위치시킨다.
3. 일정 시간마다 낙뢰 이펙트를 활성화시킨다.

이런 로직을 사용할 것이다.
이제 스크립트로 넘어가 보자.

public class MagicCloud : MonoBehaviour
{
    [SerializeField] GameObject thunder;

    private const float THUNDER_POSITION_Y = -6.3F; // 낙뢰 위치 Y값 오프셋
    private const float THUNDER_DELAY = 7F;

    private ParticleSystem[] effects;
    private Transform target;
    private void Start()
    {
        effects = thunder.GetComponentsInChildren<ParticleSystem>();
        Invoke("StartThunderField", 4f);
        InvokeRepeating("UpdateTarget", 4f, 0.5f);
        originLightColor = fieldLight.color;
    }
    //플레이어 위치 주기적 갱신
    private void UpdateTarget()
    {
        RaycastHit[] hits;
        hits = Physics.BoxCastAll(transform.position, new Vector3(60, 60, 60),
        Vector3.down, Quaternion.identity, Mathf.Infinity);
        if (hits.Length <= 0) return;

        foreach (RaycastHit hit in hits)
        {
            PlayerControl newTarget = hit.collider.GetComponent<PlayerControl>();
            if (newTarget != null) target = newTarget.transform;
        }   
    }
    // 낙뢰 담당 함수
    private void StrikeThunder()
    { 
        Vector3 playerPos = target.position;
        int cellSize = GameManager.Instance.CELL_SIZE;
        playerPos = playerPos * cellSize / cellSize; // 위치 셀 사이즈화
        thunder.transform.SetParent(null);
        Vector3 thunderPos = new Vector3(playerPos.x, THUNDER_POSITION_Y, playerPos.z);
        thunder.transform.position = thunderPos;
        StartCoroutine(StrikeAction());
    }
    // 낙뢰 액션 코루틴
    private IEnumerator StrikeAction()
    {
        foreach (ParticleSystem effect in effects) effect.Play();
    }
}

우선 직접참조로 오브젝트 thunder를 받는다.

이펙트들은 thunder.GetComponentsInchildrens로
참조 없이 thunder의 하위 오브젝트로 찾도록 한다.

MagicCloud가 활성화되면 InvokeRepeating으로 인해
4초 후 UpdateTarget 함수가 0.5초마다 반복된다.

UpdateTarget함수를 보면 BoxCastAll을 사용하는데,
굳이 배열로 받는 이유는 나중에 로컬이나 멀티플레이로의 확장가능성을
 고려해 뒀다.


이 말이 뭐냐면 플레이어가 여러 명일 때 오류를 일으키지 않을까?
하는 생각이 있어서 일단 배열로 받도록 해뒀다.

Physics.BoxCastAll(기준 위치, 각 사각형의 절반크기, 발사방향, 회전 각도, 최대 거리)이다.


위 메서드를 고려해서 코드를 짜주고
그 raycast Hit이 PlayerControl을 가지고 있다면 Player로 간주하고
그 오브젝트의 Transform을 Target으로 반환한다.

StrikeThunder은 번개를 쏠 때 쓰는 함수인데,
위치를 지정할 때 사용한다.


그냥 위치를 가져다 쓰는 게 아니라
셀 단위화로 변환해서 사용하고 오프셋을 적용시킨다.

그리고 thunder오브젝트는 Bishop의 자식이기 때문에 상대좌표이다.

즉 Bishop의 움직임에 따라 따라 움직일 수 있기 때문에
이 상태를 해제해줘야 한다.

이런 작업등을 해주고
실질적인 StrikeAction코루틴을 활성화한다.



3. 타격 포인트 표시

public class MagicCloud : MonoBehaviour
{
    private const float THUNDER_RADIOUS = 6F;
    private const float ALERT_DELAY = 1F;
    
    private IEnumerator StrikeAction()
        {
            RaycastHit[] hits;
            List<Cell> list = new List<Cell>();
            hits = Physics.SphereCastAll(thunder.transform.position,
                THUNDER_RADIOUS, Vector3.down, 5f, LayerMask.GetMask("Ground"));

            if(hits.Length > 0)
            {
                foreach(RaycastHit hit in hits)
                {
                    Cell cell = hit.collider.GetComponent<Cell>();
                    if (cell != null)
                    {
                        list.Add(cell);
                        cell.ChangeColor(cell.hazardColor);
                    }
                }
            }
            yield return new WaitForSeconds(ALERT_DELAY);
            foreach (ParticleSystem effect in effects) effect.Play();
            foreach (Cell cell in list) cell.ChangeColor(cell.originColor);
        }
}

public class Cell : MonoBehaviour
{
    public Color originColor; // 원래 색깔
    public Color hazardColor; // 빨간색
    
    // 경고를 위한 색 변경
    public void ChangeColor(Color color)
    {
        MeshRenderer mesh = GetComponent<MeshRenderer>();
        Color newColor = mesh.material.color;
        newColor = color;
        mesh.material.color = newColor;
    }
}

위에서의 StrikeAction 코루틴을 수정했다.

여기서 List를 사용하여 범위 안에 든 Cell을 담아두었다.
왜냐하면 색이 변한 후 다시 원래 상태로 돌려줘야 하기 때문이다.

SpehreCastAll(원의 중심위치, 반지름, 발사방향, 최대 거리, 레이어 필터)
이 메서드 규칙에 따라 코드를 정의하였다.

Physics.SphereCastAll을 사용하여 원 모양으로 해서
THUNDER_RADIOUS인 반지름만큼 안에 있는 셀인데,
레이어가 "Ground"인 것만 인식한다.

그런 레이캐스트를 아래방향으로 5f만큼 쏘는 것이다.

이렇게 해석한다.

그 레이캐스트와 부딪히는 게임오브젝트가 Cell 스크립트를 가지고 있다면
리스트에 넣고
 그 안에 있는 ChangeColor이라는 함수를 호출한다.

밑에 ChangeColor함수를 적어놨긴 한데,
대충 그냥 색을 바꾸는 함수이다.


그렇게 모두 색을 바꾼 후 이펙트를 플레이한다.

그리고 ALERT_DELAY인 1초간 대기 후
다시 원래 색으로 돌아온다.



4. 마무리 세팅

이제 이런 액션을 보스가 죽으면 멈추게끔 해야 한다.

그리고 보스가 죽으면 붉게 변했던 화면을 다시 원래 상태로 되돌려야 한다.

 public void RemoveMagicCloud()
    {
        fieldLight.color = originLightColor;
        CancelInvoke();
        gameObject.SetActive(false);

    }

MagicCloud.cs에 해당 함수를 넣는다.

#7 포스팅에서 했던 fieldLight의 color을 다시 원래의 색깔로
그리고 InvokeRepeat()하고 있었던 함수들을 모조리 취소한다.

그리고 MagicCloud 오브젝트를 비활성화한다.

public class Boss_Bishop : ABoss
{   
   protected override void Die()
    {
        base.Die();
        magicCloud.RemoveMagicCloud();
    }
}

이 함수를 Boss_Bishop 스크립트에서
Die()가 호출될 때 같이 실행되도록 한다.


구현 결과


결과1
결과2

정상적으로 타격범위가 표시되고 이펙트가 보인다.

그리고 보스가 죽자마자 다시 화면이
원래 조명으로 돌아오는 것을 확인할 수 있다.