총구 화염 효과는 파티클 효과나 성능을 고려해 가벼운 메시를 사용할 수 있다. 

하이라키뷰의 Player하위에 있는 FirePos를 선택하고 우클릭후 팝업 메뉴에서 Create>3D Object>Quad를 선택한다. 이름을 MuzzleFlash로 수정한다. 발사되는 총알의 충돌을 막기위해 Mesh Collider 컴포넌트는 삭제한다.

총구 화염은 실시간 그림자가 필요없으므로 Mesh Renderer의 Cast Shdows를 Off로 설정라고 조명과 관련된 Receive Shadows를 Uncheck한다. 내경우는 Disable되어 있어 끌수가 없었다. 이상함..

다운받았던 Resources>Texture>Wepons폴더의 MuzzleFlash.png파일을 끌어다 05.Images 폴더로 드래그해 임포트한다.

이 이미지는 4개의 화염이미지가 들어 있지만 4등분해서 랜덤하게 하나만 사용한다.

MuzzleFlash.png를 끌어다 MuzzleFlash에 적용한고 Shader를 Mobile/Particles/Additive로 하면 검은부분이 투명하게 바낀다.

Shader 왼쪽 삼각형을 눌러 메뉴를 펴서 Particle Texture Tiling을 0.5, 0.5로 하면 4장으로 자르게 된다.

밑의 offet을 변경해 4장중 한장을 고를 수 있다.

주인공 캐릭터 하위의 MuzzleFlash에 포한된 MeshRenderer 컴포넌트를 추출하는 로직을 FireCtrl스크립트에 추가한다. MuzzleFlash의 Offset값을 변경하려면 머터리얼 정보를 담고 있는 MeshRenderer컴포넌트에 접근해야 한다. FireCtrl에 이미 있는 firePos변수를 이용해서 접근한후 비활성화 한다.  총을 발사할때문 보이게 할것이다.

muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();

muzzleFlash.enabled = false;

실행해보면 MuzzleFlash의 MeshRenderer컴포넌트가 비활성활되어 있다.

Play옆 포즈를 누르고 씬뷰탭을 선택후 ALT+좌클릭후 마우스를 움직여 씬뷰를 회전시키면 총구의 화염이 없는 것도 확인할수 있다

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
[RequireComponent(typeof (AudioSource))]  //삭제되는것을 방지하는 어트리뷰트
public class FireCtrl : MonoBehaviour
{
    public GameObject bullet;  //총알프리팹
    public Transform firePos;  // 총알 발사 좌표
    public AudioClip fireStx;  //총소리에 사용할 음원
    private new AudioSource audio;  //오디오 소스 컴포넌트를 저장할 변수
    private MeshRenderer muzzleFlash;  //머즐이펙트 참조
    // Update is called once per frame
    private void Start() {
        audio = GetComponent<AudioSource>();  //오디오소스를 얻어온다
        muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();  //자식 컴포넌트를 얻어온다
        muzzleFlash.enabled = false;  //안보이게한다
    }
    void Update()
    {  //마우스 왼쪽 버튼을 클릭했을때  Fire 함수 호출
        if(Input.GetMouseButtonDown(0)) {
            Fire();  //발사처리
        }
    }
    void Fire() {
        Instantiate(bullet,firePos.position, firePos.rotation); //Bullet프리팹을 동적으로 발생
        audio.PlayOneShot(fireStx, 1.0f);  //총소리발생
    }
}

코루틴함수

유니티의 모든 스크립트는 메시지루트가 동작한다. 다양한 이벤트함수가 정해진 순서대로 실행되는 순환구조이다. 일반 함수를 호출하면 해당 함수 안의 로직이 다 수행해야만 실행이 끝난다. 

함수안에서 로직이 10초 걸린다고 하면10초동안 메시지루프의 다른 로직을 기다리게 하는데 이걸 Blocking이라고 한다. 

이를 개선하려면 함수를 병렬로 호출해야한다. 이걸 Multi thread라고 한다. 

유니티는 코루틴이라는 병렬처리기능을 제공한다. 

만일 Fade()라는 함수는 for문을 통해 알파값을 감소시켜 투명처리하는 함수인데 순식간에 처리하므로 효과를 확인할수 없어 갑자기 꺼지게 될것이다.

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;
    }
}

코루틴의 아이디어는 yield return null; 키워드를 만나면 제어 권한을 유니티시스템의 메시지루프로 양보하는 방식이다.

코루틴 함수는 열거자 IEnumerator 타입으로 선언해야 하고 함수내에 하나이상의 yeild 키워드를 사용해야 한다.

Ienumerate 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;
       yield return null;  //메시지루프로 제어권을 넘긴다.
    }
}

yield return null;키워드는 함수내에 여러개 삽입될 수도 있다.

코루틴 함수는 호출시 StartCoroutine()함수를 이용하여 호출한다. 전달인자는 함수의 원형(포인터)를 권장한다. "함수명"으로 호출하는건 가비지컬렉션을 발생시키 개별적으로 정지시킬수 없기 때문이다.

StartCoroutine(Fade());

yield return null대신 yield return WaitForSecond(0.3f)같이 하면 자기 자신을 0.3초동안 멈추게 할 수 있다. 대신 로직을 잘짜 무한 루프에 안 빠지게 해야한다. 다음은 적이 죽지 않았다면 0.3초간 기다려주는 로직이다. 만일 yield return new WaitForSeconds(0.3f)가 없다면 정말 무한루프에 빠질것이다.

bool isDie;
IEnumerator CheckState() {
    while(!isDie)  {  //죽지않았다면
        yield return new waitForSecond(0.3f); //3초 대기하기
    }
}

MuzzleFlash의 블링크 효과

총알알 발사할때 MuzzleFlash가 깜빡거리는 Blink효과를 구현해보자.  다음과 같이 하면 순식간에 일어나 효과가 없다

void ShowMuzzleFlash() {  //일반함수
    muzzleFlash.enabled = true;  //MuzzleFlash활성화
    muzzleFlash.enable = false; //MuzzleFlash비활성화
}

Blink효과는 점멸을 천천히 반복해야 하므로 코루틴을 사용해야한다.

Ienumerator void ShowMuzzleFlash() { //코루틴
    muzzleFlash.enabled = true; //MuzzleFlash활성화
    yield return new WaitForSecond(0.2f);
    muzzleFlash.enable = false;  //MuzzleFlash비활성화
}

MuzzleFlash의 텍스처 오프셋 변경

MuzzleFlash에 연결된 텍스처의 오프셋을 불규칙하게 변경해보자 또한 각도및 스케일도 조절해보자.

텍스처 오프셋값은 SetTextureOffset함수의 mainTextureOffset속성으로 설정할 수 있다.

처음 tiling걊을 (0.5,0.5)로 했기때문에 오프셋값은 다음 4개중 하나이다. (0,0), (0,0.5), (0.5,0), (0.5,0.5)

Randome.Range(0,2)가 리턴하는 숫자는 0, 1이므로 이값에 0.5f를 곱하여 이용하면 된다.

Vector2 offset = New Vector2(Randome.Range(0,2), Randome.Range(0,2))*0.5f;
muzzleFlash.material.mainTextureOffset = offset;
//muzzleFlash.material.setTextureOffset("_MainTxt",offset);  //이렇게도 가능하다

MuzzleFlash는 다른 게임오브젝트(FirePos)아래에 차일드화된 게임오브젝트로서, 좌표, 회전, 각도, 스케일을 수정하려면 반드시 localPosition, localRotation, localScale속성을 사용해야 한다.

MuzzleFlash를 회전시키기 위한 localRotation속성은 Quaternion 타입이므로 오일러각을 변화시키는 Quaternion.Euler(x,y,x)함수를 사용한다. 0~360도의 난수를 발생시켜 회전시켜본다. 유니티 원시모델 Quad로 만들어진 MuzzleFlash는 x축으로 -90도 회전된 모델로 z축을 기준으로 회전시켜야 하므로

float angle = Random.Range(0,360);
muzzleFlash.transfor.localRotation = Quaternion.Euler(0,0,angle);

다음과 같이 단위벡터를 사용할수도 있다.  z축은 Vector3.forward를 사용해야한다.

float angle = Random.Range(0,360);
muzzleFlash.transfor.localRotation = Quaternion.Euler(Vector3.forward*angle);

localScale을 이용해 사이즈를 조절한다. Vector3.one은 Vector3(1,1,1)을 나타내는 상수이다.

 

float scale = Random.Range(1.0f,2.0f);
muzzleFlash.transfor.localScale = Vector3.one * scale;

다음과 같이 FireCtrl을 변경하고 실행해보면 MuzzleFlash의 텍스처 크기 회전이 불규칙하게 변하는 것을 확인할 수 있다.

각도 때문에 게임뷰에서 확인이 어렵다면 게임뷰와 씬뷰를 동시에 보면서 할수 있다. 씬뷰는 게임중에도 위치와  회전을 할 수 있다.

왼쪽:씬뷰 어른쪽:게임뷰

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
[RequireComponent(typeof (AudioSource))]  //삭제되는것을 방지하는 어트리뷰트
public class FireCtrl : MonoBehaviour
{
    public GameObject bullet;  //총알프리팹
    public Transform firePos;  // 총알 발사 좌표
    public AudioClip fireStx;  //총소리에 사용할 음원
    private new AudioSource audio;  //오디오 소스 컴포넌트를 저장할 변수
    private MeshRenderer muzzleFlash;  //머즐이펙트 참조
    // Update is called once per frame
    private void Start() {
        audio = GetComponent<AudioSource>();  //오디오소스를 얻어온다
        muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();  //자식 컴포넌트를 얻어온다
        muzzleFlash.enabled = false;  //안보이게한다
    }
    void Update()
    {  //마우스 왼쪽 버튼을 클릭했을때  Fire 함수 호출
        if(Input.GetMouseButtonDown(0)) {
            Fire();  //발사처리
        }
    }
    void Fire() {
        Instantiate(bullet,firePos.position, firePos.rotation); //Bullet프리팹을 동적으로 발생
        audio.PlayOneShot(fireStx, 1.0f);  //총소리발생\
        StartCoroutine(ShowMuzzleFlash()); //총구화염효과 코루틴함수 호출
     
    }
    IEnumerator ShowMuzzleFlash() {  //코루틴
        Vector2 offset = new Vector2(Random.Range(0, 2), Random.Range(0, 2)) * 0.5f;
        muzzleFlash.material.mainTextureOffset = offset;  //4장중 한장을 선택
        float angle = Random.Range(0, 360);
        muzzleFlash.transform.localRotation = Quaternion.Euler(0,0, angle);  //로컬회전, 쿼터니언으로 변경할것
        float scale = Random.Range(1.0f, 2.0f);
        muzzleFlash.transform.localScale = Vector3.one * scale;  //로컬스케일변경
        muzzleFlash.enabled=true; //muzzleFlash 활성화
        yield return new WaitForSeconds(0.2f);  // new를 붙여줘야함. 권한을 0.2초 넘겨줌
        muzzleFlash.enabled=false;  //muzzleFlash 비활성화
    }
}

코루틴의 응용 -  임계치

게임을 실행하면 Player의 방향이 불규칙하다. 이건 마우스의 좌우 이동값이 불규칙하게 넘어오기 때문이다. Start함수를 코루틴으로 변경시켜 해결해보자. Player스크립트를 다음과 같이 수정한다. 교재는 PlayerCtrl이다.

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;

public class Player : MonoBehaviour {
    // Start is called before the first frame update
    Transform tr;
    private float moveSpeed=10f;
    private float turnSpeed=0f;
    private Animation anim;

    IEnumerator  Start() {  //start()함수는 코루틴으로 실행할 수 있다.
        tr = GetComponent<Transform>();
        anim = GetComponent<Animation>();  //추가된 코드
        anim.Play("Idel");
        yield return new WaitForSeconds(0.3f);
        turnSpeed = 500f;
    }

    // Update is called once per frame
    void Update() {
        float h = Input.GetAxis("Horizontal");  //AD 입력 좌우
        float v = Input.GetAxis("Vertical");  //WS 입력 전후
        if (Mathf.Abs(h) > float.Epsilon || Mathf.Abs(v) > float.Epsilon) {  //움직임이 없다면 불필요한 동작을 안한다.
            Vector3 dir = Vector3.right * h + Vector3.forward * v;
            tr.Translate(dir.normalized * moveSpeed * Time.deltaTime);
            PlayerAnim(h, v);
        }
        float r = Input.GetAxis("Mouse X");  //마우스 x축 입력 
        tr.Rotate(Vector3.up * turnSpeed * Time.deltaTime * r);
    }
    void PlayerAnim(float h, float v) {
        Debug.Log("Move");
        if (v >= 0.1f) {
            anim.CrossFade("RunF",0.25f);
        } else if(v <= -0.1f) {
            anim.CrossFade("RunB", 0.25f);
        } else if(h >= 0.1f) {
            anim.CrossFade("RunR", 0.25f);
        } else if (h <= -0.1f) {
            anim.CrossFade("RunL", 0.25f);
        } else {
            anim.CrossFade("Idle", 0.25f);
        }
    }
}

+ Recent posts