etc-posts/Unity :: C# 튜토리얼

[유니티C#][기초] 10.플레이어 슈팅 5 (최종)

플라즈밍 2018. 7. 8. 18:21
반응형



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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
 
using System.Collections;
using UnityEngine;
using Tutorial_Step_16;
 
namespace Tutorial_Step_15
{
    /// <summary>
    /// Player의 입력에 따라 레이저를 발사하는 기능을 처리하는 클래스
    /// </summary>
    public class PlayerShooting : MonoBehaviour
    {
        // Player의 총구의 끝 위치를 나타내는 Transform을 참조할 변수
        // 이 위치부터 레이저가 발사됨
        public Transform gunBarrelEnd;
 
        // 화면에 선을 그리기 위해 생성한 LineRenderer를 참조할 변수
        private LineRenderer gunLine;
 
        // 총알(레이저)를 발사할 때마다, 총구 부근에서 빛을 반짝이는 연출을 하기 위한 Light를 참조할 변수
        // 미리 조명 설정을 해놓고, 단지 On/Off만 해가면서 사용
        private Light gunLight;
 
        // 적에게 가할 데미지의 양
        // 총알(레이저)에 맞은 Enemy는, 이 값만큼 HP가 감소하게 됨
        public int damage = 20;
 
        // Player의 총알(레이저)에 맞았을 경우에, 대상에게 가하는 충격량
        // 높을수록 더 많은 힘으로 가격
        public float power = 10000f;
 
        // 최대 사정거리
        // 이 거리만큼 타격을 할 수 있고, Line이 그려짐
        public float range = 100f;
 
        // 총알(레이저) 발사 사이에 간격
        // 무한히 데미지를 주는 게 아니라, 일정 간격으로 주기 때문에
        public float timeBetweenBullets = 0.15f;
 
        // 화면에 gunLine(LineRenderer)이 표시되었다가 사라지는 시간
        public float effectsDisplayTime = 0.05f;
 
        // 레이저가 발사되고, 어디에 부딪히는지 Raycast를 실행할 광선 정보를 저장할 변수
        private Ray ray = new Ray();
 
        // 레이저가 발사되고, 어디에 부딪히는지 Raycast를 통해 얻어낸 정보를 저장할 변수
        private RaycastHit hit;
 
        // gunLine이 나타났다가 사라질 때가 되었는지를 체크하기 위해 시간을 누적시키는 변수
        private float timer = 0f;
 
        // 총알(레이저)를 맞아야되는 대상을 Layer 단위로 구분하기 위해서 사용하는 변수
        // Awake()에서 "Shootable" 레이어의 마스크값을 저장함
        private int layerMask;
 
        /// <summary>
        /// 시작할 때 한 번만 호출
        /// </summary>
        public void Awake()
        {
            // Player GameObject의 자식 오브젝트들 중에 LineRenderer 컴포넌트를 찾아서 참조를 얻고, gunLine에 저장
            // FixedUpdate나 Update에서 매번 GetComponent를 해주면, 비효율적이기 때문에 미리 한 번만 참조를 구해놓음
            gunLine = GetComponentInChildren<LineRenderer>();
 
            // Player GameObject의 자식 오브젝트들 중에 Light 컴포넌트를 찾아서 참조를 얻고, gunLight에 저장
            // FixedUpdate나 Update에서 매번 GetComponent를 해주면, 비효율적이기 때문에 미리 한 번만 참조를 구해놓음
            gunLight = GetComponentInChildren<Light>();
 
            // Mask 용도로 사용할 "Shootable" 레이어의 번호를 미리 구해놓음
            // 매번 새로 구할 필요가 없기 때문에 한 번만 구해놓음
            layerMask = LayerMask.GetMask("Shootable");
        }
 
        /// <summary>
        /// 매 프레임마다 호출
        /// </summary>
        public void Update()
        {
            // deltaTime을 누적시킴으로써, 단순히 흐른 시간만 계산함
            timer += Time.deltaTime;
 
            // 두 조건이 모두 true이면, 실행
            if (Input.GetButton("Fire1"&& // "Fire1"로 설정된 버튼이 눌려있으면 true, 일반적으로 마우스 왼쪽 버튼이 "Fire1"
                timer > timeBetweenBullets) // 지난 발사로부터 지난 시간이 timeBetweenBullets만큼 지났으면, 즉 일정 시간이 지났으면
            {
                // 다시 timeBetweenBullets이 될 때까지 기다렸다가 실행하기 위해서
                // 누적 시간 초기화
                timer = 0f;
 
                // 총알(레이저) 발사
                Shoot();
            }
 
            // 이펙트(LineRenderer)가 사라질 시간이 되었으면, 이펙트(LineRenderer)를 비활성화
            // effectsDisplayTime이 timeBetweenBullets보다 작기 때문에, 화면에서 라인이 나타났다가 사라지는 모습이 연출됨
            if (timer > effectsDisplayTime)
                DisableEffects();
        }
 
        /// <summary>
        /// 총알(레이저)를 발사
        /// 화면에 gunLine(LineRenderer)을 통해서 레이저처럼 보이게도 하고,
        /// 실제로 부딪힌 대상이 Cube이면, Rigidbody.AddForce를 통해 타격도 함
        /// 실제로 부딪힌 대상이 Enemy이면, 데미지를 줘서 체력을 줄이고, 피격 지점에 이펙트를 출력함
        /// </summary>
        private void Shoot()
        {
            // gunLine의 시작 위치를 총구 끝 위치(월드 좌표)로 변경
            // LineRenderer.SetPosition()의 1번 인자가 0이면 시작 위치, 1이면 끝위치
            gunLine.SetPosition(0, gunBarrelEnd.position);
 
            // Raycast를 실행할 광선의 정보를 설정
            // 광선의 시작 위치를, 총구 끝 위치(월드 좌표)로 설정
            ray.origin = gunBarrelEnd.position;
            // 광선의 방향을, 총구가 가리키고 있는 방향으로 설정 (즉 Player가 마우스 커서를 바라보는 방향으로)
            ray.direction = gunBarrelEnd.forward;
 
            // Raycast 실행
            // 총구로부터 발사된 광선에, 뭔가가 부딪히면 (부딪힐 수 있는 대상을 layerMask로 설정)
            // 부딪힌 지점까지 Line을 그림
            if (Physics.Raycast(ray,    // Raycast를 하기 위한 광선 정보
                out hit,    // Raycast를 하고 난 뒤, 얻어낼 정보를 저장할 구조체
                range,  // Raycast를 계산할 최대 사정거리 (무한히 할 필요가 없기 때문에)
                layerMask   // Raycast를 할 대상이 되는 GameObject들의 Layer (이 Layer로 설정되어 있는 GameObject만 Raycast의 대상이 됨), 즉 레이어가 "Shootable"인 GameObject만 체크
                ))
            {
                // gunLine의 목표 위치를, Player의 총구로부터 Raycast한 광선이 부딪힌 지점으로 설정
                gunLine.SetPosition(1, hit.point);
 
                // 부딪힌 대상에 Collider가 있으면, 대상의 종류에 따라 힘이나 데미지를 가함
                // Collider 단위로 Raycast를 하기 때문에, 일반적으로는 당연히 있음
                if (hit.collider != null)
                {
                    // 피격 대상의 이름에 "Cube"가 포함되어 있으면(즉 큐브 오브젝트이면), 힘을 가함
                    if (hit.collider.name.Contains("Cube"))
                    {
                        // 피격된 Collider와 같은 GameObject에 붙어있는 Rigidbody Component의 참조를 얻어서 rb에 저장
                        // 성공하면 Rigidbody Component의 참조값이 저장되고, 실패하면 null이 저장됨
                        var rb = hit.collider.GetComponent<Rigidbody>();
 
                        // Rigidbody를 얻어왔으면, 즉 피격 대상에 Rigidbody가 있으면
                        if (rb != null)
                            // Rigidbody.AddForce를 통해 힘을 가함
                            // Player의 총구가 가리키는 방향에 power만큼의 힘을 곱해서 매개변수로 넘겨줌
                            rb.AddForce(gunBarrelEnd.forward * power);
                    }
 
                    // 피격 대상의 이름에 "Enemy"가 포함되어 있으면(즉 적 오브젝트이면), 데미지를 가함
                    if (hit.collider.name.Contains("Enemy"))
                    {
                        // 피격된 Collider와 같은 GameObject에 붙어있는 EnemyHealth Component의 참조를 얻어서 eh에 저장
                        var eh = hit.collider.GetComponent<EnemyHealth>();
                        // EnemyHealth를 얻어왔으면, 즉 피격 대상에 EnemyHealth가 있으면
                        if (eh != null)
                            // EnemyHealth.TakeDamage를 통해 적에게 데미지를 주고, 피격 지점에 이펙트를 출력함
                            eh.TakeDamage(damage, hit.point);
                    }
                }
            }
            // 총구로부터 발사된 광선에 아무것도 부딪히지 않으면, 최대 사정거리까지 Line을 그림
            else
            {
                // 총구 끝 위치(월드 좌표)부터, 총구 방향*최대 사정거리만큼의 벡터를 더해서 목표 위치를 계산
                var goalPosition = gunBarrelEnd.position + (gunBarrelEnd.forward * range);
 
                // gunLine의 목표 위치를, goalPosition으로 설정
                gunLine.SetPosition(1, goalPosition);
            }
 
            // 모든 이펙트를 활성화함
            // 즉 총알(레이저가) 발사될 때마다, LineRenderer과 Light를 모두 출력함
            EnableEffects();
        }
 
        /// <summary>
        /// 이펙트들을 전부 활성화함
        /// 재사용하기 때문에 삭제하지는 않고, 단지 비활성화만 해놨다가 다시 사용
        /// gunLine(LineRenderer)
        /// gunLight(Light)
        /// </summary>
        private void EnableEffects()
        {
            // gunLine(LineRenderer) Component를 활성화 (Inpector에서 컴포넌트 이름 왼쪽에 있는 체크박스의 값)
            // 즉 화면에 라인을 그림
            gunLine.enabled = true;
            // gunLight(Light) Component를 활성화 (Inpector에서 컴포넌트 이름 왼쪽에 있는 체크박스의 값)
            // 즉 화면에 조명을 출력
            gunLight.enabled = true;
        }
 
        /// <summary>
        /// 이펙트들을 전부 비활성화함
        /// 재사용하기 때문에 삭제하지는 않고, 단지 비활성화만 해놨다가 다시 사용
        /// gunLine(LineRenderer)
        /// gunLight(Light)
        /// </summary>
        private void DisableEffects()
        {
            // gunLine(LineRenderer) Component를 활성화 (Inpector에서 컴포넌트 이름 왼쪽에 있는 체크박스의 값)
            // 즉 화면에 라인을 그리지 않음
            gunLine.enabled = false;
            // gunLight(Light) Component를 활성화 (Inpector에서 컴포넌트 이름 왼쪽에 있는 체크박스의 값)
            // 즉 화면에 조명을 출력하지 않음
            gunLight.enabled = false;
            
        }
    }
}
 
cs


반응형