2019년 11월 27일 수요일

11. 파티클을 이용한 블럭 제거하기

이번 장에서는 파티클(Particle)과 Scale 애니메이션을 이용하여 블럭이 자연스럽게 사라지는 효과를 연출할 것이다.

실행 결과는 다음과 같다.

소스 코드 다운로드

https://github.com/ninez-entertain/BingleStarter 에서 소스코드를 다운받을 수 있다. >git clone https://github.com/ninez-entertain/BingleStarter.git 또한, 각 진행 과정은 개별 branch로 제공되며 소스코드 다운로드 후에 각 스텝 별로 브랜치를 이용할 수 있다. 이번 장의 브랜치는 "step-11"으로 아래 git 명령어로 이번 장의 브랜치로 바로 이동할 수 있다. git checkout step-11

파티클 추가하기

Material 준비하기

다음 과정으로 파티클에 사용할 Material를 추가합니다.
  1. Assets/Sprites 하위에 Effect/Explosion 폴더를 생성한다.
  2. 아래에서 이미지를 다운로드해서 1에서 생성한 Explosion 폴더에 저장한다.
    https://github.com/ninez-entertain/BingleStarter/tree/master/Assets/Sprites/Effects/Explosion
  3. Assets 하위에 Materials 폴더를 생성하고, 생성한 폴더로 이동한다.
  4. [Create | Material] 메뉴를 이용해서 Material을 추가하고, 'Smoke_Add'로 이름을 변경한다.
  5. 'Smoke_Add' Material를 선택한다.
  6. Inspector 상단 Shader 리스트박스를 클릭해서 [Shaders 팝업]을 띄운다.
  7. 검색항목에 'alpha'를 입력해서 리스팅된 결과에서 'Alpha Blended(Lagacy Shaders/Particles/Alpha Blended')를 선택한다.
  8. Inspector가 선택된 Shader의 내용으로 변경되는지 확인한다.
  9. Inspector에서 [Tint Color]의 색상을 클릭해서 [Color] 팝업을 띄운다.
  10. RGBA 값 (1.0, 1.0, 1.0, 1.0)을 입력 또는 마우스로 선택한다.


파티클 GameObject 추가하기

다음 과정으로 파티클을 생성하기 위해 사용할 Prefab을 추가한다.
  1. 씬에서 Empty GameObject를 생성하고 이름을 'FX_BLOCK_EXPLOSION_NORMAL'로 변경한다.
  2. FX_BLOCK_EXPLOSION_NORMAL을 선택한 후, Inspector에서 [Add Component]를 선택한다.
  3. [Add Component] 팝업 상단에서 'Particle'을 입력해서 [Particle System]을 선택한다.
  4. Inspector에 Particle System 컴포넌트가 추가된 것을 확인한다.
  5. 모듈별 속성을 아래와 같이 추가한다.
    표기한 속성 이외의 값은 디폴드 값을 사용한다.

Main 모듈


속성설명
Duration0.1파티클 시스템이 파티클을 방출(Emission)하는 시간
Loopingunchecked반복실행 여부
Start Lifetime0.5파티클의 수명 (단위: 초)
Start Speed0Emission될 때 속도
Start Size0.7Emission될 때 시작 크기

Emission 모듈

[+] 버튼 클릭해서 Bursts 항목 추가한 후, 아래와 같이 입력한다.
Time 0, Count 1, Cycles 1, Interval 0.01, Probability 1.00

Size Of Lifetime

Size : 2.0
Preset : 직선형
Start : (0.0, 0.24)

Texture Sheet Animation

속성설명
ModeGrid출력할 이미지의 구성 형태로 타일(Tile) 형식으로 배치된 이미지를 사용한다.
TilesX : 8, Y : 18 x 1 형태의 이미지 즉, 가로방향으로 배치된 8개의 이미지가 순차적으로 렌더링되면서 애니메이션 효과를 보여 준다.

Renderer

속성설명
Render ModeBillboard디폴트값. 파티클이 항상 카메라 향한다.
MaterialSmoke_Add위에서 준비한 Smoke_Add Material을 드래그해서 지정한다.
Sorting Layer IDBlock파티클 Renderer에서 사용하는 스프라이트에 Sorting Layer를 설정한다.
Order In Layer2블럭보다 위에 렌더링 되도록 '2'를 입력한다.

자세한 설정은 아래를 참고바랍니다.

파티클 삭제 스크립트 추가

파티클의 방출(Emission)이 끝난 후 파티션을 출력하기 위해 생성한 GameObject를 자동으로 삭제하는 스크립트를 추가한다.
(위치 : Scripts/Effect/ParticleAutoDestroy.cs)
using UnityEngine;
using System.Collections;

namespace Ninez.Effect
{
    [RequireComponent(typeof(ParticleSystem))]
    public class ParticleAutoDestroy : MonoBehaviour
    {
        void OnEnable()
        {
            StartCoroutine(CoCheckAlive());
        }

        IEnumerator CoCheckAlive()
        {
            while (true)
            {
                yield return new WaitForSeconds(0.5f);
                if (!GetComponent<ParticleSystem>().IsAlive(true))
                {
                    Destroy(this.gameObject);

                    break;
                }
            }
        }
    }
}
Effect/ParticleAutoDestroy.cs
6 [RequireComponent] Attribute는 컴포넌트로 등록될 때 지정한 컴포넌트가 사전에 등록되어있는지 체크한다.
ParticleSystem이 컴포넌트로 등록되어 있어야 한다.
9 - 12 GameObject가 활성화될 때 호출되는 OnEnable() 함수로서 CoCheckAlive() 코루틴을 시작한다.
14 - 26 파티클의 Lifecycle을 체크해서 Emission이 완료되었으면 GameObject를 삭제한다.
18 0.5초 주기로 체크한다.

파티클 GameObject에 컴포넌트 등록하기.

  1. 씬에서 FX_BLOCK_EXPLOSION_NORMAL GameObject를 선택한다.
  2. ParticleAutoDestroy를 Inspector로 드래그해서 컴포넌트로 등록한다.

파티클 Prefab 등록하기

지금까지 추가한 파티클 GameObject를 Prefab(이하 Explosion Prefab)으로 등록한다.
  1. 씬에 추가한 FX_BLOCK_EXPLOSION_NORMAL GameObject를 드래그해서 Assets/Prefabs 폴더로 드롭한다.
  2. 씬에 추가된 FX_BLOCK_EXPLOSION_NORMAL GameObject는 파란색으로 변경되는 것을 볼 수 있다.
  3. 씬에 추가된 FX_BLOCK_EXPLOSION_NORMAL GameObject를 삭제한다.
    파티클은 플레이 중에 동적으로 생성할 것이다.

파티클 적용하기

추가한 Explosion Prefab을 이용해서 블럭이 제거될 때 파트클을 방출해서 사라지는 효과를 연출해 보자.

BlockConfig에 파티클 Prefab 등록하기

BlockConfig는 모든 BlockBehaviour 객체에서 참조하는 공통 자원(스프라이트 등)을 가지고 있는 애셋으로 Explosion Prefab을 BlockConfig에 등록하면 BlockBehaviour에서 접근하기 용이하다.
[CreateAssetMenu(menuName = "Bingle/Block Config", fileName = "BlockConfig.asset")]
public class BlockConfig : ScriptableObject
{
    public float[] dropSpeed;
    public Sprite[] basicBlockSprites;
    public GameObject explosion;

    public GameObject GetExplosionObject(BlockQuestType questType)
    {
        switch (questType)
        {
            case BlockQuestType.CLEAR_SIMPLE:
                return Instantiate(explosion) as GameObject;
            default:
                return Instantiate(explosion) as GameObject;
        }
    }
}
BlockConfig.cs
6 블럭이 제거될 때 사용할 파티클 Prefab을 저장하는 GameObject
8 - 17 블럭이 제거될 수행하는 미션(BlockQuestType)에 따라 사용하게될 파티클 GameObject를 리턴한다.
BlockQuestType에 따라 적절할 Prefab을 찾아서 GameObject를 생성(Instantiate)한 후 리턴한다.
현재까지 제거 가능한 블럭의 제거 형태는 CLEAR_SIMPLE 타입이 유일하다. 진행하면서 종류를 추가해 나갈 것이다.
13 Prefab을 이용해서 GameObject를 생성(Instiantiate)해서 리턴한다.
Prefab을 참조하는 explosion을 리턴하지 않고 GameObject를 생성해서 리턴하는 것이 호출하는 입장에서 편리하다.

BlockConfig 애셋에 파티클 Prefab 등록하기

  1. Assets/Config/Block 애셋을 클릭한다.
  2. Inspector에 새로 추가한 'Explosion' 속성이 보이는 것을 확인한다.
  3. Prefabs/FX_BLOCK_EXPLOSION_NORMAL을 드래그해서 Inspector의 'Explosion'에 등록한다.

Block 제거될 때 파티클 출력하기

다음과 같이 블럭이 제거될 때 파티클을 먼저 렌더링하고, Destroy()하도록 수정한다.
public void DoActionClear()
{
    Destroy(gameObject);
    StartCoroutine(CoStartSimpleExplosion(true));
}

/*
 * 블럭이 폭발한 후, GameObject를 삭제한다.
 */
IEnumerator CoStartSimpleExplosion(bool bDestroy = true)
{
    //1. 폭파시키는 효과 연출 : 블럭 자체의 Clear 효과를 연출한다 (모든 블럭 동일)
    GameObject explosionObj = m_BlockConfig.GetExplosionObject(BlockQuestType.CLEAR_SIMPLE);
    explosionObj.SetActive(true);
    explosionObj.transform.position = this.transform.position;

    yield return new WaitForSeconds(0.1f);

    //2. 블럭 GameObject 객체 삭제 or make size zero
    if (bDestroy)
        Destroy(gameObject);
    else
    {
        Debug.Assert(false, "Unknown Action : GameObject No Destory After Particle");
    }
}
BlockBehaviour.cs
3 메소드 호출 즉시 Block GameObject를 제거했던 코드를 삭제한다.
4 블럭을 제거하는 코루틴을 실행한다.
10 - 22 파티클을 방출하고, GameObject를 제거하는 코루틴.
bDestroy : 파티클을 방출한 후 GameObject를 제거할지 나타내는 플래그
13 CLEAR_SIMPLE에 적합한 Explosion GameObject를 요청한다.
14 GameObject를 활성화한다. 활성화 해야 파티클이 시작된다.
15 제거되는 블럭과 동일한 위치에서 사라지는 효과가 보여지도록 위치를 설정한다.
17 파티클 Emission 후 100ms 대기한다.
20, 21 블럭을 제거한다.

플레이하기

플레이버튼(▶)을 클릭해서 지금까지 적용한 내용을 플레이 해보자.


블럭이 제거될 때 사라지는 효과가 적용된 것을 볼 수 있다.
그러나 유심히 살펴보면 블럭의 종류(Breed)에 관계없이 항상 '흰색'의 파티클이 렌더링되는 것을 볼 수 있다. 블럭의 색상과 동일한 색으로 사라지는 파티클이 보여지면 좀 더 완성도 있는 게임이 될 것 같다.

계속해서, 블럭에 지정된 컬러로 사라지는 파티클의 색상을 바꿔보자.

파티클 컬러 변경하기

블럭 종류(Breed)별로 색상 정보를 저장하고 이를 참조해서 파티클의 컬러를 변경하면 된다. 모든 BlockBehaviour에서 참조하는 속성이므로 Explosion Prefab과 마찬가지로 BlockConfig에 추가한다.

BlockConfig에 블럭별 색상 추가하기

아래와 같이 컬러를 저장하는 배열과 요청 메소드를 추가한다.
public class BlockConfig : ScriptableObject
{
    // -- 중략 --
    public Color[] blockColors;

    public Color GetBlockColor(BlockBreed breed)
    {
        return blockColors[(int)breed];
    }
}
Config/BlockConfig.cs
4 색상을 저장할 배열을 선언한다.
6 - 9 블럭 종류(Breed)에 해당되는 색상을 리턴하는 메소드를 정의한다.

블럭별 색상 입력하기

BlockConfig에 추가한 블럭 색상(blockColors) 속성에 컬러값을 입력한다.
  1. Config/BlockConfig를 선택한다.
  2. Inspector에서 [Block Colors]를 펼친다.
  3. Size에 '6'을 입력하고 엔터를 입력한다.
  4. 각 블럭별 색상을 아래와 같이 입력한다.
    • Element 0 : {R: 0.38, G: 0.27, B: 0.00, A: 1}
    • Element 1 : {R: 0.10, G: 0.50, B: 0.27, A: 1}
    • Element 2 : {R: 0.38, G: 0.38, B: 0.38, A: 1}
    • Element 3 : {R: 0.11, G: 0.11, B: 0.11, A: 1}
    • Element 4 : {R: 0.60, G: 0.10, B: 0.00, A: 1}
    • Element 5 : {R: 0.10, G: 0.18, B: 0.20, A: 1}

파티클에 블럭별 색상 적용하기

블럭이 사라질 때 연출되는 파티클에 다음과 같이 색상을 적용한다.
IEnumerator CoStartSimpleExplosion(bool bDestroy = true)
{
    //1. 폭파시키는 효과 연출 : 블럭 자체의 Clear 효과를 연출한다 (모든 블럭 동일)
    GameObject explosionObj = m_BlockConfig.GetExplosionObject(BlockQuestType.CLEAR_SIMPLE);
    ParticleSystem.MainModule newModule = explosionObj.GetComponent().main;
    newModule.startColor = m_BlockConfig.GetBlockColor(m_Block.breed);

    explosionObj.SetActive(true);
    explosionObj.transform.position = this.transform.position;

    yield return new WaitForSeconds(0.1f);

    //2. 블럭 GameObject 객체 삭제 or make size zero
    if (bDestroy)
        Destroy(gameObject);
    else
    {
        Debug.Assert(false, "Unknown Action : GameObject No Destory After Particle");
    }
}
BlockBehaviour.cs
5 파티클의 Main Module에 대한 참조를 구한다.
6 BlockConfig에서 블럭의 종류(Breed)에 설정된 컬러를 구해서 파티클의 startColor에 적용한다.

플레이하기

플레이버튼(▶)을 클릭해서 블럭이 사라질 때 지정된 컬러가 보여지는지 확인 해보자.


자연스럽게 블럭 제거하기

파트클을 적용함으로써 블럭이 사라지는 효과가 개선되었다. 그러나 자세히 살펴보면 파티클 효과가 발동되면서 블럭이 한순간에 화면에서 사라지는 것을 볼 수 있다. 블럭이 사라질 때 크기(Scale)가 작아지면서 사라지면 좀 더 자연스러운 효과를 낼 수 있을 것이다.

Scale 애니메이션 추가

/*
    * param toScale 커지는(줄어지는) 크기, 예를 들어, 0.5인 경우 현재 크기에서 절반으로 줄어든다.
    * param speed 초당 커지는 속도. 예를 들어, 2인 경우 초당 2배 만큼 커지거나 줄어든다. 
    */
public static IEnumerator Scale(Transform target, float toScale, float speed)
{
    //1. 방향 결정 : 커지는 방향이면 +, 줄어드는 방향이면 -
    bool bInc = target.localScale.x < toScale;
    float fDir = bInc ? 1 : -1;

    float factor;
    while (true)
    {
        factor = Time.deltaTime * speed * fDir;
        target.localScale = new Vector3(target.localScale.x + factor, target.localScale.y + factor, target.localScale.z);

        if ((!bInc && target.localScale.x <= toScale) || (bInc && target.localScale.x >= toScale))
            break;

        yield return null;
    }

    yield break;
}
Action2D.cs
5 - 23 Scale 애니메이션 Enumerator를 구현한다.
target : 애니메이션 대상 GameObject
toScale : 커지는(줄어지는) 최종 크기. 0.5로 설정하면 현재 크기에서 절반으로 줄어드는 애니메이션이 수행된다.
speed : 줄어드는 속도. 2이면 2배 속도로 진행한다.
8, 9 줄어드는 방향을 결정한다. (커지는 방향이면 +, 줄어드는 방향이면 -)
12 - 21 deltaTime 만큼의 비율로 크기가 축소(증가)된다.

Scale 애니메이션 적용하기

파티클을 방출하기 전에 블럭이 줄어드는 효과를 먼저 연출하는 코드를 추가한다.
IEnumerator CoStartSimpleExplosion(bool bDestroy = true)
{
    //크기가 줄어드는 액션 실행한다 : 폭파되면서 자연스럽게 소멸되는 모양 연출, 1 -> 0.3으로 줄어든다.
    yield return Util.Action2D.Scale(transform, Core.Constants.BLOCK_DESTROY_SCALE, 4f);

    //폭파시키는 효과 연출 : 블럭 자체의 Clear 효과를 연출한다 (모든 블럭 동일)
    GameObject explosionObj = m_BlockConfig.GetExplosionObject(BlockQuestType.CLEAR_SIMPLE);

    // -- 중 략 --
}
BlockBehaviour.cs
4 2D Scale 애니메이션 Enumerator를 실행한다. 크기가 1에서 0.3으로 줄어든다.
Scale 애니메이션이 끝난 후에 파티클을 렌더링되도록 대기한다.

추가로, Constants에 'BLOCK_DESTROY_SCALE'을 선언한다.
public static class Constants
{
    public static float BLOCK_ORG = 0.5f;           //블럭의 출력 원점
    public static float SWIPE_DURATION = 0.2f;      //블럭 스와이프 애니메이션 시간
    public static float BLOCK_DESTROY_SCALE = 0.3f; //블럭이 삭제될 때 줄어드는 크기
}
Core/Constants.cs

플레이하기

플레이버튼(▶)을 클릭해서 Scale 애니메이션이 적용되는지 확인해보자.

정상 속도 (TimeScale = 1)1/5 속도 (TimeScale = 0.2)

오른쪽 화면은 TimeScale을 0.2로 변경해서 느리게 플레이한 화면이다.
블럭이 일정 크기로 작아진 후에 사라지는 파티클 효과가 연출되는 것을 볼 수 있다.

TimeScale

TimeScale은 플레이 동안 시간이 흐르는 크기를 나타낸다. 기본값은 1.0으로 실제로 흐르는 시간과 같은 속도로 플레이된다. 0.5로 설정하면 시간이 느리게 가는 효과를 구현할 수 있다. 플레이 도중에 슬로우 모션 같은 효과를 연출하기 위해서 런타임에 Time.TimeScale을 변경할 수도 있고, 테스트 목적으로 에디터에서 TimeScale을 조정할 수도 있다. 에디터에서 TimeScale을 조정하는 방법은 아래와 같다. 1. [Edit | Project Settings...] 메뉴를 선택해서 [Project Settings] 팝업을 띄운다. 2. 왼쪽 목록에서 [Time] 항목을 선택한다. 3. TimeScale을 원하는 값으로 변경한다.

마지막으로 매칭될 블럭이 제거되고 드롭이 시작하기 전에 약간의 대기를 추가한다.(아래 붉은색 화살표 구간)
[매칭 ➔ 제거 드롭 ➔ 매칭 ➔ 제거 드롭 ...]이 반복되는 동안 드롭된 블럭이 매칭되어 제거되면서 바로 드롭이 발생해서 급하게 플레이가 진행되는 느낌을 받게 된다. 그래서 블럭이 제거된 후 드롭되기 전에 약간의 대기시간을 추가하면 매칭 후 플레이가 좀 더 자연스러워진다.
(드롭되는 속도, 대기 시간등에 대한 느낌은 개인차가 있기 때문에 시간을 변경해 보면서 최적의 값을 찾아보길 바랍니다)
public IEnumerator Evaluate(Returnable matchResult)
{
    // -- 중 략 --

    //3.3 매칭된 블럭을 제거한다. 
    clearBlocks.ForEach((block) => block.Destroy());
    
    //3.3.1 블럭이 제거되는 동안 잠시 Delay, 블럭 제거가 순식간에 일어나는 것에 약간 지연을 시킴
    yield return new WaitForSeconds(0.15f);

    //3.4 3매칭 블럭 있는 경우 true 설정   
    matchResult.value = true;

    yield break;
}
Board.cs

다음 장에서는 블럭이 드롭되고 빈블럭으로 남아있는 보드를 새로운 블럭으로 채우는 과정을 진행하겠습니다.

문의사항 및 잘못된 부분은 댓글 및 메일(ninez.entertain@gmail.com)으로 부탁드립니다.
감사합니다.

댓글 없음:

댓글 쓰기