유니티 탑다운 슈터 게임 좀비서바이버 개발일지 4일차
총알이 발사되고, 투사체의 궤적을 표시하는 LineRenderer 를 사용해서 총알의 궤적을 잠시동안 나타내는 작업은 다른 작업과 동시에 진행 되도록 병렬화할 필요가 있기 때문에 코루틴을 사용하여 ShotEffect 를 완성하였습니다.
Fire (발사) 메서드 완성하기
현재 발사가능한 상태인지 확인 && 마지막 총 발사 시점에서 GunData의 속성 중 하나인 발사간격 보다 시간이 더 지났을 때
if(state == State.Ready && Time.time >= lastFireTime + gunData.timeBetFire)
이렇게 if문이 완성 되었습니다.
if문 안에는 lastFireTime을 현재 시간인 Time.time으로 갱신하는 처리와 함께 Shot() 메서드가 실행되게 됩니다.
Shot 메서드 완성하기
RayCast의 사용
RayCast는 보이지 않는 광선을 쐈을때, 광선이 다른 콜라이더와 충돌하는지 검사하는 기능입니다.
이때 광선은 Ray타입으로 유니티에서 자체적으로 제공하는 타입이며 광선의 정보를 따로 담고 있는 객체입니다.
RayCast를 사용해서 Shot() 메서드를 완성하였습니다.
private void Shot() {
//container for hit information
RaycastHit hit;
//container for bullet hitzone
Vector3 hitPosition = Vector3.zero;
//Raycasting
if(Physics.Raycast(fireTransform.position, fireTransform.forward, out hit, fireDistance))
{
//if Ray collides with an object, get IDamageable from target
IDamageable target = hit.collider.GetComponent<IDamageable>();
if(target != null )
{
//get IDamageable type success, Damage calculation will progress
target.OnDamage(gunData.damage, hit.point, hit.normal);
}
//saving position info
hitPosition = hit.point;
}
else
{
//if Ray didn't collides any object, hitzone is maxRange of bullet
hitPosition = fireTransform.position + fireTransform.forward * fireDistance;
}
//Play ShotEffect
StartCoroutine(ShotEffect(hitPosition));
//Mag round -1
magAmmo--;
if(magAmmo <= 0)
{
//if mag is empty, change state Empty
state = State.Empty;
}
}
IDamageable 타입과 OnDamage 메서드는 나중에 구현을 만들게 됩니다.
광선을 쏴서 그 광선이 충돌했을 경우 충돌한 물체에서 IDamageable 타입의 target을 가져옵니다.
해당 타입을 가져오는데에 성공했다면 대미지를 입히는 OnDamage 메서드를 실행하고, 맞은 장소(점)를 Vector3 타입 hitPosition에 저장합니다.
else문은 광선을 쐈는데 어떤 Collider에도 충돌하지 않았을 경우입니다.
Vector3의 연산
fireTransform.position , fireTransform.foward 는 Vector3 타입이지만
fireDistance는 float 타입입니다.
fireTransform.position + fireTransform.foward * fireDistance 연산이 이루어지는데
사칙연산 우선순위에 따라 스칼라 연산을 통해 방향과 속도(float) 곱이 이루어지고 최종적으로 position 정보에 더해지게 되면 이는 공간좌표를 의미합니다. 처음 fireTransform.position 에서 방향x속도로 1초동안 나아간 결과 공간값이 되겠습니다. 그런데 fireTransform.foward는 정규화된 벡터라서 값이 1이죠? 이러면 그냥 거리를 더하는 개념과 동일하다 봐야죠?
이렇게, fireDistance 가 총알의 사거리를 의미하게 됩니다.
RayCasting 결과 어떤 Collider와도 충돌하지 않았을 경우 이를 hitPosition에 담습니다.
이후엔 탄창에서 탄약을 제거, 남은 탄약이 0이하일 경우 State를 Empty로 변경합니다.
다음은 Reload 메서드 입니다.
3요소에 해당 하지 않으면 리로딩을 실시합니다.
이미 재장전 중이거나 / 예비탄약이 없거나 / 현재 탄약이 최대탄창수 이상 이미 가득 차있거나
public bool Reload() {
//Reloading condition, If you meet any of the three conditions
if (state == State.Reloading || ammoRemain <= 0 || magAmmo >= gunData.magCapacity)
{
return false;
}
StartCoroutine(ReloadRoutine());
return true;
}
리로딩 역시 코루틴을 사용해서 진행됩니다. 리로드 사운드를 재생하면서 리로드 애니메이션이 나오고, 리로딩이 진행되는 중에는 연산을 하지 않게 됩니다. WaitForSeconds 를 사용했네요.
private IEnumerator ReloadRoutine() {
// 현재 상태를 재장전 중 상태로 전환
state = State.Reloading;
gunAudioPlayer.PlayOneShot(gunData.reloadClip);
// operation standby to complete reloading
yield return new WaitForSeconds(gunData.reloadTime);
//calculating to fill ammo
int ammoToFill = gunData.magCapacity - magAmmo;
//if remaining ammo is lower than to fill ammo, recalculate it be ammoReamain to ZERO
if (ammoRemain < ammoToFill)
ammoToFill = ammoRemain;
magAmmo += ammoToFill;
ammoRemain -= ammoToFill;
//changing state to ready
state = State.Ready;
}
대기가 끝나면 예비탄창과 장전된 탄약 사이의 계산이 시작됩니다.
ammoToFill 을 지역 변수로 선언, 채워질 탄약의 값을 담습니다.
탄창 최대치 - 현재 탄창에 남은 탄약 을 ammoToFill의 기본값으로 들고 가되
예비탄약이 채워야할 탄약보다 적은 경우 예비탄약의 양을 몽땅 채워야할 탄약으로 처리합니다.
이렇게 재장전을 하면 예비탄약이 0이 되겠지요.
일반적인 경우 채워야할 탄약을 예비탄약에서 뺄셈 연산을 해주고
현재 탄창을 ammoToFill 만큼 채우게 됩니다.