구현 목적
- 플레이어 캐릭터의 시야 범위 안에 있는 오브젝트를 타겟팅하여 공격 대상으로 지정하기
- 타겟팅된 오브젝트에서 타겟팅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(0, 360)]
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]에 접근) 에러난다.
다음 구현 목표
타겟팅 시스템을 이용해서 캐릭터가 일반 공격을 하면 타겟팅 대상에게 매직미사일을 발사하기
일반 공격은 무조건 타겟팅 대상에 적중해야하므로 유도 기능을 구현해야한다.
'게임 개발 > 유니티 게임 개발 일지' 카테고리의 다른 글
#9 조이스틱과 HUD 구현하기 (3) | 2020.02.19 |
---|---|
#8 캐릭터 구현하기 - 일반공격(매직미사일 발사) 구현 (0) | 2020.02.19 |
#6 캐릭터 구현하기 - Field Of View 캐릭터 시야 구현하기 (0) | 2020.02.19 |
#5 캐릭터 구현하기 - 캐릭터의 손으로 지팡이 잡기 (0) | 2020.02.19 |
#4 캐릭터 구현하기 - 기능 별로 스크립트 구조화하기 (0) | 2020.02.19 |
댓글