탑건: 매버릭 미션 개발하기 #4 : 지대공 미사일 구현

Lunetis·2022년 8월 1일
0

탑건: 매버릭

목록 보기
5/14
post-thumbnail

영화에서 협곡 내부를 저공비행하는 작전이 만들어진 가장 큰 이유는 지대공 미사일 때문입니다.

이 미사일들이 기지에 굉장히 많이 배치되어 있기 때문에, 미사일이 전투기를 요격할 수 있는 고도보다 낮게 내려가서 습격을 하는 작전이 만들어지게 되었습니다.

이 지대공미사일은 소련에서 만들어진 S-125라고 합니다.

고증에 맞춰서 이 지대공 미사일의 3D 모델을 구하려고 했는데,

다 좋은데 텍스쳐가 없네요.


https://3dwarehouse.sketchup.com/model/u34101f72-fb6d-4b90-97a0-cf19fed51c03/S-125-SA-3-GOA-launcher

이건 어떨까요.

SketchUp 파일은 유니티에서도 자체적으로 지원합니다.
그런데 2021과 2022 모델은 제대로 불러와지지 않네요.

2019와 2020은 됩니다.

영화와 완전히 동일한 몸체는 아니지만 마음에 드네요.

'좋아요'를 주고 이제 프로그래밍하러 갑시다.



미사일 프리팹 생성

이 지대공 미사일 포대가 가지고 있는 미사일을 똑 떼어내서 미사일 프리팹을 하나 만들겠습니다.

기존에 적 전투기가 날리던 미사일과의 크기 비교입니다.

실제 길이를 비교해보면, 전투기에 탑재되는 AIM-9 사이드와인더 미사일의 길이는 2.85m,
S-125 미사일의 길이는 6.09m입니다. 2배보다 조금 더 길죠.


지대공 미사일 모델링을 임포트한 후 크기 수정을 하지 않고 바로 비교해봤는데, 딱히 수정할 필요가 없겠네요. 오히려 좋습니다.


기존에 있는 미사일 프리팹을 참고해서 이펙트와 컴포넌트를 추가합니다.
일단 기본 상태로 둬보고, 너무 쉽거나 어렵다 싶으면 값을 조정하겠습니다.


다 만들었으면 오브젝트를 Project 탭으로 끌어다놓아서 프리팹으로 만들어줍니다.



지대공 미사일 발사시키기

적 전투기에 붙이는 컴포넌트 중 EnemyWeaponController라는 게 있었습니다.

누가 봐도 미사일을 발사하게 생긴 스크립트인데,
이걸 아무런 수정 없이 지대공 미사일에 붙이면 무슨 일이 벌어지는지 확인해보겠습니다.

값을 조금 수정하고, 실행하겠습니다.


뭔가... 뭔가 뜨는데요.

여기네요.

이 씬에 enemyMissileObjectPool이 없어서 에러가 나고 있는 상황이었습니다.


그나저나 지대공 미사일 전용 오브젝트 풀도 없는데요.

전용 오브젝트 풀도 만들어주고, 기본 미사일 오브젝트 풀 대신 따로 사용하려는 오브젝트 풀이 있다면 그걸 등록해서 사용할 수 있도록 바꿔주겠습니다.

EnemyWeaponController

[SerializeField]
ObjectPool customMissileObjectPool = null;

void LaunchMissile()
{
    ...
    
    // Get from Object Pool and Launch
    GameObject missile;
    if(customMissileObjectPool != null)
    {
        missile = customMissileObjectPool.GetPooledObject();
    }
    else
    {
        missile = GameManager.Instance.enemyMissileObjectPool.GetPooledObject();
    }
    
    ...
}

customMissileObjectPool이라는 오브젝트 풀을 가지는 변수를 추가합니다. 오브젝트 풀을 따로 등록했다면 그 풀에서 오브젝트를 꺼내고, 그렇지 않으면 GameManager가 가지고 있는 기본 미사일 오브젝트 풀에서 꺼냅니다.

지대공 미사일 전용 오브젝트 풀을 만들고,

지대공 미사일의 EnemyWeaponController에 등록합니다.

그리고 기본 미사일 오브젝트 풀도 언젠가는 써먹을테니 만들어서 추가해둡시다.

이제 실행을 해보죠.

분명 발사는 하고 있는데,

락온이 되지 않고 직선으로만 날아가고 있네요. 타겟이 설정되지 않은 것 같습니다.

에러도 나고 있고요.

원인을 찾아보니 AircraftAI 컴포넌트가 없어서 에러가 나고 있었습니다.
이 컴포넌트를 항상 가지고 있을 적 전투기에 붙여질 컴포넌트로 상정하고 만들어지다보니 이 사단이 나버렸네요.

void LaunchMissile()
{
    float initialSpeed = (aircraftAI != null) ? aircraftAI.Speed + 15 : 15;

    ...

    missileScript.Launch(targetObject, initialSpeed, gameObject.layer);
}

이렇게 수정하고 다시 실행해봅시다.

여전히 직선으로 날아가네요.

플레이어 입장에서 미사일 경고는 뜨긴 하는데 즉시 꺼지고요.
더 깊숙한 문제가 있나봅니다.



미사일 타겟 감지 문제 수정

미사일이 더 이상 특정 대상을 따라가지 못하는 경우의 코드는 angle > boresightAngle일 때입니다.
여기서 로그를 찍어서 두 값이 어떻게 됐길래 발사하자마자 경고가 꺼지는지 보았습니다.

boresightAngle이 1이네요. 그 어디서도 1로 설정해주는 곳이 없을텐데...

대입하는 곳을 검색해보았습니다.

여기가 유력한 용의자같습니다.

AdjustValuesByDifficulty()는 난이도에 따라서 적 미사일의 성능이 조정되는 코드입니다.
그런데 지금 상황에서는 아마 난이도 값이 설정되지 않은 상태일 겁니다.

왜냐하면 난이도는 메인 화면에서 게임 시작하기 전에 선택하게 되어 있거든요.

GetFloatFromDifficultyXML()을 들여다보니 여기서 defaultValue가 1이었네요.
GetDifficultyData에서 데이터를 얻어오지 못해서 1로 설정되고 있었습니다.

EnemyMissile

protected override void AdjustValuesByDifficulty()
{
    smartTrackingRate = MissionData.GetFloatFromDifficultyXML("enemyMissileTrackingRate", smartTrackingRate);
    boresightAngle = MissionData.GetFloatFromDifficultyXML("enemyMissileBoresightAngle", boresightAngle);
    turningForce = MissionData.GetFloatFromDifficultyXML("enemyMissileTurningForce", turningForce);

    // Debug.Log("Adjuested missile values");
}

GetFloatFromDifficultyXML()에서 두 번째 매개변수는 기본값을 나타냅니다.
데이터를 얻어오는 데에 실패하면 그냥 기존에 설정한 값을 그대로 쓰도록 코드를 설정해야겠네요.

로그도 제대로 출력되고 있고,

미사일도 저를 향해서 날아오고 있습니다.

근데 끝내주게 못 맞추네요. 지금 아무 조작도 하고 있지 않은데도 못 맞추고 있습니다.



미사일 성능 문제...?

추적 성능이 높은 미사일인 QAAM의 값을 본따서 해볼까요.
위치 예측 정도인 SmartTrackingRate 값은 기본 미사일은 0.3이지만 QAAM은 0.9로 세팅해놓았습니다.

여전히 피격 판정이 나오지 않고 있습니다.


이쯤 되면 또 다른 생각이 들죠.
충돌 처리가 되지 않고 있는 것 같습니다.



미사일이 안 맞잖아

역시 이 쪽이 문제였습니다.

Default로 된 오브젝트는 충돌판정을 하나도 처리하지 않도록 만들어놓았었네요.

레이어를 바꿔줍시다.

제 기억으로는 미사일을 발사할 때 그 미사일을 발사하는 오브젝트의 레이어 값을 그대로 복사하도록 만들어놓았을 것이기 때문에, 지대공 미사일은 Default 레이어가 되어서 아무런 충돌 처리를 하지 않고 있었을 겁니다.


이제는 그냥 발사하자마자 알아서 터지고 있네요.
미사일이 생성될 때, 그 미사일의 충돌 범위가 땅과 맞닿아서 벌어지고 있는 상황으로 보입니다.

아예 땅에 닿지 않을 정도로 미사일 발사 위치를 조정해야겠습니다.

상식적으로 이제는 돼야 하는데.

마침내 미사일에 맞는 모습을 볼 수 있게 되었습니다.



지대공 미사일 전용 스크립트

영화에서의 지대공 미사일은 일정 고도 이상인 비행기에 대해서 미사일을 발사합니다.
그리고 타겟으로 지정한 비행기의 위치에 따라 회전하는 모습도 보이고요.


그 기능을 구현해보겠습니다.

SAM

[RequireComponent(typeof(EnemyWeaponController))]
public class SAM : MonoBehaviour
{
    Transform targetTransform;

    EnemyWeaponController enemyWeaponController;

    [SerializeField]
    bool enableByTargetAltitude = true;
    
    [SerializeField]
    Transform missileLauncherBody;
    [SerializeField]
    float rotateLerpAmount = 1.0f;

    void Awake()
    {
        enemyWeaponController = GetComponent<EnemyWeaponController>();
    }

    // Start is called before the first frame update
    void Start()
    {
        targetTransform = enemyWeaponController.TargetObject?.transform;
    }

    // Update is called once per frame
    void Update()
    {
        if(targetTransform == null)
        {
            targetTransform = enemyWeaponController.TargetObject?.transform;
        }

        // disable
        if(enableByTargetAltitude == true && targetTransform.position.y < transform.position.y)
        {
            enemyWeaponController.enabled = false;
        }
        else
        {
            enemyWeaponController.enabled = true;
            Vector3 lookVector = targetTransform.position - transform.position;
            float lookAngle = Mathf.Atan2(lookVector.x, lookVector.z) * Mathf.Rad2Deg;
            missileLauncherBody.rotation = Quaternion.Slerp(missileLauncherBody.rotation, Quaternion.Euler(0, lookAngle, 0), rotateLerpAmount * Time.deltaTime);
        }
    }
}

EnemyWeaponController 컴포넌트를 가지고 있다는 가정 하에 스크립트를 작성하므로, RequireComponent로 해당 컴포넌트 보유를 강제합니다.

EnemyWeaponController.TargetTransform을 공유하며, enableByTargetAltitude가 true일 경우 이 오브젝트보다 낮은 고도에 타겟이 있다면 미사일 발사를 하지 않습니다.
타겟을 감지하는 경우 타겟을 바라보는 방향으로 몸체(body)가 회전합니다. 이 때 천천히 돌아가도록 Quaternion.Slerp를 사용합니다.


EnemyWeaponController에는 다른 스크립트에서 targetObject를 얻어올 수 있도록 코드를 추가합니다.

그리고 지지대와 따로 회전하는 발사대가 있고, 그 발사대를 기준으로 탐색을 해야 하는 경우를 대비한 코드도 추가합니다.

public TargetObject TargetObject
{
    get { return targetObject; }
}

[SerializeField]
Transform rotatableBody;

float GetAngleBetweenTransform(Transform otherTransform)
{
    Vector3 direction = (rotatableBody != null) ? rotatableBody.forward : transform.forward;
    Vector3 diff = otherTransform.position - transform.position;
    return Vector3.Angle(diff, direction);
}

여기서는 미사일 포대가 잘 안 보이는 것 같지만,

분명 제대로 돌아가고 있는 모습을 볼 수 있습니다.

미사일 발사대보다 비행기의 고도가 높아지면 작동을 시작하고,

비행기의 고도가 낮아지면 작동을 멈춥니다.



발사 대수 제한 (보류)

이 부분은 플레이해봤을 때 너무 어려워서 제한이 필요하겠다 싶으면 적용하겠습니다.

실제로 적용한다면 모델링처럼 4대까지만 발사되게끔 만들고, 발사할 때마다 발사대에 있는 미사일 모델링이 하나씩 사라져야겠죠.



실제 환경 테스트

한 번 영화처럼 세팅을 해보겠습니다.

지대공 미사일이 엄청나게 많이 배치되어 있고, 적기도 따라오는 환경을 테스트해보죠.

아직 영화에 나오는 5세대 전투기 Su-57을 가져오지는 않았기 때문에 F-15C를 3대 배치하고,

플레이어는 이곳에,

지대공 미사일은 6대를 배치해보겠습니다.

약간의 자비를 위해, 지대공 미사일의 발사 딜레이는 6초로 설정하겠습니다.


자, 게임을 시작하죠.


협곡 너머에 3대의 적기가 보이고요...
















지옥이 따로 없네요.

영화에서 강조하던 지대공 미사일의 무서움을 몸소 체험할 수 있게 되었습니다.

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

0개의 댓글