목차
Preview
목표
-
Sub Emitters(서브 이미터) 모듈 이해하기
-
파이어볼 이펙트 만들기
준비물
- 글로우 모양의 동그란 텍스쳐와 Additive 마테리얼
-
파이어볼을 발사할 수 있게 해줄 스크립트
-
아래 소스코드를 다운로드하여 프로젝트 내에 넣어둔다.
Source Code
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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
// 2021. 03. 02. 03:02
// 작성자 : Rito
namespace Rito
{
public class ProjectileShooter : MonoBehaviour
{
public enum Direction
{
Left, Right, Up, Down, Random
}
/***********************************************************************
* Public Fields
***********************************************************************/
#region .
public Direction _direction = Direction.Random;
public GameObject _projectilePrefab;
[Range(1f, 10f)] public float _lifeTime = 5f;
[Range(1f, 20f)] public float _speed = 10f;
[Range(1f, 20f)] public float _distanceFromCamera = 10f;
[Range(0.01f, 1f)]
private float _clickInterval = 0.1f; // 클릭 허용 간격
#endregion
/***********************************************************************
* Private Fields
***********************************************************************/
#region .
private float _currentClickInterval = 0f;
#endregion
/***********************************************************************
* Unity Events
***********************************************************************/
#region .
private void Start()
{
_poolGo = new GameObject("Projectile Pool");
}
private void OnEnable()
{
if (_projectilePrefab == null)
{
Debug.LogError("ProjectileShooter : 투사체 오브젝트를 등록해주세요");
return;
}
_projectilePrefab.SetActive(false);
}
private void Update()
{
if (_projectilePrefab == null) return;
if (_currentClickInterval > 0f)
{
_currentClickInterval -= Time.deltaTime;
return;
}
if (Input.GetMouseButton(0) || Input.GetMouseButton(1))
{
StartCoroutine(ShootRoutine());
_currentClickInterval = _clickInterval;
}
}
#endregion
/***********************************************************************
* Coroutine
***********************************************************************/
#region .
private IEnumerator ShootRoutine()
{
Vector3 moveDir;
Vector3 worldMove;
float lifeTime = _lifeTime;
// 이동 방향 결정
switch (_direction)
{
case Direction.Left: moveDir = Vector3.left; break;
case Direction.Right: moveDir = Vector3.right; break;
case Direction.Up: moveDir = Vector3.up; break;
case Direction.Down: moveDir = Vector3.down; break;
case Direction.Random:
default:
moveDir = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), 0f).normalized;
break;
}
worldMove = Camera.main.transform.TransformDirection(moveDir) * _speed;
// 투사체 스폰
GameObject psInstance = Spawn();
Transform psTransform = psInstance.transform;
// 투사체 위치 지정
Vector3 mousePos = Input.mousePosition;
mousePos.z = _distanceFromCamera;
psTransform.position = Camera.main.ScreenToWorldPoint(mousePos);
float t = 0f;
while (t < lifeTime)
{
psTransform.Translate(worldMove * Time.deltaTime, Space.World);
t += Time.deltaTime;
yield return null;
}
Despawn(psInstance);
}
#endregion
/***********************************************************************
* Pooling
***********************************************************************/
#region .
private Queue<GameObject> _poolQueue = new Queue<GameObject>();
private GameObject _poolGo;
private GameObject Spawn()
{
GameObject next;
if (_poolQueue.Count == 0)
next = Instantiate(_projectilePrefab);
else
next = _poolQueue.Dequeue();
next.SetActive(true);
next.transform.SetParent(_poolGo.transform);
return next;
}
private void Despawn(GameObject go)
{
go.SetActive(false);
//go.transform.SetParent(_poolGo.transform);
_poolQueue.Enqueue(go);
}
#endregion
}
}
1. 파티클 시스템 제작
기본 준비
-
파티클 시스템 게임오브젝트 생성
-
트랜스폼을 Reset하여 Position(0, 0, 0), Rotation(0, 0, 0) 설정
-
마테리얼 적용
메인 모듈
-
Start Lifetime
: 1 -
Start Speed
: 0 -
Start Size
: 10
Shape 모듈
- 파티클이 같은 위치에서만 생성되도록, Shape 모듈을 체크 해제한다.
Emission 모듈
Rate over Time
: 5
Color over Lifetime 모듈
-
색상은 주황색으로 시작하여 노란색으로 끝나도록 한다.
-
투명도는 255로 시작하여 0으로 끝나도록 한다.
Size over Lifetime 모듈
- Curve로 설정하고, 크기가 0% ~ 100% ~ 0%로 변화하도록 다음처럼 그래프를 지정한다.
현재 상태
Sub Emitter 모듈
- Sub Emitter 모듈이란?
파티클에 생성, 파괴, 충돌 등의 이벤트가 발생할 때 생성되는, 본 파티클 시스템에 종속적인 또다른 파티클 시스템을 등록하는 것
-
Sub Emitter 모듈에 체크하고, 우측 상단의 [+] 버튼을 눌러 서브 이미터 하나를 추가한다.
Inherit
속성은 Color를 지정한다.
-
기본적으로
Birth
속성이 지정되어 있으므로, 파티클이 생성될 때 서브 이미터 파티클 시스템이 함께 생성된다. -
Inherit
를 Color로 지정하였으므로, 본 파티클 시스템의 파티클 색상을 서브 이미터가 상속하게 된다.
2. 서브 이미터 설정
- Sub Emitter 모듈에서 [+] 버튼을 눌렀을 때 파티클 시스템 게임오브젝트의 자식으로 서브 이미터 파티클 시스템이 생성된다.
-
하이라키에서 파티클 시스템 게임오브젝트의 좌측 화살표를 눌러 확인할 수 있다.
-
서브 이미터 역시 파티클 시스템이기 때문에, 똑같이 모듈을 통해 다양한 속성을 설정할 수 있다.
마테리얼 설정
- 파이어볼 파티클 시스템에 사용한 마테리얼을 서브 이미터에도 드래그하여 똑같이 적용한다.
메인 모듈
-
Start Lifetime
- [Random Between Two Constants] : (0.5, 1) -
Start Size
- [Random Between Two Constants] : (1, 6) -
Simulation Space
: World
Emission 모듈
Rate over Time
: 24
Shape 모듈
-
Shape
: Sphere -
Radius
: 0.3
Color over Lifetime 모듈
- 알파 값만 255 ~ 0으로 변화하도록 설정
Size over Lifetime 모듈
- 100% ~ 0%로 감소하는 그래프 설정 (하단 프리셋 중 3번째 클릭)
테스트
- 씬 뷰에서 기즈모 핸들을 잡고 이동시켜보면 위처럼 서브 이미터가 잔상처럼 남는 효과를 확인할 수 있다.
3. Projectile Shooter 적용
-
위의 ‘준비물’ 부분에 있는
ProjectileShooter.cs
스크립트가 프로젝트 내에 존재해야 한다. -
하이라키의 빈 공간을 우클릭하여
Create Empty
를 눌러 빈 게임오브젝트를 생성한다. -
빈 게임오브젝트를 F2로 수정하여 이름을
Projectile Shooter
로 지정한다. -
생성한 게임오브젝트를 선택하고, 인스펙터에서
Add Component
를 눌러Projectile Shooter
를 추가하거나, 프로젝트 윈도우에서 스크립트를 드래그하여 컴포넌트로 추가한다.
- 하이라키에서 파이어볼 게임오브젝트를 드래그하여 인스펙터의
Projectile Prefab
부분에 가져와 등록한다.
-
게임을 시작하여, 게임 씬의 공간을 클릭할 경우 파이어볼이 생성되는 모습을 확인할 수 있다.
-
Projectile Shooter의
Direction
옵션을 수정하여 발사 방향을 바꿀 수 있다.