Posts 파티클 시스템 예제 - 04 - Fire Ball
Post
Cancel

파티클 시스템 예제 - 04 - Fire Ball

목차



Preview


2021_0302_Fireball_Preview01

2021_0302_Fireball_Preview02


목표


  • Sub Emitters(서브 이미터) 모듈 이해하기

  • 파이어볼 이펙트 만들기


준비물


  • 글로우 모양의 동그란 텍스쳐와 Additive 마테리얼

image


  • 파이어볼을 발사할 수 있게 해줄 스크립트

  • 아래 소스코드를 다운로드하여 프로젝트 내에 넣어둔다.

  • ProjectileShooter.zip

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으로 끝나도록 한다.

image


Size over Lifetime 모듈

  • Curve로 설정하고, 크기가 0% ~ 100% ~ 0%로 변화하도록 다음처럼 그래프를 지정한다.


현재 상태

2021_0302_Fireball_Mid


Sub Emitter 모듈

  • Sub Emitter 모듈이란?

    파티클에 생성, 파괴, 충돌 등의 이벤트가 발생할 때 생성되는, 본 파티클 시스템에 종속적인 또다른 파티클 시스템을 등록하는 것

  • Sub Emitter 모듈에 체크하고, 우측 상단의 [+] 버튼을 눌러 서브 이미터 하나를 추가한다.

  • Inherit 속성은 Color를 지정한다.

2021_0302_Fireball_SubEmitter

  • 기본적으로 Birth 속성이 지정되어 있으므로, 파티클이 생성될 때 서브 이미터 파티클 시스템이 함께 생성된다.

  • Inherit를 Color로 지정하였으므로, 본 파티클 시스템의 파티클 색상을 서브 이미터가 상속하게 된다.


2. 서브 이미터 설정


  • Sub Emitter 모듈에서 [+] 버튼을 눌렀을 때 파티클 시스템 게임오브젝트의 자식으로 서브 이미터 파티클 시스템이 생성된다.

image

  • 하이라키에서 파티클 시스템 게임오브젝트의 좌측 화살표를 눌러 확인할 수 있다.

  • 서브 이미터 역시 파티클 시스템이기 때문에, 똑같이 모듈을 통해 다양한 속성을 설정할 수 있다.


마테리얼 설정

  • 파이어볼 파티클 시스템에 사용한 마테리얼을 서브 이미터에도 드래그하여 똑같이 적용한다.


메인 모듈

  • 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으로 변화하도록 설정

image


Size over Lifetime 모듈

  • 100% ~ 0%로 감소하는 그래프 설정 (하단 프리셋 중 3번째 클릭)

image


테스트

2021_0302_Fireball_Move

  • 씬 뷰에서 기즈모 핸들을 잡고 이동시켜보면 위처럼 서브 이미터가 잔상처럼 남는 효과를 확인할 수 있다.


3. Projectile Shooter 적용


  • 위의 ‘준비물’ 부분에 있는 ProjectileShooter.cs 스크립트가 프로젝트 내에 존재해야 한다.

  • 하이라키의 빈 공간을 우클릭하여 Create Empty를 눌러 빈 게임오브젝트를 생성한다.

  • 빈 게임오브젝트를 F2로 수정하여 이름을 Projectile Shooter로 지정한다.

  • 생성한 게임오브젝트를 선택하고, 인스펙터에서 Add Component를 눌러 Projectile Shooter를 추가하거나, 프로젝트 윈도우에서 스크립트를 드래그하여 컴포넌트로 추가한다.

image


  • 하이라키에서 파이어볼 게임오브젝트를 드래그하여 인스펙터의 Projectile Prefab 부분에 가져와 등록한다.

image

  • 게임을 시작하여, 게임 씬의 공간을 클릭할 경우 파이어볼이 생성되는 모습을 확인할 수 있다.

  • Projectile Shooter의 Direction 옵션을 수정하여 발사 방향을 바꿀 수 있다.


4. 결과


2021_0302_Fireball_Preview01

2021_0302_Fireball_Preview02

This post is licensed under CC BY 4.0 by the author.