본문 바로가기
Unity 강의/Unity Course(2) - 절대강좌! 유니티

[Unity Course 2] 05. 총 발사 로직 7

by 첨부엉. 2024. 6. 20.
반응형
위키북스 출판사 이재현 저자님의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.

 

총구 화염 효과 - Muzzle Flash

 

FirePos 하위에 MuzzleFlash 라는 이름의 3D 오브젝트 Quad 를 생성하고 Mesh Collider 컴포넌트는 삭제한다.

 

MeshRenderer 의 Ligthing  - Receive Shadows 는 언체크한다.

 

이 이미지를 04.Images에 임포트한다.

네개의 이미지가 있는데 불규칙하게 노출해서 총구의 화염 효과를 표현

이 이미지를 MuzzleFlash 로 드래그 한다.

쉐이더를 Mobile/Particle/Additive 로 변경 후 Offset 값을 0.5 , 0.5 로 변경

4가지의 이미지중 하나의 이미지만 노출하기 위한 타일링 변경

using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class FireCtrl : MonoBehaviour
{
    // 총알 프리팹
    public GameObject bullet;
    // 총알 발사 좌표
    public Transform firePos;
    // 총소리에 사용할 오디오 음원
    public AudioClip fireSfx;

    // AudioSource 컴포넌트를 저장할 변수
    private new AudioSource audio;
    // Muzzle Flash 의 MeshRenderer 컴포넌트
    private MeshRenderer muzzleFlash;

    private void Start()
    {
        audio = GetComponent<AudioSource>();

        // FirePos 하위에 있는 MuzzleFlash의 Material 컴포넌트 추출
        muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();
        // 처음 시작할 때 비활성화
        muzzleFlash.enabled = false;
    }

    private void Update()
    {
        // 마우스 왼쪽 버튼을 클릭했을 때 Fire함수 호출
        if (Input.GetMouseButtonDown(0)){
            Fire();
        }
    }

    void Fire()
    {
        // Bullet 프리팹을 동적으로 생성(생성할 객체, 위치, 회전)
        Instantiate(bullet, firePos.position, firePos.rotation);
        // 총소리 발생
        audio.PlayOneShot(fireSfx, 1.0f);
    }
}

 

 

MuzzleFalsh 머티리얼의 Offsset 값을 변경하려면 머티리얼 정보를 담고 있는 MeshRenderer 컴포넌트에 접근

 // FirePos 하위에 있는 MuzzleFlash의 Material 컴포넌트 추출
 muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();

총을 발사할 때만 렌더링 해야하므로 MeshRenderer 컴포넌트를 비활성화 한다.

// 처음 시작할 때 비활성화
muzzleFlash.enabled = false;

 

코루틴 함수

유니티는 게임을 실행하면 모든 스크립트에 일종의 메세지 루프(Message Loop)가 동작

메세지 루프란 유니티의 다양한 이벤트 함수가 정해진 순서대로 실행되는 순환구조

일반적인 함수를 호출하면 해당 함수 안의 로직을 다 수행해향만 실행이 끝남

 

블로킹(Blocking) : 10초 정도 걸리는 로직이 있다면 10초동안 다른 로직 실행 불가함, 즉 게임이 10초 동안 멈춰있음 이 상태를 

 

멀티 스레드 : 메세지 루프의 다른 함수가 정상적으로 실행되면서 시간이 오래 걸리는 함수를 병렬로 호출

 

멀티스레드와 같이 비동기로 처리해야 하는 로직을 구현하기 위해 멀티 스레드와 유사한 코루틴을 제공

Co 협력 + Routine 루틴 = 협력 루틴, 협력 동작

*** 메세지 루프와 코루틴이 서로 번갈아 가면서 로직을 수행함

Unity - Manual: Order of execution for event functions (unity3d.com)

 

Unity - Manual: Order of execution for event functions

Instantiating Prefabs at run time Order of execution for event functions Event functions are a set of built-in events that your MonoBehaviour scriptsA piece of code that allows you to create your own Components, trigger game events, modify Component proper

docs.unity3d.com

    void Fade()
    {
        for(float f = 1f; f>=0;f-=0.1f){
            Color c = GetComponent<Renderer>().material.color;
            c.a = f;
            GetComponent<Renderer>().material.color = c;

        }
    }

 

위와 같이 코드를 작성하면 한 프레임 안에 끝나서 점진적으로 투명해지는 모습을 볼 수 없다.

 

눈으로 투명 처리 되는 과정을 확인하기 위해 코루틴을 사용한다.

IEnumerator Fade()
{
    for (float f = 1f; f >= 0; f -= 0.1f)
    {
        Color c = GetComponent<Renderer>().material.color;
        c.a = f;
        GetComponent<Renderer>().material.color = c;

        yield return null;
    }
}

 

코루틴 함수는 안에 있는 yield 키워드를 만나면 제어 권한을 유니티 메인 메세지 루프로 양보하는 방식으로 점진적인 작업 처리

 

yield return null 은 다음 프레임까지 해당 코루틴을 잠시 대기 하는 동안 메인 메시지 루프로 제어권을 넘겨 다른 작업을 처리하는 의미

프로세스가 블로킹되지 않고 마치 멀티 스레드로 처리하는 것과 비슷한 효과

 

코루틴 함수는 일반 함수를 호출하듯이 함수명으로 호출할 수 없음

private void Update()
{
    if(Input.GetKeyDown(KeyCode.Space))
    {
        StartCoroutine("Fade"); // 함수명을 문자열로 전달하는 방식
        StartCoroutine(Fade()); // 함수의 원형을 전달하는 방식 (권장)
    }
}

위와 같이 StartCoroutine 으로 실행해야됨

문자열로 실행하게되면 가비지 컬랙션이 발생하여 함수의 원형을 사용하길 권장한다.

 

다음 프레임으로 정지하는 yield return null 대신에 WaitForSeconds 를 사용해 일정시간을 정지시킬 수 있다.

bool isDie;

IEnumerator CheckState()
{
    while (!isDie)
    {
        // 적 캐릭터의 상태를 체크하는 로직
        yield return new WaitForSeconds(0.3f);
    }

MuzzleFlash 의 블링크 효과

총알을 발사할 때 MuzzleFlash 가 깜빡거리는 블링크 효과를 구현

앞서 공부한 코루틴 함수를 사용해 구현하면 총구의 화염 효과를 표현할 수 있음

using System.Collections;
using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class FireCtrl : MonoBehaviour
{
    // 총알 프리팹
    public GameObject bullet;
    // 총알 발사 좌표
    public Transform firePos;
    // 총소리에 사용할 오디오 음원
    public AudioClip fireSfx;

    // AudioSource 컴포넌트를 저장할 변수
    private new AudioSource audio;
    // Muzzle Flash 의 MeshRenderer 컴포넌트
    private MeshRenderer muzzleFlash;

    private void Start()
    {
        audio = GetComponent<AudioSource>();

        // FirePos 하위에 있는 MuzzleFlash의 Material 컴포넌트 추출
        muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();
        // 처음 시작할 때 비활성화
        muzzleFlash.enabled = false;
    }

    private void Update()
    {
        // 마우스 왼쪽 버튼을 클릭했을 때 Fire함수 호출
        if (Input.GetMouseButtonDown(0)){
            Fire();
        }
    }

    void Fire()
    {
        // Bullet 프리팹을 동적으로 생성(생성할 객체, 위치, 회전)
        Instantiate(bullet, firePos.position, firePos.rotation);
        // 총소리 발생
        audio.PlayOneShot(fireSfx, 1.0f);
        // 총구 화염 효과 코루틴 함수 호출
        StartCoroutine(ShowMuzzleFlash());
    }

    IEnumerator ShowMuzzleFlash()
    {
        // MuzzleFlash 활성화
        muzzleFlash.enabled = true;

        // 0.2초 동안 대기(정지)하는 동안 메세지 루프로 제어권을 양보
        yield return new WaitForSeconds(0.2f);

        // MuzzleFlash 비활성화
        muzzleFlash.enabled = false;
    }
}

 

MuzzleFlash의 텍스처 오프셋 변경

단순한 MeshRenderer 컴포넌트 활성화/ 비활성화 하는 것만으로는 총구 화염 효과라고 보기에는 어려움

오프셋을 불규칙하게 변경

using System.Collections;
using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class FireCtrl : MonoBehaviour
{
    // 총알 프리팹
    public GameObject bullet;
    // 총알 발사 좌표
    public Transform firePos;
    // 총소리에 사용할 오디오 음원
    public AudioClip fireSfx;

    // AudioSource 컴포넌트를 저장할 변수
    private new AudioSource audio;
    // Muzzle Flash 의 MeshRenderer 컴포넌트
    private MeshRenderer muzzleFlash;

    private void Start()
    {
        audio = GetComponent<AudioSource>();

        // FirePos 하위에 있는 MuzzleFlash의 Material 컴포넌트 추출
        muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();
        // 처음 시작할 때 비활성화
        muzzleFlash.enabled = false;
    }

    private void Update()
    {
        // 마우스 왼쪽 버튼을 클릭했을 때 Fire함수 호출
        if (Input.GetMouseButtonDown(0)){
            Fire();
        }
    }

    void Fire()
    {
        // Bullet 프리팹을 동적으로 생성(생성할 객체, 위치, 회전)
        Instantiate(bullet, firePos.position, firePos.rotation);
        // 총소리 발생
        audio.PlayOneShot(fireSfx, 1.0f);
        // 총구 화염 효과 코루틴 함수 호출
        StartCoroutine(ShowMuzzleFlash());
    }

    IEnumerator ShowMuzzleFlash()
    {
        // 오프셋 좌푯값을 랜덤 함수로 생성
        Vector2 offset = new Vector2(Random.Range(0, 2), Random.Range(0, 2)) * 0.5f;

        // 텍스처의 오프셋 값 설정
        muzzleFlash.material.mainTextureOffset = offset;

        // MuzzleFlash의 회전 변경
        float angle = Random.Range(0, 360);
        muzzleFlash.transform.localRotation = Quaternion.Euler(0, 0, angle);

        // MuzzleFlash의 크기 조절
        float scale = Random.Range(1.0f, 2.0f);
        muzzleFlash.transform.localScale = Vector3.one * scale;

        // MuzzleFlash 활성화
        muzzleFlash.enabled = true;

        // 0.2초 동안 대기(정지)하는 동안 메세지 루프로 제어권을 양보
        yield return new WaitForSeconds(0.2f);

        // MuzzleFlash 비활성화
        muzzleFlash.enabled = false;
    }
}

텍스처의 오프셋 값을 수정하기 위해 mainTextureOffset 속성 사용 

Random.Range(0,2) 가 반환하는 난수는 0,1이고 이 값에 0.5를 곱하면 0.0f, 0.5 값중 하나가 나오게 된다.

// 오프셋 좌푯값을 랜덤 함수로 생성
Vector2 offset = new Vector2(Random.Range(0, 2), Random.Range(0, 2)) * 0.5f;

 

아래의 두 문장 둘다 결과 값은 똑같다

// 텍스처의 오프셋 값 설정
muzzleFlash.material.mainTextureOffset = offset;
muzzleFlash.material.SetTextureOffset("_MainTxt", offset);

 

Quad로 만든 MuzzleFlash는 x 축을 기준으로 -90도 회전 된 모델로서 z축을 기준으로 회전시켜야하므로 Vector3.forward를 사용해도 됨

// MuzzleFlash의 회전 변경
float angle = Random.Range(0, 360);
muzzleFlash.transform.localRotation = Quaternion.Euler(0, 0, angle);
muzzleFlash.transform.localRotation = Quaternion.Euler(Vector3.forward * angle);

 

코루틴의 응용 - 임계치

using System.Collections;
using UnityEngine;

public class PlayerCtrl : MonoBehaviour
{
    [SerializeField]
    private Transform tr;
    // 이동 속력 변수
    public float moveSpeed = 10.0f;
    // 회전 속도 변수
    public float turnSpeed = 80.0f;
    // Animation 컴포넌트를 저장할 변수
    private Animation anim;

    IEnumerator Start()
    {
        // 컴포넌트를 추출해 변수에 대입
        tr = GetComponent<Transform>();
        anim = GetComponent<Animation>();

        // 애니메이션 실행
        anim.Play("Idle");

        turnSpeed = 0.0f;
        yield return new WaitForSeconds(0.3f);
        turnSpeed = 80.0f;
    }
    
    ...생략

 

 

Start함수는 다른 이벤트 함수와는 다르게 코루틴으로 실행할 수 있다.

회전속도를 지정하는 변수를 0.0f 로 초기화 한다.잠시 대기후 원래의 회전 속도값을 turnSpeed 변수에 지정함

 

***즉 처음 시작할 때 넘어온 마우스의 불규칙한 값을 적용하지 않고 안정적인 값이 넘어올 때까지 잠시 대기한 후 정상적인 로직을 실행하도록 함

 

이처럼 처음 시작할 때 쓰레기값이 넘어온다면 코루틴을 활용해 허용 임계치를 적용할 수 있음

 

모바일에서도 가상 조이스틱을 터치했을 때도 터치하자마자 조이스틱의 방향 벡터를 계산해 사용하면 쓰레기 값이 넘어와 주인공 캐릭터가 원하지 않는 방향으로 움직이는 현상이 발생함

 

일정 시간이 지난 후부터 계싼하거나 조이스틱의 이동 변위 값이 임계치 이상일 때만 계산하는 방식을 착용함

 

반응형