Ace Combat Zero: 유니티로 구현하기 #16 : 게임 오버 연출

Lunetis·2021년 6월 12일
0

Ace Combat Zero

목록 보기
17/27
post-thumbnail


이번 편은 전혀 진지하지 않은 분위기로 진행하겠습니다.



게임 오버 연출

게임에서 항상 이길 수는 없는 법입니다.

게임 도중에 죽는다거나, 임무 달성 조건을 충족하지 못했거나, 시간 제한이 다 되었거나 등등
여러 조건에 의해서 게임 오버가 되곤 합니다.

그리고 게임 오버 전용 연출이 나오죠.


이런 식으로 단순하게 팝업을 하나 띄워주는 게임도 있고, (스타크래프트 시리즈, 마인크래프트)


임팩트가 쩔어줘서 밈이 된 게임도 있고, (다크 소울 시리즈, GTA 시리즈)


개그나 깨알같은 요소를 넣기도 하죠. (메탈기어 솔리드 2: Sons of Liberty, My Summer Car)





그러면 에이스 컴뱃 시리즈에서의 게임 오버 연출을 봅시다.


https://youtu.be/rYvuHHAvMsE?t=1168

사망이 아닌, 임무 목표 달성에 실패했을 때의 연출입니다.

UI나 조작은 그대로 두되 가운데에 "MISSION FAILED" 라는 문구가 뜹니다.
그리고 임무에 실패했다는 미션별 고유 대사가 출력됩니다.


https://youtu.be/rYvuHHAvMsE?t=2991

사망 시에 연출은 두 가지로 나뉩니다.

먼저 피격을 받아서 비행기의 DMG가 100%가 되었을 때의 경우입니다.
비행기가 빙빙 돌아가다가 몇 초 후에 폭파됩니다.

그 때 카메라는 비행기의 주위를 느린 속도로 돌고, 비행기가 폭발하면 줌 아웃됩니다.

https://youtu.be/9lKVJ8000Cw?t=577

그 다음으로는 땅과 목표물 등 무언가에 부딪혔을 때입니다.
그 자리에서 즉시 폭발하고, 카메라가 줌 아웃됩니다.

(*에이스 컴뱃 7에서는 지면에 부딪혀도 충돌 시 각도가 크지 않으면 일정량의 데미지만 받고 다시 방향이 보정되는 옵션이 있습니다.)


어떤 식으로 게임 오버가 되었든 간에, "MISSION FAILED" 문구는 화면이 암전될 때까지 계속 유지됩니다.


일단 피격으로 인한 게임 오버 연출부터 만들어보겠습니다.



시네머신 (Cinemachine)

시네머신은 유니티 엔진에서 제공하는 카메라 연출 패키지입니다.

코드를 작성하지 않고도 줌인/줌아웃, 시점 변경, 흔들림 등의 효과를 쉽게 만들 수 있습니다.

만들어야 하는 연출을 확인해봅니다.

  • 비행기에 카메라 포커싱
  • 횡방향으로 조금씩 카메라가 돌아감
  • 폭발 전까지는 줌 인, 폭발 후에는 줌 아웃

이 연출을 시네머신과 약간의 코딩, 그리고 애니메이션으로 만들어보려고 합니다.


시네머신은 Package Manager에서 설치할 수 있습니다.

이 포스트를 작성하는 2021년 6월 11일 기준으로 최신 버전은 2.6.5 버전이네요.

우선 비행기를 비춰줄 카메라를 하나 만들어주고,

Cinemachine Brain 컴포넌트를 추가합니다.

그리고 상단의 Cinemachine 탭에서 Create Virtual Camera를 선택하면,

"CM vcam1"이라는 오브젝트가 생성되고,
그 안에는 CinemachineVirtualCamera 컴포넌트가 추가되어 있습니다.



카메라 설정

시네머신 컴포넌트를 최대한 이용해서 비행기 주위를 도는 카메라를 만들어봅시다.

Follow로 지정된 대상을 따라 카메라가 움직이고,
Look At으로 지정된 대상을 향해 카메라의 시선이 돌아가게 됩니다.

이 카메라는 비행기 주위를 돌아가면서 (Follow) 비행기를 비춰줘야 하므로 (Look At)
둘 다 플레이어의 비행기로 지정하면 됩니다.

BodyFollow로 지정한 대상을 어떤 식으로 따라갈 것인가를 설정합니다.

Orbital Transposer는 대상 주위를 빙빙 돌면서 따라가는 효과를 만듭니다.
그리고 내부의 값을 적절히 설정해줍니다.

Speed는 회전하는 속도, Input Axis ValueSpeed에 곱해질 값입니다.
(Input.GetAxis()에서 얻는 -1 ~ 1 사이의 값이 여기에 들어갑니다.)

Input Axis Name 값은 공백으로 둡니다. 이 값은 기존 유니티 Input 시스템을 사용해서 Axis의 값에 따라 카메라를 회전시키는데,
저희는 새로운 Input 시스템을 사용하기 때문에 여기에 뭐라도 써지는 순간 에러를 미친듯이 출력하게 됩니다.


AimLook At에 지정된 대상을 어떻게 바라볼 것인지 설정합니다.

Hard Look At으로 두면 정확히 중간에 대상이 놓이도록 바라보게 됩니다.
천천히 비행기에 초점을 이동시킨다거나 할 필요가 없기 때문에 이렇게 설정해줍니다.


Noise는 카메라의 떨림을 표현할 때 사용합니다.
폭발 시에 약간의 떨림이 필요하기 때문에, 아무 Noise Profile을 선택해줍니다.

Amplitude Gain은 카메라가 떨리는 진폭, (얼마나 크게 흔들리는가)
Frequency Gain은 카메라가 떨리는 주기 (얼마나 빠르게 흔들리는가)를 설정합니다.


DeathCam.cs

public class DeathCam : MonoBehaviour
{
    CinemachineOrbitalTransposer orbitalTransposer;

    // Start is called before the first frame update
    void Start()
    {
        CinemachineVirtualCamera vCam = GetComponent<CinemachineVirtualCamera>();
        orbitalTransposer = vCam?.GetCinemachineComponent<CinemachineOrbitalTransposer>();
        orbitalTransposer.m_XAxis.Value = Random.Range(0, 360);
    }

    void Update()
    {
        orbitalTransposer.m_XAxis.m_InputAxisValue = 1;
    }
}

데스캠을 제어하는 코드입니다.

죽을 때마다 카메라가 회전을 시작하는 위치가 다르기 때문에, 데스캠이 활성화되었을 때 무작위 각도로 카메라를 놓도록 Start() 함수를 작성합니다.

그리고 Input을 사용하지 않아서 InputAxisValue를 얻어올 수 없기 때문에, Update()에서 InputAxisValue의 값을 1로 고정시킵니다.



애니메이션 설정

이제 이 값들을 조정하는 연출을 애니메이션으로 만들 겁니다.

다시 만들어야 하는 연출을 확인해봅니다.

  • 비행기에 카메라 포커싱
  • 횡방향으로 조금씩 카메라가 돌아감
  • 폭발 전까지는 줌 인, 폭발 후에는 줌 아웃

일단 포커싱은 초기 설정으로 끝냈고, 카메라가 돌아가는 것을 만들어봅시다.

vCam을 선택한 상태에서 애니메이션 창을 열고 새 클립을 만들어줍니다.

빙글빙글 돌아가다 터지는 애니메이션은 "DeathCam_Delayed"라고 이름을 지었습니다.

작업에 들어가기 전에, 피격 후 몇 초 후에 2차 폭발이 일어나도록 설정했는지 확인해봅니다.
2.5초였네요.


2.5초까지는 빠른 속도로 돌고, 2.5초가 지나서 2차 폭발이 일어난 후에는 카메라가 줌 아웃하고 느린 속도로 돌아야 합니다.

시작 부분부터 2.5초 구간까지는 Max Speed 값을 50으로 놓고,

이후에는 10까지 떨어뜨립시다.

카메라 줌 아웃은 Follow Offset.z 값을 조정해서 구현합니다.
초기값은 -10이었다가, 대략 2.8초가 지난 시점에서 -30까지 멀어지게 합시다.

폭발 시에는 카메라가 떨려야 합니다.
Amplitude Gain 값을 조절해서 카메라의 떨림을 추가해보죠.

애니메이션을 모두 만들었으면 vCam에 애니메이션 컴포넌트를 추가해서 아까 만든 애니메이션을 등록합니다.

Play Automatically를 체크했으니 활성화된 즉시 실행될 거에요.

일단 Inspector View에서 비활성화해줍시다.



게임 오버 시 오브젝트 활성화/비활성화

플레이어의 비행기가 파괴되어서 게임 오버가 된 경우는 게임 오버 UI와 대사 UI 빼고 모두 숨김 처리합니다. 물론 조작도 불가능하죠.

임무 실패로 인한 게임 오버일 때는 게임 UI가 그대로 유지됩니다.

GameManager.cs

public void GameOver(bool isDead)
{
    UIController.SetLabel(AlertUIController.LabelEnum.MissionFailed);

    if(isDead)
    {
        foreach(GameObject obj in disableOnGameOverObjects)
        {
            obj.SetActive(false);
        }
        foreach(GameObject obj in enableOnGameOverObjects)
        {
            obj.SetActive(true);
        }

        aircraftController.DisableControl();
    }
}

게임 오버 시에 Mission Failed 라벨이 뜨는 건 고정으로 두고,

게임 오버 시에 활성화해야 할 오브젝트와 비활성화해야 할 오브젝트를 미리 지정해주고,
플레이어가 죽어서 게임 오버가 되면 각 오브젝트들을 활성화/비활성화하는 코드를 작성합니다.

UI 중에서는 보존해야 하는 대사 UI와 라벨 UI를 그대로 두고,
(그 외에 약간의 조정도 필요합니다.)

비활성화하는 UI들을 따로 등록해줍니다.
아까 만들고 비활성화했던 카메라 속성들은 활성화시킬 오브젝트 목록에 등록합니다.


AircraftController.cs

public void DisableControl()
{
    GetComponent<PlayerInput>().enabled = false;
}

비행기가 파괴될 때 2차 폭발 이전에 날아가는 동안 조종을 할 수 없도록 만들어야 하는데,
그냥 PlayerInput을 비활성화해서 구현합니다.


PlayerAircraft.cs

protected override void DestroyObject() 
{
    CommonDestroyFunction();
    GameManager.Instance.GameOver(true);
    Invoke("DelayedDestroy", destroyDelay);
}

public void SelfDestruct()
{
    DestroyObject();
}

이제 저 게임오버를 호출하는 코드를 작성합니다.

PlayerAircraft.DestroyObject()는 기체가 파괴되었을 때 호출됩니다.
여기서 GameManager.GameOver()를 호출하게 만들면 되겠죠.


그리고 게임 오버를 테스트하기 위한 자폭 기능을 추가했습니다.

스페이스 바를 누르면 터뜨리게 해놓았죠.

그 외에 재시작하는 기능도 추가하고요.

이벤트를 등록한 다음에 실행해봅시다.


카메라 위치는 잘 잡힌 것 같습니다.

약간의 연출만 더 손봐주면 될 것 같네요.



비행기 돌리기

조종 불능 상태가 되었다는 것을 표현하는 것인지는 모르겠지만,
2차 폭발로 사라지기 전에는 비행기가 돌아가는 연출이 있습니다.

돌아가는 속도도 랜덤이고요.

그 돌아가는 연출을 추가해보겠습니다.


TargetObject.cs

protected bool isDestroyed;

protected void CommonDestroyFunction()
{
    isDestroyed = true;

    ...
}

TargetObject에는 bool isDestroyed라는 변수가 있습니다.
이 값이 원래는 피격 후에 체력이 0 이하로 떨어졌을 때 true로 바꿔줬는데,
상속된 오브젝트에서도 공통으로 호출하는 CommonDestroyFunction() 내부로 옮겨줍시다.


PlayerAircraft.cs

[SerializeField]
float rotateSpeedOnDestroy = 600;

// Start is called before the first frame update
protected override void Start()
{
    base.Start();
    uiController = GameManager.UIController;
    
    uiController.SetDamage(0);
    uiController.SetScoreText(0);

    rotateSpeedOnDestroy *= Random.Range(0.5f, 1.0f);
}

// Update is called once per frame
void Update()
{
    if(isDestroyed == true)
    {
        transform.Rotate(0, 0, rotateSpeedOnDestroy * Time.deltaTime);
    }
}

PlayerAircraft에서는 돌아가는 속도인 rotateSpeedOnDestroy를 추가하고,
Start()에서 속도를 랜덤으로 약간 조정한 다음,
Update()에서는 isDestroyedtrue일 때 비행기를 돌려주도록 합시다.

이제 알았는데 뒤에 비행운이 회오리치는 게 보이니까 좀 웃기네요.

[SerializeField]
List<TrailRenderer> contrails;

protected override void DestroyObject() 
{
    CommonDestroyFunction();
    GameManager.Instance.GameOver(true);
    Invoke("DelayedDestroy", destroyDelay);

    foreach(TrailRenderer trailRenderer in contrails)
    {
        trailRenderer.emitting = false;
    }
}

비활성화시키는 코드를 추가하겠습니다.


그리고 더 이상 자폭시키지 않고, 날아오는 미사일에 일부러 맞아보겠습니다.

지금까지 상대의 공격 시스템을 비활성화시켰지만 이제 활성화시키고,

단 한 방에 하늘나라로 갈 수 있도록 HP를 1로 설정한 후 실행합니다.
(미사일 공격력은 10으로 설정된 상태입니다.)



아주 멋지네요.
(?)


충돌로 인한 게임 오버

충돌 시에는 그 자리에서 바로 터져버립니다.
아까의 연출에서 2차 폭발 이전의 연출을 제거하면 될 것 같습니다.

연출 애니메이션의 2.5초 이후 부분을 가져와서,

새로운 애니메이션을 하나 만들어줍니다.
이름은 DeathCam_Instant라고 붙여줬습니다.

그리고 VCam의 애니메이션을 새 애니메이션으로 교체해줍니다.

피격으로 인한 애니메이션은 언제 사용하냐고요?
조금 있다 예외 처리할 때 다시 가져올 겁니다.


PlayerAircraft.cs

// Ground/Object Collision
private void OnCollisionEnter(Collision other)
{
    if(other.gameObject.layer == LayerMask.NameToLayer("Ground") || 
        other.gameObject.GetComponent<TargetObject>() != null)
    {
        DestroyObjectImmediate();
    }
}

void DestroyObjectImmediate() 
{
    CommonDestroyFunction();
    GameManager.Instance.GameOver(true);
    DelayedDestroy();
}

void DelayedDestroy()
{
    GameObject obj = Instantiate(destroyEffect, transform.position, Quaternion.identity);
    Destroy(aircraftModel);

    Collider[] colliders = GetComponents<Collider>();
    foreach(Collider col in colliders)
    {
        col.enabled = false;
    }
    GetComponent<AircraftController>().enabled = false;
}

비행기의 충돌 체크 함수를 만들어줍니다.

부딪힌 오브젝트가 땅이거나, TargetObject 속성을 가지고 있으면 즉시 파괴합니다.

지금까지 플레이어에게 Rigidbody 속성이 없는 상태로 작업을 해왔는데, 이제 추가해줍시다.

그냥 Rigidbody만 추가하고 놔두면 미사일과 충돌할 때 진행 방향과 회전이 뒤틀릴 수 있기 때문에,
모든 축에 대해 FreezePosition과 Freeze Rotation을 체크했습니다.

현재 비행기의 충돌체입니다.
미사일이 이 충돌체에 닿으면 데미지를 입고, 땅이나 오브젝트가 닿으면 자폭합니다.


  1. 지면과 충돌하기

  1. 오브젝트과 충돌하기

두 경우 모두 연출이 잘 나오고 있습니다.



임무 실패로 인한 게임 오버 상황

시간 제한 내에 어떤 임무를 달성하지 못하거나, 무언가를 놓쳐버렸거나, 보호에 실패했거나 하는 등,
플레이어가 사망하지 않은 상태의 게임 오버도 고려해야 합니다.

아직 게임 스테이지 세부 내용을 구현하지 않은 만큼, 시간 제한으로 인한 패배를 만들어보겠습니다.

UIController.cs

void SetTime()
{
    remainTime -= Time.deltaTime;
    
    if(remainTime <= 0)
    {
        GameManager.Instance.GameOver(false);
        remainTime = 0;
        return;
    }

    ...
}

시간은 GameManager에서 설정하고, UIController에서 카운트하고 있습니다.

남은 시간이 0이 되면 GameManager.GameOver(bool isDead)를 호출합니다.
매개변수로 bool형 변수를 전달하는데, 사망으로 인한 게임 오버가 아니므로 false를 보내줍니다.

GameManager의 게임 속성에서 남은 시간을 3초로 설정해보겠습니다.


시간이 0초가 되면 미션이 실패했다는 표시가 뜹니다.


게임 오버 시에 구현해야 할 것이 있는데,
미니맵과 화면 상의 타겟이 모두 없어져야 한다는 겁니다.

그리고 이미 타겟으로 설정되었던 오브젝트도 타게팅이 풀려야 하죠.


TargetController.cs

public void RemoveAllTargetUI()
{
    if(targetUIs.Count == 0) return;

    foreach(TargetUI targetUI in targetUIs)
    {
        Destroy(targetUI.gameObject);
    }
    targetUIs.Clear();
}

GameManager.cs

public void GameOver(bool isDead)
{
    UIController.SetLabel(AlertUIController.LabelEnum.MissionFailed);
    
    foreach(TargetObject obj in objects)
    {
        obj.DeleteMinimapSprite();
    }
    targetController.RemoveAllTargetUI();
    objects.Clear();
    weaponController.ChangeTarget();
    
    ...
}

게임 오버 라벨을 띄우는 것과 동시에,

TargetController가 들고 있는 모든 타겟 UI를 제거하고,
GameManager가 들고 있는 모든 오브젝트의 미니맵 스프라이트를 제거한 후 objects 리스트를 비웁니다.

마지막으로 WeaponController의 타겟을 재설정하는데, objects를 모두 날려버렸기 때문에 타겟이 없는 상태로 바뀌게 됩니다.


타겟을 모두 없애버린 모습입니다.



게임 오버가 겹칠 때의 예외 처리

이미 한 가지 게임 오버 상황이 발생했을 때, 다른 게임 오버 상황이 겹쳐질 수 있습니다.

  1. 시간 초과 후 미션 실패 -> 지면에 충돌
  2. 피격으로 제어 불능 상태 돌입 -> 2차 폭발 이전에 지면에 충돌

1번은 "MISSION FAILED"를 보고 해탈한 플레이어가 땅에다 들이박는 경우,
2번은 피격당했는데 하필 땅을 향하고 있는 경우에 발생하게 됩니다.

이 두 가지 경우도 연출이 자연스럽게 이뤄져야 합니다.



DeathCam.cs

[SerializeField]
AnimationClip deathCamInstantAnimation;
[SerializeField]
AnimationClip deathCamDelayedAnimation;
Animation anim;

public void PlayAnimation(bool isInstantAnimation)
{
    anim.clip = isInstantAnimation ? deathCamInstantAnimation : deathCamDelayedAnimation;
    anim.Play();
}

즉시 폭발할 때의 애니메이션과 2차 폭발을 기다리는 애니메이션을 가지고 있다가,

게임 오버 시 상황에 따라서 둘 중 하나의 애니메이션을 재생하는 함수를 만들어줍니다.


GameManager.cs

[SerializeField]
DeathCam deathCam;

public void GameOver(bool isDead, bool isInstantDeath = false)
{
    ...
    if(isDead)
    {
        ...
        deathCam.PlayAnimation(isInstantDeath);
    }
}

GameOver()에 '즉시 파괴되었는가?' 라는 뜻의 매개변수 isInstantDeath를 추가합니다.

이 값을 고스란히 DeathCam.PlayAnimation()에 전달해줄 겁니다.


PlayerAircraft.cs

protected override void DestroyObject() 
{
    GameManager.Instance.GameOver(true, false);
    ...
}

void DestroyObjectImmediate() 
{
    GameManager.Instance.GameOver(true, true);
    ...
}

PlayerAircraft에서는 사망 시에 호출할 함수가 두 가지로 나뉘어져 있습니다.

DestroyObject()는 피격으로 인한 사망이며, GameOver()의 두 번째 매개변수에 false를 넘겨줍니다.
DestroyObjectImmediate()는 충돌로 인한 사망이며, GameOver()의 두 번째 매개변수에 true를 넘겨줍니다.

이 값은 GameManager를 타고 DeathCam에 전달됩니다.

각 변수들을 모두 할당한 다음 죽으러 가봅시다.

참고: Animation.Animations 목록에 사용하려는 애니메이션이 모두 들어가있어야 합니다.

일단 세 가지 평범한 경우는 모두 확인했습니다.

그러면 앞서 언급했던 게임 오버가 겹치는 경우도 테스트해봅시다.




  1. 시간 초과 후 미션 실패 -> 지면에 충돌 : 확인

  1. 피격으로 제어 불능 상태 돌입 -> 2차 폭발 이전에 지면에 충돌

다시 자폭 기능을 활성화해서 테스트해봤는데, 몇 가지 수정해야 할 부분들을 발견할 수 있었습니다.

  • 이미 지면에 충돌했으나 2차 폭발이 그대로 발생
  • 지면 속으로 카메라가 들어감


2차 폭발 방지

PlayerAircraft.cs

void DestroyObjectImmediate() 
{
    CancelInvoke();
    ...
}

지면에 부딪혔는데도 2차 폭발이 발생하는 건 CancelInvoke()로 막아줄 수 있습니다.


카메라가 땅 밑으로 들어가지 못하게 막기

이럴 때 사용할 수 있을 뻔한 컴포넌트가 있습니다.

CinemachineVirtualCamera 컴포넌트의 하단에 Add Extension 라는 버튼이 있고,
여기서 몇 가지 컴포넌트를 추가할 수 있습니다.

이 중에서 CinemachineCollider는 카메라에 충돌체를 부여해서 지면을 뚫고 들어가는 것을 막는 방식을 사용하지만...

벽에 부딪힐 때 카메라 이동이 별로 자연스럽지 못합니다.
수동으로 카메라 높이를 조절해줘야 할 것 같습니다.



DeathCam.cs

[SerializeField]
float minimumOffset = 5;

void AdjustHeight()
{
    RaycastHit hit;
    Vector3 raycastPos = transform.position;
    raycastPos.y += 100;
    Physics.Raycast(raycastPos, Vector3.down, out hit);

    float minimumHeight = transform.position.y - (hit.distance - 100) + minimumOffset;
    float calculatedOffset = minimumHeight - orbitalTransposer.FollowTarget.position.y;

    if(orbitalTransposer.m_FollowOffset.y < calculatedOffset) orbitalTransposer.m_FollowOffset.y = calculatedOffset;
}

void Update()
{
    orbitalTransposer.m_XAxis.m_InputAxisValue = 1;
    AdjustHeight();
}

지면의 높이를 알아내서, 카메라가 무조건 지면의 높이보다 minimumOffset만큼 위에 있도록 설정해주는 기능을 추가합니다.

지면 + minimumOffset 값인 minimumHeight를 계산하고,
이 값을 시네머신의 오프셋 값으로 변환해서, 현재 설정된 시네머신의 오프셋보다 크면 그 값으로 초기화해줍니다.

이제 높이 문제는 사라진 줄 알았는데...

카메라 밑으로 비행기가 지나가니 그 비행기의 높이를 기준으로 위치를 재조정해버리네요.

이런 일은 거의 없을 줄 알았는데요.

void AdjustHeight()
{
    RaycastHit hit;
    int layerMask = 1 << LayerMask.NameToLayer("Ground");
    
    Vector3 raycastPos = transform.position;
    raycastPos.y += 100;
    Physics.Raycast(raycastPos, Vector3.down, out hit, Mathf.Infinity, layerMask);

    ...
}

Ground만 감지할 수 있도록 레이어 마스크를 추가합니다.
이렇게 하면 비행기가 지나가도 그 비행기는 Raycast에 걸리지 않습니다.


이제 정말로 문제는 없을 것 같습니다.



테스트 도중 확인된 문제

비행기가 상당히 빠르게 움직여서 그런지, 지면 충돌 테스트 도중 가끔씩 지면을 뚫고 들어가는 일이 발생합니다.

이 문제는 이전에 기총 구현할 때 다룬 적이 있습니다.


현재 비행기는 Transform.translate()를 이용해서 매 프레임마다 순간이동시키고 있는데,
이 방식은 특정 프레임 사이에 Collider를 지나가버리는 경우 충돌을 인식하지 못하는 문제가 있습니다.

제가 해보기로는 10번 중에 한 번 꼴로 발생하는데... 이 정도면 낮은 확률이라고 보기는 어렵겠네요.
움직임 방식을 Rigidbody로 바꿔야 할 것 같습니다.


사실 바꾸는 것 자체는 어렵지 않습니다.


AircraftController.cs

void MoveAircraft()
{
    ...
    // transform.Rotate(rotateValue * Time.fixedDeltaTime);
    rb.MoveRotation(rb.rotation * Quaternion.Euler(rotateValue * Time.fixedDeltaTime));
    
    // transform.Translate(new Vector3(0, 0, speed * Time.deltaTime));
    rb.velocity = transform.forward * speed;
}

void FixedUpdate()
{
    MoveAircraft();
}

움직이는 부분을 이렇게 바꿔주면 되거든요.

하지만 이렇게 바꾸는 순간 내가 발사하는 미사일, 총알, 그리고 주변에 돌아다니는 비행기와 지형이 모두 떨리는 것처럼 보일 수 있습니다.

그 때는 Rigidbody.Interpolate가 서로 다른 값으로 적용되어 있지는 않은지 살펴봐야 합니다.

그리고 rigidbody 기반 움직임의 경우 비행하다가 미사일에 맞으면 속도나 방향 등이 변할 수 있는데,
Freeze Rotation은 체크되어 있는지, Mass 값은 비행기답게 설정되어 있는지 등등 여러 가지를 확인해봐야 합니다.



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

0개의 댓글