본문 바로가기
프로그래밍/유니티

[유니티/C#] Raycast를 활용해 클릭한 대상 공격하기

by HS-Coder 2022. 1. 7.
728x90

클릭한 대상을 공격하게 하는 것은 클릭한 위치로 이동하는 것과 코드가 상당히 비슷하다.

 

다만, 공격하려는 대상이 나무나 다른 오브젝트에 가려져 있을 때도

대상을 정확히 공격할 수 있도록 ray가 hit한 모든 대상 중에 특정한 target만 추려내는 것이 필요하다. 

이때 사용할 함수는 Physics.RaycastAll()이다.

Physics.Raycast와의 차이는 ray가 hit한 모든 대상의 정보가 저장된다는 것. 복수의 정보를 저장해야 하니 RaycastHit[]처럼 배열(array)을 사용해야 한다. 

 

코드 작성에 앞서 유니티 상에서 target으로 삼을 대상에 CombatTarget이라는 스크립트를 작성해 붙여준다.

다순히 target을 판별하기 위한 것이므로 CombatTarget에 별도의 코딩을 필요 없다.

 

RaycastHit[] hits = Physics.RaycastAll(Camera.main.ScreenPointToRay(Input.mousePosition));
            
            foreach (RaycastHit hit in hits)
            {
                // hit 중 CombatTarget 스크립트가 있는 녀석을 target으로 설정 
                CombatTarget target = hit.transform.GetComponent<CombatTarget>();

                // null이면 foreach문 계속 진행 
                if (target == null) continue; 
                if (Input.GetMouseButtonDown(0))
                {
                    // 클릭 시 target을 인자로 Attack함수 실행
                    GetComponent<Fighter>().Attack(target); 
                }

Ray를 쏴서 ray가 hit한 대상을 hits라는 배열에 저장 -> foreach 문을 사용해서 hit 대상에서 CombatTarget 클래스를 가지고 있는 녀석을 target으로 설정 -> 마우스 우클릭을 시 target을 인자로 Figther 클래스의 Attack을 호출

 

여기서 중요한 것은 transform에서 GetComponenet를 사용할 수 있다는 점과 반복문에서 'continue'의 사용!

 

위의 코드는 PlayerController라는 클래스에서 구현했고, 실제 공격 관련 로직은 Fighter 클래스를 별도로 구현했다.

마찬가지로, 움직임 관련 로직은 Mover 클래스에서 구현했다.

 

이렇게 구분하는 이유는 dependency를 줄이고 추후 AI 로직(Enemy Controller)을 구현할 때, Fighter와 Mover를 그대로 재활용하기 위함이다. 

 

Fighter 클래스를 한 번 살펴보면,

[SerializeField] float attackRange = 2;
        [SerializeField] Transform target;

        void Update()
        {
            // target이 없으면 return으로 update 함수 조기 종료 
            if (target == null) return;

            // attackrange 보다 target과의 거리가 짧으면 true 아니면 false
            bool inAttackRange = Vector3.Distance(transform.position, target.position) < attackRange;
            if (target!=null && !inAttackRange)
            {
               // attackRange 밖에 있으면 target으로 이동
                GetComponent<Mover>().MoveTo(target.position); 
            }
            else
            {
               // attacRange 안 이면 대상을 바라보고, 이동은 멈춘 후 공격
                transform.LookAt(target);
                target = null;
                GetComponent<Mover>().Stop(); 
                GetComponent<Animator>().SetTrigger("Attack");
            }
        }
        public void Attack(CombatTarget combatTarget)
        {
            target = combatTarget.transform; 
        }

​메소드 추출하기로 더 다듬어야 할 부분이 많지만 일단 기본적은 로직은 아래와 같다,

1) target이 없으면 update 함수 조기 종료 + null reference 오류 방지 

2) attack range를 설정해 range 안에 들어가면 멈추기 (아니면 적과 캐릭터가 겹쳐버린다)

3) 반대로, attack range 밖에서 적을 클릭했을 때는 attack range까지 적을 향해 이동

4) attack range에 들어가면 animation 을 trigger 해서 공격 실행 (+대미지 입히기)

 

728x90