티스토리 뷰

 

이제 잔상을 남길 수 있습니다.

 

SkinnedMeshRenderer의 BakeMesh 메서드만 이해한다면 누구나 잔상을 만들 수 있습니다.

 

현재 SkinnedMeshRenderer가 참조하는 Mesh의 모습을 그대로 복제하여,

매개변수의 Mesh mesh에 할당하는 식인가 봅니다.

 

이 작동 원리를 이해했다면 구현 방식을 쉽게 생각 해볼 수 있습니다.

바로 잔상의 Mesh를 가진 객체를 계속 생성하는 것입니다.

 

1. 잔상을 남기고 싶은 순간마다 객체를 생성합니다. 객체에는 Mesh FilterMesh Renderer가 필요하겠습니다.

2. Mesh Filter에는 아까의 BakeMesh 메서드로 복제한 메쉬를, Mesh Renderer에는 복제한 메쉬가 사용할 Material을 할당합니다.

3. 시간이 지나면 잔상이 사라지도록 Destroy()를 사용하거나 SetActive(false)를 사용합니다.

 

아래는 테스트를 위한 코드입니다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TrailController : MonoBehaviour
{
    // 캐릭터에 포함된 SkinnedMeshRenderer 배열
    SkinnedMeshRenderer[] skinnedMeshes;

    public float speed = 1f;
    public float rotationSpeed = 700f;

    // 트레일 효과를 제어하는 코루틴 참조
    Coroutine Trail;

    public float trailTime = 1f;
    public float trailTerm = 0.1f;
    public float trailLifeTime = 0.25f;
    public Material mat; // 트레일 메쉬에 사용할 머티리얼

    // 트레일 GameObject를 관리하기 위한 Queue (오브젝트 풀링)
    Queue<GameObject> trail_Q = new Queue<GameObject>();

    void Start()
    {
        // SkinnedMeshRenderer 배열을 Start에서 초기화하여 불필요한 GetComponent 호출을 방지
        skinnedMeshes = GetComponentsInChildren<SkinnedMeshRenderer>();
    }

    void Update()
    {
        // Space 키를 눌렀을 때 트레일 시작 또는 중지
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StartTrail();
        }

        // 속도에 따라 앞으로 이동
        transform.position += transform.forward * Time.deltaTime * speed;
        // 수평 입력과 회전 속도에 따라 회전
        transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Horizontal") * rotationSpeed * Time.deltaTime, 0);
    }

    void StartTrail()
    {
        // 트레일 코루틴을 토글 방식으로 제어
        if (Trail != null)
        {
            StopCoroutine(Trail);
            Trail = null;
            return;
        }

        // 트레일 코루틴 시작
        Trail = StartCoroutine(Trail_co(trailTime));
    }

    IEnumerator Trail_co(float time)
    {
        WaitForSeconds wft = new WaitForSeconds(trailTerm);

        while (time > 0)
        {
            foreach (var skinnedMesh in skinnedMeshes)
            {
                GameObject go;
                MeshFilter mf;
                Fade fade;

                if (trail_Q.Count == 0)
                {
                    // 트레일 메쉬를 위한 새로운 GameObject 생성
                    go = new GameObject("Trail");
                    mf = go.AddComponent<MeshFilter>();

                    var mr = go.AddComponent<MeshRenderer>();
                    mr.material = mat;

                    // 메쉬 페이드를 관리하는 Fade 컴포넌트 추가
                    fade = go.AddComponent<Fade>();
                    fade.mat = mr.material;
                    fade.return_Q = trail_Q;
                }
                else
                {
                    // Queue에서 기존 GameObject 재사용
                    go = trail_Q.Dequeue();
                    mf = go.GetComponent<MeshFilter>();
                    fade = go.GetComponent<Fade>();
                }

                // SkinnedMesh를 Static Mesh로 변환하여 베이크
                if (mf.mesh == null)
                    mf.mesh = new Mesh();
                skinnedMesh.BakeMesh(mf.mesh);

                // 페이드 파라미터 설정 및 트레일 GameObject 활성화
                fade.fadeTime = trailLifeTime;
                go.transform.SetPositionAndRotation(transform.position, transform.rotation);
                go.SetActive(true);
            }

            // 시간을 감소시키고 다음 반복을 위해 대기
            time -= trailTerm;
            yield return wft;
        }
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Fade : MonoBehaviour
{
    public Material mat; // 오브젝트의 재질
    public float fadeTime = 1.0f; // 페이드 아웃에 걸리는 시간
    private float maxFadeTime; // 페이드 아웃 시작 시의 최대 시간

    public Queue<GameObject> return_Q; // 오브젝트를 재사용하기 위한 큐

    private void Start()
    {
        // 초기화 시 페이드 타임의 최대값을 설정합니다.
        maxFadeTime = fadeTime;

        // 공유 머티리얼을 수정하지 않도록 개별 인스턴스를 사용합니다.
        mat = new Material(mat);
    }

    void Update()
    {
        // 경과 시간에 따라 fadeTime을 감소시킵니다.
        fadeTime -= Time.deltaTime;

        // 머티리얼의 "_Fade" 속성을 0에서 1로 변화시킵니다.
        float fadeValue = Mathf.InverseLerp(0, maxFadeTime, fadeTime);
        mat.SetFloat("_Fade", fadeValue);

        // fadeTime이 0보다 작아지면 오브젝트를 큐에 추가하고 비활성화합니다.
        if (fadeTime < 0)
        {
            fadeTime = maxFadeTime; // 다음에 재사용될 때를 위해 fadeTime 초기화
            return_Q.Enqueue(gameObject);
            gameObject.SetActive(false);
        }
    }
}

코드가 마냥 길어보이고 웬걸 스크립트가 두 개나 필요한가 싶겠지만,

Destroy를 사용하는게 부담스러워 보여서 오브젝트 풀링과 함께 사용해보았습니다.

또한 별도의 스크립트에서 서서히 투명해지도록 Shader의 프로퍼티 값을 조절하였습니다.

 

사용한 쉐이더 그래프입니다.

색상과 프레넬의 강도를 조절할 수 있습니다.

fade는 단순히 투명도를 조절할 값으로 상기의 Fade.cs에서 조절합니다.

BakeMesh를 응용하여 다른 재미있는 것들을 생각해 볼 수 있을 것 같습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함