본문 바로가기
게임 개발/유니티 게임 개발 일지

#7 캐릭터 구현하기 - 타겟팅 시스템

by FlowTree 2020. 2. 19.
반응형

구현 목적

  • 플레이어 캐릭터의 시야 범위 안에 있는 오브젝트를 타겟팅하여  공격 대상으로 지정하기
  • 타겟팅된 오브젝트에서 타겟팅UI를 출력하여 플레이어에게 타겟팅 시각적 정보 제공

 

구현할 기능

1.타겟팅 대상 식별(Field Of View 시스템 연계)

  • 캐릭터와 시야 범위 안에 있는 오브젝트들 사이에서 가장 가까운 거리 계산하기
  • 가장 가까운 거리의 오브젝트를 타겟팅하기

2.타겟팅UI

  • 타겟팅UI 활성화 - 타겟팅 대상일 때
  • 타겟팅UI 비활성화 - 타겟팅 대상이 아닐 때

 

구현 과정

이번 구현에서 가장 중요한 기능은 가장 가까운 거리의 계산하고 해당 오브젝트를 찾는 것이다.

foreach를 활용하여 가장 가까운 거리를 찾는 글이 있는 블로그를 참고했다.
(List, Vector3.Distance, foreach 이용)

 

링크: https://a-game-developer0724.tistory.com/51

 

가장 가까운 거리의 오브젝트를 찾은 후 해당 오브젝트의 자식 오브젝트(Canvas)에 접근하여

만들어둔 타겟팅UI를 On/Off 컨트롤하였다.

       

  • 가장 가까운 거리의 게임 오브젝트의 자식 오브젝트 Canvas에 접근
     targetingUI = targeting.transform.Find("Canvas").transform.gameObject; 
  •  Canvas 활성화 = targetingUI활성화 
     targetingUI.SetActive(true);
     비활성화는 SetActive(false);

테스트 해보니 잘되는 듯 했으나...

 

문제 발생

시야 범위 안에서 다른 타겟을 타겟팅하면 이전의 타겟팅UI가 안 꺼지는 문제 발생

https://www.youtube.com/watch?v=Ju6YqymqL98

문제를  처음 직면했을 때 도대체 뭐가 문제인지 몰랐다.

왜 모른지 생각해보니 타겟팅 시스템에 대해서 이해를 잘 못했고, 머리 속에서 시스템 구현 과정에서 뭔가 빼먹은 게 있는 것 같았다. 그래서 문제를 파악하고 관련 규칙을 추가해보기로 했다.

 

문제 해결

Field Of View 시스템과 타겟팅 시스템이 연계가 되니 내 머리속에서 잘 이해를 못한 것 같았다. 그래서 노트에 순서 별기능들을 적으면서 이해하려고 했고 이해하기 쉽도록 기능들을 간략화해서 플로우 차트로 그렸다.  플로우 차트를 보면서 기능들의 연결을 파악했고 현재 발생한 문제가 무엇인지 정리하니 부족한 부분을 찾을 수 있었다.

 

문제 원인 파악

  • 시야 범위 안에서 N회 타겟팅 중일 때(초회차 아님), 더 가까운 타겟을 보면 타겟팅은 바뀌지만, 기존 타겟팅UI는 남아있다.
  • 타겟팅UI 활성/비활성화의 상세한 규칙 필요
  • 타겟팅UI 비활성화 조건
    1.이전 타겟팅 대상이 시야 범위에 없을 때
    2.이전 타겟팅 대상이 시야 범위 안에 있을 때
      - 타겟팅 대상이 사망
      - 이전 타겟팅 대상보다 더 가까이 있는 타겟팅 대상이 있을 경우

플로우 차트

(개인 노트에 그렸지만 블로그에 올리려고 https://www.draw.io/를 이용해서 정리함)

Field Of View와 타겟팅 시스템이 어떻게 연결되는지 알기 위해서 간략화해서 그렸다. 자세한 정보는 아래의 스크립트를 통해 확인할 수 있다.

문제 원인 파악과 플로우 차트를 통해서 Field Of View와 타겟팅 시스템이 N회 실행 시

1.시야 범위 안에 있고 2.이전 targetingUI가 남아 있을 경우 3.해당targetingUI 꺼줌을 추가해서

문제를 해결했다.

 

https://www.youtube.com/watch?v=_Cic5__53xg

 

스크립트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class FieldOfView : MonoBehaviour
{
    public float viewRadius;
    [Range(0360)]
    public float viewAngle;
 
    public LayerMask targetMask;
    public LayerMask obstacleMask;
    //있어야함, 왜냐면 타겟 사이에 다른 오브젝트가 있는데 그 오브젝트를 투과해서 뒤의 타겟오브젝트를 볼 수 있음
 
    public List<GameObject> visibleTargets = new List<GameObject>();
    public GameObject targeting;
    public float shortDis;
 
    public GameObject targetingUI;
 
 
    void Start()
    { //플레이 시 FindTargetsDelay 코루틴을 실행한다. 0.2초 간격으로
        StartCoroutine("FindTargetsDelay", .2f);
    }
 
    IEnumerator FindTargetsDelay(float delay)
    {
        while (true)
        {
            yield return new WaitForSeconds(delay);
            FindTargets();
        }
    }
 
    void FindTargets()
    {
        visibleTargets.Clear();
        Collider[] targetInViewRadius = Physics.OverlapSphere(transform.position, viewRadius, targetMask);
        for (int i = 0; i < targetInViewRadius.Length; i++//ViewRadius 안에 있는 타겟의 개수 = 배열의 개수보다 i가 작을 때 for 실행
        {
            Transform target = targetInViewRadius[i].transform; //타겟[i]의 위치
            Vector3 dirToTarget = (target.position - transform.position).normalized; //vector3타입의 타겟의 방향 변수 선언 = 타겟의 방향벡터, 타겟의 position - 이 게임오브젝트의 position) normalized = 벡터 크기 정규화 = 단위벡터화
            if (Vector3.Angle(transform.forward, dirToTarget) < viewAngle / 2// 전방 벡터와 타겟방향벡터의 크기가 시야각의 1/2이면 = 시야각 안에 타겟 존재
            {
                float dstToTarget = Vector3.Distance(transform.position, target.position); //타겟과의 거리를 계산
                if (!Physics.Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask)) //레이캐스트를 쐇는데 obstacleMask가 아닐 때 참이고 아래를 실행함
                {
                    visibleTargets.Add(target.transform.gameObject); //게임오브젝트가 리스트에 들어가긴 함, 
                    Debug.DrawRay(transform.position, dirToTarget * 10f, Color.red, 5f);
                    print("raycast hit!");
                }
            }
        }
 
 
        if (visibleTargets.Count != 0//범위 안에 있는 게임오브젝트 리스트 존재 시, 거리 계산 시작
        {
            if (targetingUI != null//시야 범위 안에 있고, 이전 targetingUI가 남아 있을 경우, UI 꺼줌
            {
                targetingUI.SetActive(false);
            }
 
            targeting = visibleTargets[0];
            //첫번째를 지정, 첫번째는 타겟팅 대상, 실행했을 때 리스트에 값이 없으면 ArgumentOutOfRangeException 에러가 나옴
                /*visibleTargets[0] != null visibleTargets[0]의 값이 비어있지 않으면 실행하려고 했으나 계속 에러 발생.
              * 리스트 개수를 통해 해결함 visibleTargets.Count != 0
                */
            shortDis = Vector3.Distance(transform.position, visibleTargets[0].transform.position); //visibleTargets 리스트의 첫번째와의 거리를 기준으로 잡기
 
            //리스트에서 가장 가까운 거리의 게임 오브젝트 찾기
            foreach (GameObject found in visibleTargets)
            {
                float distance = Vector3.Distance(transform.position, found.transform.position);
                if (distance < shortDis)
                {
                    targeting = found;
                    shortDis = distance;
                }
            }
 
            Debug.Log(targeting.name); //가장 가까운 거리의 게임오브젝트찾음
 
            // 가장 가까운 거리의 게임 오브젝트의 자식 오브젝트 Canvas에 접근
            targetingUI = targeting.transform.Find("Canvas").transform.gameObject;
 
            //Canvas 활성화 = targetingUI활성화
            targetingUI.SetActive(true);
        }
 
        //범위 안에 게임 오브젝트가 없고, targetingUI가 비어있지 않으면 이전 UI 비활성화
        else if(visibleTargets.Count == 0 && targetingUI != null)
        {
            targetingUI.SetActive(false);
 
        }
    }
}
cs

 

여담) 알게된 것

List에도 인덱스는 있다.

List에 값이 없는 상태에서 List에 접근하면 (예, List타입변수명[0]에 접근) 에러난다.

 

 

다음 구현 목표

타겟팅 시스템을 이용해서 캐릭터가 일반 공격을 하면 타겟팅 대상에게 매직미사일을 발사하기

일반 공격은 무조건 타겟팅 대상에 적중해야하므로 유도 기능을 구현해야한다.

 

반응형

댓글