본문 바로가기

Unity/▶ Game Development: Zombie Game

[Zombie Game] #2. Gun 오브젝트와 스크립트

728x90
반응형

Gun 스크립트

Gun 스크립트의 필드 /  전역변수와 상태

스크립트 내에서 선언한 구조체와 선언 변수

enum타입의 State 구조체 선언
public enum State {
   Ready, // 발사 준비됨
   Empty, // 탄창이 빔
   Reloading // 재장전 중
}
//구조체와 마찬가지로 참조가 가능함.
public State state { get; private set; } // 현재 총의 상태를 표현하는 프로퍼티
public Transform fireTransform; // 총알이 발사될 위치
public ParticleSystem muzzleFlashEffect; // 총구 화염 효과
public ParticleSystem shellEjectEffect; // 탄피 배출 효과

private LineRenderer bulletLineRenderer; // 총알 궤적을 그리기 위한 렌더러

 

총의 설정값을 다룰 선언 변수

public float damage = 25; // 공격력
private float fireDistance = 50f; // 사정거리

public int ammoRemain = 100; // 남은 전체 탄약
public int magCapacity = 25; // 탄창 용량
public int magAmmo; // 현재 탄창에 남아있는 탄약

public float timeBetFire = 0.12f; // 총알 발사 간격
public float reloadTime = 1.8f; // 재장전 소요 시간
private float lastFireTime; // 총을 마지막으로 발사한 시점

Awake() 메서드

메서드의 코드는 아래의 [더보기]를 참고한다.

더보기
private void Awake() {
    // 사용할 컴포넌트들의 참조를 가져오기
    gunAudioPlayer = GetComponent<AudioSource>();
    bulletLineRenderer = GetComponent<LineRenderer>();

    // 사용할 점을 두개로 변경
    bulletLineRenderer.positionCount = 2;
    // 라인 렌더러를 비활성화
    bulletLineRenderer.enabled = false;
}

위에 코드를 보면, 라인 렌더러가 사용할 점의 수를 2로 변경하고 라인 렌더러 컴포넌트를 미리 비활성화한다. 

라인 렌더러의 활성상태는 인스펙터 창에서 직접 설정이 가능하지만 코드 내에서도 참조를 통해 접근 및 활성화가 가능하다.

 Renderer

렌더러는 객체를 화면에 나타나게 만드는 것이다. 이 클래스를 사용하여 모든 객체, 메시 또는 파티클 시스템의 렌더러에 액세스 하며 렌더러를 비활성화하여 개체를 보이지 않게 만들 수 있고 자료에 액세스 할 수 있다. 

 

Renderer.enabled 활성화된 경우 렌더링된 3D 개체를 표시한다.
Renderer.positionCount 정점 수를 설정/가져오며 선의 정점 수를 반환한다. 
Renderer.SetPosition 선의 정점 위치를 설정한다.

OnEnable() 메서드

메서드의 코드는 아래의 [더보기]를 참고한다.

더보기
private void OnEnable() {
    // 현재 탄창을 가득채우기
    magAmmo = magCapacity;
    // 총의 현재 상태를 총을 쏠 준비가 된 상태로 변경
    state = State.Ready;
    // 마지막으로 총을 쏜 시점을 초기화
    lastFireTime = 0;
}

OnEnable 메서드는 이전에서 다뤘듯이 컴포넌트가 활성화될 때마다 실행되는 유니티 엔진의 기본 메서드이다.

이 메서드는 Gun 스크립트에서 Gun 컴포넌트가 활성화될 때마다 총의 상태와 기본 탄알의 처리를 구현한다.

 

· magAmmo: 현재 탄창에 남아있는 탄약( 스크립트 내 전역 공간에서 선언)

· magCapacity: 탄창 용량. 25로 초기화됨 (스크립트 내 전역 공간에서 선언)

 

앞서 선언한 구조체를 참조하여 현재 상태를 총을 쏠 상태로 변환(Ready)하고 마지막으로 총을 쏜 시점을 초기화한다.

ShotEffect() 메서드

메서드의 코드는 아래의 [더보기]를 참고한다.

더보기
// 발사 이펙트와 소리를 재생하고 총알 궤적을 그린다
private IEnumerator ShotEffect(Vector3 hitPosition) {
    
    muzzleFlashEffect.Play();// 총구 화염 효과 재생
    shellEjectEffect.Play();// 탄피 배출 효과 재생
    gunAudioPlayer.PlayOneShot(shotClip);// 총격 소리 재생

    // 선의 시작점은 총구의 위치
    bulletLineRenderer.SetPosition(0, fireTransform.position);
    // 선의 끝점은 입력으로 들어온 충돌 위치
    bulletLineRenderer.SetPosition(1, hitPosition);
    // 라인 렌더러를 활성화하여 총알 궤적을 그린다
    bulletLineRenderer.enabled = true;

    // 0.03초 동안 잠시 처리를 대기
    yield return new WaitForSeconds(0.03f);

    // 라인 렌더러를 비활성화하여 총알 궤적을 지운다
    bulletLineRenderer.enabled = false;
}

[ 라인 렌더러 설정 ]

bulletLineRenderer.SetPosition(0, fireTransform.position);// 선의 끝점은 입력으로 들어온 충돌 위치
bulletLineRenderer.SetPosition(1, hitPosition);// 라인 렌더러를 활성화하여 총알 궤적을 그린다
bulletLineRenderer.enabled = true;

 

· bulletLineRenderer에 할당된 라인 렌더러 컴포넌트가 그리는 선분의 첫 번째 점은 총구 위치를 설정한다.

· 두 번째 점은 탄알이 닿는 곳의 위치를 설정하며 ShotEffect 메서드가 입력으로 받은 hitPosition이다.

private IEnumerator ShotEffect(Vector3 hitPosition) 

 

yield return new WaitForSeconds0.03f); // 0.03초 동안 잠시 처리를 대기
bulletLineRenderer.enabled = false;  // 라인 렌더러를 비활성화하여 총알 궤적을 지운다

Fire() 메서드

메서드의 코드는 아래의 [더보기]를 참고한다.

더보기
public void Fire() {
    // 현재 상태가 발사 가능한 상태
    // && 마지막 총 발사 시점에서 timeBetFire 이상의 시간이 지남
    if (state == State.Ready && Time.time >= lastFireTime + timeBetFire)
    {
        // 마지막 총 발사 시점을 갱신
        lastFireTime = Time.time;
        // 실제 발사 처리 실행
        Shot();
    }
}

Fire() 메서드는 public으로 외부에 공개된 메서드이며 총을 발사 가능한 상태에서만 Shot() 함수가 실행되도록 하는 조건을 가진 실행 함수이다. 실행 조건은 아래와 같다.

 

  1. 총의 현재 상태(State)의 값이 총을 발사할 준비된(Ready) 상태인 확인

  2. 현재 시간이 총을 최근에 발사한 시점 + 발사 간격인지 확인( Time.time >= lastFireTime + timeBetFire)

 

2번의 조건을 다시 말하면 총이 모션이 겹치지 않고 '총을 다 쏜 후' 인지를 검사하는 조건이라고 할 수 있다.

Shot() 메서드

메서드의 코드는 아래의 [더보기]를 참고한다.

더보기
private void Shot() {
    // 레이캐스트에 의한 충돌 정보를 저장하는 컨테이너
    RaycastHit hit;
    // 총알이 맞은 곳을 저장할 변수
    Vector3 hitPosition = Vector3.zero;

    // 레이캐스트(시작지점, 방향, 충돌 정보 컨테이너, 사정거리)
    if (Physics.Raycast(fireTransform.position,
        fireTransform.forward, out hit, fireDistance))
    {
        // 레이가 어떤 물체와 충돌한 경우

        // 충돌한 상대방으로부터 IDamageable 오브젝트를 가져오기 시도
        IDamageable target =
            hit.collider.GetComponent<IDamageable>();

        // 상대방으로 부터 IDamageable 오브젝트를 가져오는데 성공했다면
        if (target != null)
        {
            // 상대방의 OnDamage 함수를 실행시켜서 상대방에게 데미지 주기
            target.OnDamage(damage, hit.point, hit.normal);
        }

        // 레이가 충돌한 위치 저장
        hitPosition = hit.point;
    }
    else
    {
        // 레이가 다른 물체와 충돌하지 않았다면
        // 총알이 최대 사정거리까지 날아갔을때의 위치를 충돌 위치로 사용
        hitPosition = fireTransform.position +
                      fireTransform.forward * fireDistance;
    }

    // 발사 이펙트 재생 시작
    StartCoroutine(ShotEffect(hitPosition));

    // 남은 탄환의 수를 -1
    magAmmo--;
    if (magAmmo <= 0)
    {
        // 탄창에 남은 탄약이 없다면, 총의 현재 상태를 Empty으로 갱신
        state = State.Empty;
    }
}

[ 코드 해석 ]

RaycastHit hit;// 레이캐스트에 의한 충돌 정보를 저장하는 컨테이너
Vector3 hitPosition = Vector3.zero; // 총알이 맞은 곳을 저장할 변수

· hit은 레이캐스트의 결과를 저장할 변수이다.

· hitPosition은 탄알이 충돌한 위치를 저장하는 변수이며 Vector3 타입을 가진다.

 

Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo,float maxDistance)

 

Raycast() 메서드를 실행 후 hitInfo에 채워진 정보를 꺼내 충돌 정보를 알 수 있다.

 

· Vector3 origin : 레이의 시작점

· Vector3 direction : 레이의 방향

· RaycastHit hitInfo: 레이가 충돌한 경우 hitInfo에 자세한 충돌 정보가 저장됨

· float maxDistance: 레이 충돌을 검사할 최대 거리

 

Raycast() 메서드는 충돌 여부를 bool 타입으로 반환한다. 이때 hitInfo에 out 키워드를 붙여 사용한다는 점에 주의하자! 

 

out 키워드는 메서드가 return 이외의 방법으로 추가 정보를 반환할 수 있게 한다. out 키워드로 입력된 변수는 메서드 내부에서 변경된 사항이 반영된 채 되돌아오기 때문이다. Raycast() 메서드는 자신의 내부에서 hitInfo에 충돌 정보를 채운다.

이때 Raycast() 메서드가 종료되었을 때 변경 사항이 유지된 채로 hitInfo가 반환된다.

/*생략*/
// 레이캐스트(시작지점, 방향, 충돌 정보 컨테이너, 사정거리)// 레이가 어떤 물체와 충돌한 경우
if (Physics.Raycast(fireTransform.position,fireTransform.forward, out hit, fireDistance)){
    // 충돌한 상대방으로부터 IDamageable 오브젝트를 가져오기 시도
    IDamageable target = hit.collider.GetComponent<IDamageable>();

    // 상대방으로 부터 IDamageable 오브젝트를 가져오는데 성공했다면 상대방의 OnDamage 함수를 실행시켜서 상대방에게 데미지 주기
    if (target != null) target.OnDamage(damage, hit.point, hit.normal);
    hitPosition = hit.point;// 레이가 충돌한 위치 저장
}
else{// 레이가 다른 물체와 충돌하지 않았다면

    // 총알이 최대 사정거리까지 날아갔을때의 위치를 충돌 위치로 사용
    hitPosition = fireTransform.position +fireTransform.forward * fireDistance;
}

· 레이가 어떤 게임 오브젝트의 콜라이더와 충돌하면 조건을 충족해 if 문이 실행된다.

· 조건문이 실행되면 충돌한 상대방 게임 오브젝트로부터 IDamageable 타입의 컴포넌트를 가져와 target에 할당한다.

 

target.OnDamage(damage, hit.point, hit.normal);
// damage: 탄알의 대미지
// hit.point:  레이가 충돌한 위치
// hit.normal : 레이가 충돌한 위치

· hit.collider는 충돌한 상대방 게임 오브젝트의 콜라이더 컴포넌트이다.

· target이 null 상태가 아니라면 IDamageable 타입의 컴포넌트가 할당된 상태로 메서드가 구현 가능한 상태이다.

· taget이 구현 가능한 상태인 경우 OnDamage() 메서드를 실행하고 인자 값에 따른 처리를 구현한다.

 

· 레이가 다른 물체와 충돌하지 않은 경우 라인 레더러가 그리는 탄알 궤적의 끝 점이 최대 사정거리에 위치한다.

 

hitPosition = fireTransform.position +fireTransform.forward * fireDistance;

· 레이캐스트 처리가 끝나면 ShotEffect() 코루틴을 실행하고 탄알의 충돌 위치 hitposition을 입력으로 넘겨준다. 그리고 남은 탄알 수 에서 1을 뺀다.(총을 쐈으므로 1을 줄인다.)

 

magAmmo--; // 남은 탄환의 수를 -1
if (magAmmo <= 0) state = State.Empty;
// 탄창에 남은 탄약이 없다면, 총의 현재 상태를 Empty으로 갱신

Reload() 메서드

메서드의 코드는 아래의 [더보기]를 참고한다.

더보기
// 재장전 시도
public bool Reload() {
    if (state == State.Reloading || ammoRemain <= 0 || magAmmo >= magCapacity)
    	return false;
    
	/*
    state == state.REloading: 이미 재장전을 하는 중
    ammoRemain <= 0:  재장전에 사용할 남은 탄알이 없음
    magAmo >= magCapacity
    */


    StartCoroutine(ReloadRoutine());// 재장전 처리 시작
    return true;
}

ReloadRoutine() 코루틴 메서드

더보기
private IEnumerator ReloadRoutine() {
    // 현재 상태를 재장전 중 상태로 전환
    state = State.Reloading;
    // 재장전 소리 재생
    gunAudioPlayer.PlayOneShot(reloadClip);

    // 재장전 소요 시간 만큼 처리를 쉬기
    yield return new WaitForSeconds(reloadTime);

    // 탄창에 채울 탄약을 계산한다
    int ammoToFill = magCapacity - magAmmo;

    // 탄창에 채워야할 탄약이 남은 탄약보다 많다면,
    // 채워야할 탄약 수를 남은 탄약 수에 맞춰 줄인다
    if (ammoRemain < ammoToFill)
    {
        ammoToFill = ammoRemain;
    }

    // 탄창을 채운다
    magAmmo += ammoToFill;
    // 남은 탄약에서, 탄창에 채운만큼 탄약을 뺸다
    ammoRemain -= ammoToFill;

    // 총의 현재 상태를 발사 준비된 상태로 변경
    state = State.Ready;
}
728x90
반응형
댓글