탑건: 매버릭 미션 개발하기 #2 : 레이저 유도 폭탄 만들기

Lunetis·2022년 7월 27일
0

탑건: 매버릭

목록 보기
3/14
post-thumbnail

영화에서는 적국의 기지를 폭격하기 위해 레이저 유도 폭탄을 사용합니다.

그런데 그 폭탄이 현실에서 어떤 것이냐면,

그렇다고 하네요.


잠시 에이스 컴뱃에서의 GBU-24를 봅시다.

게임 내에서는 GPB(Guided Penetration Bomb)라는 이름으로 등장하고, 근처에 있는 지상 목표물에 자동적으로 타게팅되고, 발사하면 타게팅된 목표물로 자동으로 날아가는 방식입니다.


하지만 실제로는 이렇게 간단하지 않습니다.

게임에서 조종사는 발사하고 내버려두지만, 실제 레이저 유도 폭탄은 명중하기까지 목표물에 레이더를 조사하고 있어야 합니다. 레이저 조사를 하지 않거나 끊으면 폭탄은 그냥 자유 낙하하게 됩니다.

또한 F/A-18E/F 슈퍼 호넷은 단좌기(F/A-18E)와 복좌기(F/A-18F)로 나뉩니다.

단좌기의 경우 혼자서 조종, 발사, 레이더 조사까지 하고, (실제로 그런지는 모르겠습니다만)
복좌기의 경우 한 명이 조종하고 다른 한 명이 발사와 레이더 조사를 맡습니다.
영화에서도 단좌기와 복좌기의 차이를 확인할 수 있습니다.


그렇지만 단좌기든 복좌기든, 플레이어는 조종과 무기 발사를 모두 담당해야 합니다.

조종은 하던대로 하면 되는데, 무기 발사와 레이더 조사 부분을 어떻게 만들지 생각해야 합니다.
이 부분을 어떻게 구현하느냐에 따라 미션의 난이도가 천차만별이 되기 때문이죠.



일단 발사부터 시키기

자세한 걸 설정하기 전에 우선 기본적인 작업부터 시작합시다.

미사일이든 폭탄이든 발사하는 것부터 가능해야 하지 않겠어요?


https://sketchfab.com/3d-models/low-poly-gbu-24-laser-guided-bomb-8dcd4a634e924a52ab05d4e90b3e3e82

로우폴리 GBU-24 모델을 가져왔습니다. 그런데 mtl 파일이 제대로 불러와지지 않네요.

적당히 색칠해줬습니다.

새로운 스크립트를 만들 차례입니다.
이전에 만들었던 미사일을 상속해서 만들겠습니다.

목표물을 향해 직접 유도되는 기능이 없다보니 안 쓰는 속성이 좀 많겠네요.

public class LaserGuidedBomb : Missile
{
    // Guidance
    public Vector3 guidedPosition;

    public void Launch(Vector3 guidedPosition, float launchSpeed, int layer)
    {
        minimapSprite.SetMinimapSpriteVisible(true);
        isDisabled = false;

        this.guidedPosition = guidedPosition;
        speed = launchSpeed;
        gameObject.layer = layer;
    }

    protected override void LookAtTarget()
    {
        Vector3 targetDir = guidedPosition - transform.position;
        float angle = Vector3.Angle(targetDir, transform.forward);

        Quaternion lookRotation = Quaternion.LookRotation(targetDir);
        rb.rotation = Quaternion.Slerp(rb.rotation, lookRotation, turningForce * Time.fixedDeltaTime);
    }

    void OnCollisionEnter(Collision other)
    {
        // This line must be collision check
        if(true)
        {
            isHit = true;
        }

        Explode();
        DisableMissile();
    }

    protected override void DisableMissile()
    {
        // Send Message to object that it is no more locked on
        if(isDisabled == false && isHit == false)
        {
            ShowMissedLabel();
        }
        
        isDisabled = true;
        transform.parent = parent;
        gameObject.SetActive(false);
    }

    protected override void AdjustValuesByDifficulty()
    {
        
    }

    void OnDisable()
    {    
        rb.velocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;
        CancelInvoke();
    }

    void FixedUpdate()
    {
        LookAtTarget();
        rb.velocity = transform.forward * speed;
    }
}

일단은 미사일처럼 직진하게끔 만들되,
플레이어가 가리키는 곳인 guidedPosition으로 조금씩 방향을 전환할 수 있는 기능의 뼈대를 구현합니다.

그리고 발사할 때 호출하는 함수도 Missile과 약간 다릅니다.
첫 번째 매개변수가 Transform 대신 Vector3로 들어가죠.

적당히 값을 설정하고,
(이럴 줄 알았으면 Weapon이라는 객체 하위에 Missile과 LaserGuidedBomb가 있어야 하는데)

프리팹으로 만들어준 다음,

WeaponController의 Special Weapon에 등록합니다.

일단 이대로 실행했을 때 무슨 일이 일어나는지 보겠습니다.

일단 레이저 유도 폭탄에 맞는 UI를 등록하지 않아서 이렇게 초록색으로 채워지고 있고,

발사는 되는 모습이군요.

근데 분명 자유 낙하 방식에 가까울텐데 속도가 비행기보다 빠릅니다.



여러가지 수정 작업

발사 속도

발사 속도부터 손을 좀 봅시다.

WeaponController.cs

missileScript.Launch(targetObject, aircraftController.Speed + 15, gameObject.layer);

무기 발사 스크립트에서는 어떤 걸 쏘든 간에 발사한 무기의 초기 속도를 기체 속도 + 15로 설정해주고 있었습니다. 이 15라는 오프셋을 무기마다 다르게 두겠습니다.


Missile.cs

public float additionalReleaseSpeed = 15;

발사 시에 사용할 오프셋을 Missile 클래스에 추가하고,


WeaponController.cs

missileScript.Launch(..., aircraftController.Speed + missileScript.additionalReleaseSpeed, ...);

이렇게 바꿉니다.

이전에 존재하지 않았던 public 또는 Serializable 변수를 선언할 때 초기값을 설정해주면
Inspector 창에는 자동으로 초기 설정값으로 설정되는 것을 볼 수 있습니다.

그리고 레이저 유도 폭탄은 이 값을 0으로 설정해보면,

미사일은 그대로 잘 날라가고,
레이저 유도 폭탄은... 너무 정직하네요.

자유 낙하식으로 보이도록 조금 조정을 해줘야 할 것 같습니다.

발사 시 추가되는 속도를 음수로 두겠습니다.

LaserGuidedBomb.cs

public float initialFallAmount;
public float lerpAmount;

void Update()
{
    initialFallAmount = Mathf.Lerp(initialFallAmount, 0, lerpAmount * Time.deltaTime);
    transform.Translate(Vector3.down * initialFallAmount * Time.deltaTime);
}

그리고 활성화되는 초기에 약간 아래쪽으로 이동하도록 코드를 작성합니다.

(가속 시)

(감속 시)

이제 좀 그럴듯하게 보이네요.


소리 수정

폭탄을 떨어뜨리는 소리 대신 미사일을 발사하는 소리가 나고 있습니다.
무기에 전용 발사 소리가 있는 경우, 기본 발사 소리 대신 전용 소리를 출력하도록 바꾸겠습니다.

Missile.cs

[Tooltip("If null, plays default missile launch audio clip (Can be found at SoundManager)")]
public AudioClip launchAudio;

미사일에 AudioClip 변수를 하나 추가합니다.
오디오를 지정하지 않으면 이전에 사용하던 미사일 발사 소리를 출력하게 됩니다.

"이건 항상 지정되어야 할 것 같은데 왜 null이지" 라고 생각할 사람이 있을까봐 툴팁도 추가했습니다.

이전에 만들어둔 미사일은 이 부분을 그냥 비워두고,

지금 만들고 있는 폭탄에는 전용 발사 소리를 넣어줍니다.


WeaponController.cs

void LaunchMissile(ref int weaponCnt, ref ObjectPool objectPool, ref WeaponSlot[] weaponSlots)
{
    ...
    
    if(missileScript.launchAudio != null)
    {
        missileAudioSource.PlayOneShot(missileScript.launchAudio);
    }
    else
    {
        missileAudioSource.PlayOneShot(SoundManager.Instance.GetMissileLaunchClip());
    }
}

Missile 클래스의 launchAudio가 존재하면 그 효과음을 출력하고, 그렇지 않으면 기존처럼 SoundManager에 등록된 미사일 발사 효과음을 출력하도록 만듭니다.

소리는 gif로는 들려드릴 수 없지만 아무튼 잘 작동합니다.


폭탄 투하 위치 조정

이 폭탄은 비행기 날개에서 투하되는 것이 아니라 가운데에서 투하되어야 합니다.

폭탄이 투하되는 위치를 빈 GameObject로 새로 만들고,

Missile.cs

public int maxActivePayload = 2;

미사일 클래스에 새로운 변수 maxActivePayload를 추가합니다.

동시에 활성화될 수 있는 무기 개수를 뜻합니다. 미사일은 2개, 폭탄은 1개입니다.

WeaponController.cs

[SerializeField]
Transform centerMissileTransform;

void LaunchMissile(ref int weaponCnt, ref ObjectPool objectPool, ref WeaponSlot[] weaponSlots)
{
    Vector3 missilePosition;
  
    ...

    // Get from Object Pool and Launch
    GameObject missile = objectPool.GetPooledObject();
    Missile missileScript = missile.GetComponent<Missile>();
    
    // Select Launch Position
    if(missileScript.maxActivePayload == 1)
    {
        missilePosition = centerMissileTransform.position;
    }
    else
    {
        if(weaponCnt % 2 == 1)
        {
            missilePosition = rightMissileTransform.position;
        }
        else
        {
            missilePosition = leftMissileTransform.position;
        }
    }

    missile.transform.position = missilePosition;

코드 순서를 살짝 변경합니다.

Missile 스크립트에 있는 maxActivePayload가 1이면 투하 위치를 centerMissileTransform.position으로 변경합니다. 2 이상인 경우 기존과 같이 좌/우 날개 밑에서 투하합니다.

그 다음에 LaserGuidedBombmaxActivePayload 값을 1로 둡니다.

이제 미사일과 폭탄의 발사/투하 지점이 달라졌습니다.


UI 교체: 보류

자... 여기를 건드려야 하는데...

지금 폭탄이 잘 굴러가게끔 코드를 수정하느라 바쁘므로 UI는 나중에 건드립시다.

아무것도 없는 투명한 스프라이트로 만들어주면,

최소한 초록색으로 채워진 사각형은 보이지 않습니다.

참고로 에이스 컴뱃 7에서 최대 1개만 준비 가능한 무기의 UI는 이렇게 오른쪽에만 나타납니다.



유도 기능 확인

아까 특정 위치로 유도되는 기능을 폭탄 스크립트에 추가하긴 했는데, 특정 위치를 지정해주는 기능은 아직 추가하지 않았습니다.

그 기능부터 구현하겠습니다.


LaserGuidanceController

public class LaserGuidanceController : MonoBehaviour
{
    [HideInInspector]
    public LaserGuidedBomb lgb;
    RaycastHit hit;
    int layerMask;
    public float raycastDistance = 1000;

    void Start()
    {
        layerMask = 1 << LayerMask.NameToLayer("Ground");
        layerMask += 1 << LayerMask.NameToLayer("Object");
    }

    // Update is called once per frame
    void Update()
    {
        if(lgb == null)
            return;
        
        Physics.Raycast(transform.position, transform.forward, out hit, raycastDistance, layerMask);

        if(hit.collider == null)
        {
            lgb.guidedPosition = transform.position + transform.forward * raycastDistance;
        }
        else
        {
            lgb.guidedPosition = hit.point;
        }
        
    }
}

레이저 유도에 사용하는 스크립트를 하나 작성했습니다.

Raycast를 사용하며, 비행기가 바라보는 방향으로 직선을 그려서 그 직선이 닿은 위치를 LaserGuidedBomb에 전달해줍니다.


WeaponController

public MonoBehaviour specialWeaponScript;

public void OnSwitchWeapon(InputAction.CallbackContext context)
{
    if(context.action.phase == InputActionPhase.Performed)
    {
        ...
        specialWeaponScript.enabled = useSpecialWeapon;
    }
}

WeaponController에서는 특수무기를 사용할 때 활성화할 컴포넌트를 들고 있고, 특수무기와 기본무기(미사일) 사이를 전환할 때마다 해당 컴포넌트를 활성화/비활성화할 수 있는 코드를 추가합니다.

이제 특수무기로 전환하면 specialWeaponScript가 활성화되고, 미사일로 전환하면 비활성화됩니다.

레이저 유도 폭탄을 떨어뜨리는 동안에만 유도 기능을 활성화하고, 기본 미사일로 전환하면 유도를 중지하도록 하는 역할을 합니다.


그리고 유도 위치를 확인하기 위해 디버그 코드를 추가하겠습니다.

LaserGuidedBomb

public GameObject guideDebugObject;

void Update()
{
    ...
    guideDebugObject.transform.position = guidedPosition;
}

guideDebugObject라는, 현재 폭탄이 유도되는 위치를 보여주는 오브젝트가 guidedPosition 위치에 놓여지는 기능을 추가했습니다.

유도 위치를 보여주는 오브젝트는 이렇게 생겼습니다.

이제 컴포넌트에 새로 만든 변수들을 모두 설정 또는 등록하고 실행해보겠습니다.

Raycast Distance는 비행기에서 얼마나 멀리 떨어진 거리까지 ray를 쏠 것인지 결정합니다
150 이내의 물체에 부딪히면 부딪힌 위치로, 그보다 멀리 떨어지면 150만큼 떨어진 위치를 지정합니다.


투하 후에는 빨간 공이 유도되는 위치에 놓여지고, 비행기를 움직이면 유도되는 위치도 같이 움직이며 폭탄이 그 위치를 향해 움직이고 있습니다.

기본 미사일로 전환할 경우 유도 위치가 고정되고,
다시 특수무기로 전환하면 다시 유도 위치가 바뀌는 것을 확인할 수 있습니다.



지형에서도 잘 되는지 확인하기

이번에는 지형을 생성하고 테스트해봅시다.


적당히 굴곡을 만들어보죠.

지형을 생성하면 자동으로 Terrain Collider가 있기 때문에 바로 실행해도 됩니다.

Raycast Distance를 조금 더 늘려주겠습니다.


제대로 지형 위에 유도 위치가 지정되고 있습니다.

기본 미사일로 전환해서 유도 위치를 고정시킨 후에도 알아서 폭탄이 유도되는 모습을 볼 수 있습니다.
그런데 가지도 않았는데 도중에 터지는 것 같네요.

Capsule Collider를 키워놓은 바람에 이 Collider가 지형에 닿아서 터진 것으로 보입니다.

크기에 맞게 줄여보죠.

이 정도면 된 것 같습니다.



영화와 비슷한 상황 재현하기

영화에 나온 지형을 대충 만들어볼까요?

분화구같은 지형 가운데에 목표물이 있죠.
이 목표물에 레이저를 조사해서 폭탄을 유도해야 합니다.

협곡을 따라 올라가서,

뒤집고,

폭탄을 투하합니다.

임무 성공입니다.



음, 방금 말은 취소하죠.



해야 할 일 추가요

이대로 끝내기에는 화면에 표시되는 UI가 너무 허접하다는 것이 문제입니다.

위에서 설명할 때 사용했던 에이스 컴뱃 7의 UI처럼, 초록색 원이나 기타 UI를 사용해서 현재 폭탄이 유도되고 있는 위치를 제대로 보여줄 필요가 있습니다.

허접한 빨간색 공은 유도 UI가 완성되면 치워버려야겠죠.


다음에 할 일로 레이저 유도 폭탄 UI를 만들어야겠습니다.

이렇게 예정에 없던 일감이 추가됩니다.

이 프로젝트의 작업 결과물은 Github에 업로드되고 있습니다.
https://github.com/lunetis/OperationMaverick

0개의 댓글