UDN
Search public documentation:

PhysicalAnimationKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 홈 > 피직스 > 피지컬 애니메이션
UE3 홈 > 애니메이션 > 피지컬 애니메이션
UE3 홈 > 애니메이터 > 피지컬 애니메이션
UE3 홈 > 스켈레탈 메시 > 피지컬 애니메이션

피지컬 애니메이션


문서 변경내역: James Golding 작성. James Tan 수정. 홍성진 번역.

개요


어색하지 않은 애니메이션을 위해서라면 어태치먼트(attachment, 붙은 것)에 피직스 시뮬레이션을 돌려줘야 하는 상황이 종종 있습니다. 예를 들어 죄수에 매달려 이리저리 끌리는 체인이나 의식불명 캐릭터의 래그돌 정도입니다. 캐릭터가 움직이며 다양한 애니메이션을 블렌딩함에 따라, 물체의 위치를 미리 결정하는 것이 불가능합니다. 언리얼 엔진 3 에서는 애니메이터와 프로그래머가 스켈레탈 메시의 일부나 전체 본을 물리적으로 시뮬레이트되는 리짓 바디 어태치먼트로 정의할 수 있도록 하여 이러한 문제를 해결합니다.

팔에 붙은 체인은 피직스로 돌리고, 바디 나머지 부분은 애니메이션 블렌딩과 인버스 키네마틱스(역운동학)로 돌리는 캐릭터입니다.

SkeletalMeshWithPhysicsSimulatedChains.jpg

전체 바디 시뮬레이션에 피직스를 사용하는 캐릭터입니다.

SkeletalMeshRagdoll.jpg

파이프라인


피지컬 애니메이션 시스템은 언리얼 엔진 3 애니메이션 파이프라인의 일부입니다. 첫째, 일반 애니메이션은 애님트리를 통해 블렌딩을 다룰 때 처리됩니다. 둘째, 애님트리를 통해 인버스 키네마틱스 콘트롤을 적용합니다. 마지막으로 피직스 애셋을 통해 스켈레톤에 피직스를 적용합니다. 그리고서 피직스 서브 시스템은 나머지 피직스를 처리하고, 그래픽스 렌더 시스템은 씬을 렌더합니다.

스켈레톤에 대한 피직스는 본이 픽스(고정)되었는가 아닌가에 따라 계산됩니다. 픽스된 본은 피직스로 시뮬레이트하지 않으며, 애니메이션 블렌딩과 인버스 키네마틱 시스템으로 돌립니다. 언픽스된 본은 피직스로 시뮬레이트합니다.

작업방식


보통 프로그래머와 애니메이터가 함께 작업하여 필요한 피지컬 애니메이션 콘텐츠를 만들어 내게 됩니다. 애니메이터는 피직스 애셋을 셋업하며, 여기에는 바디 로킹(lock)과 시스템을 시뮬레이트하여 전부 잘 묶여있는지 확인하는 작업도 포함됩니다. 그 후 프로그래머는 게임플레이에 따라 피지컬 애니메이션을 켜고 끄는 것을 담당합니다.

피직스 콘트롤


피직스 종류

피직스에는 두 가지 종류가 있습니다:
  1. 키네마틱 ("고정")
    • 가라 그런 곳으로 정확히 갑니다.
    • 콜리전을 무시합니다.
    • 맞은 오브젝트를 매우 세게 밀어냅니다.
  2. 다이내믹 ("미고정")
    • 콜리전에 현실적으로 반응합니다.
    • 콘트롤하는 데 포스나 컨스트레인트를 사용해야 합니다.

피직스 오브젝트

세 가지 피직스 오브젝트가 있습니다:
  1. 포스 (Forces, 지속력)
    • (중력이나 바람처럼) 지속적인 힘을 적용하는 데 사용합니다.
  2. 임펄스 (Impulse, 순간력)
    • (폭발이나 총격처럼) 일회성 힘을 적용하는 데 사용합니다.
  3. 컨스트레인트 (Constraint, 제약)
    • (조인트나 스프링처럼) 연속적인 힘을 적용하는 데 사용합니다.

스켈레탈 메시에 부분적인 피지컬 시뮬레이션


요즘 게임에 흔히 사용되는 효과는, 바디 일부에 피직스 시뮬레이션을 적용하면서도 나머지 캐릭터는 애니메이션 블렌딩과 인버스 키네마틱스로 돌립니다. 이렇게 셋업하기 가장 쉬운 방법은, 캐릭터의 스켈레톤 메시에 모든 본은 물론 물리적으로 시뮬레이트되는 부분까지 포함시키는 것입니다. 아래는 체인 캐릭터에 사용된 스켈레톤 입니다.

SkeletalMeshBones.jpg

물리적으로 시뮬레이트되게 하려는 본을 포함해서, PhAT 툴을 사용하여 스켈레탈 메시에 대한 피직스 애셋을 만들어 주기도 해야 한다는 뜻입니다. 그 부분을 제외한 모든 것을 (트리 뷰에서 우클릭한 다음 여기 아래 바디 전부 고정해제...여기 아래 바디 전부 고정... 을 선택하여) '고정시켜' 이러한 피직스 부분이 어때 보이는지를 PhAT 에서 미리볼 수 있습니다. 본을 선택한 다음 Fixed 프로퍼티를 체크/해제시켜 개별 리짓 바디의 고정 상태를 설정해도 됩니다.

SetFixedBodies.jpg

툴에서 시뮬레이션 도중, Preview Anim Set 옵션에 애님세트를 할당한 다음 툴바에 있는 드롭다운 메뉴에서 (run cycle 같은) 애니메이션을 선택하면 되풀이(loop)시킬 수 있습니다. 애니메이션을 되풀이하려면 재생 버튼을 누르고, 멈추려면 중지 버튼을 누릅니다. 시뮬레이션을 중지할 필요 없이 바로 애니메이션을 변경할 수 있습니다. Blend On Poke 프로퍼티를 체크하면 왼쪽 마우스 버튼으로 스켈레탈 메시를 찔러볼 수 있으며, 부분/전체 피지컬 시뮬레이션을 블렌딩시킬 수 있습니다.

PlayAnimationWithSimulatingUnfixedBodies.jpg

PhysicsAsset 을 게임내 캐릭터에 할당하고 bHasPhysicsAssetInstance 을 참으로 설정했는지 확인하십시오.

SetupSkeletalMeshActor.jpg

이제 게임을 실행할 때 콘솔에 nxvis collision 라 치면 캐릭터의 피직스 오브젝트에 하얀 모양을 볼 수 있을 것입니다. 아무튼 지금 당장은 물리적으로 시뮬레이트되고 있지는 않은 것입니다.

ShowPhysicsCollision.jpg

고려해야 할 것은, 이 본에 대한 피직스를 캐릭터의 애니메이션에 어떻게 블렌딩해 넣을지 입니다. 피직스 엔진의 결과물과 애니메이션 시스템의 결과물을 얼마만큼 블렌딩할지 선택하는 데 있어, 보통 스켈레탈 메시에 있는 PhysicsWeight (피직스 웨이트) 파라미터를 사용합니다. 이 경우 딱 이 본들만 PhysicsWeight (피직스 웨이트)가 0 일 때도 항상 피직스 엔진 결과물을 사용하도록 하려는 것입니다. 그렇게 하려면, 피직스 애셋 안의 각 리짓 바디에 bAlwaysFullAnimWeight (항상 풀 애님 웨이트)라는 파라미터가 있습니다. 이러한 본들에 대해 이 옵션을 참으로 설정합니다. 스켈레탈 메시 컴포넌트에 있는 bEnableFullAnimWeightBodies (풀 애님 웨이트 바디 켜기) 옵션도 참으로 설정해 줘야 합니다.

SettingAlwaysFullAnimWeight.jpg

액터의 피직스가 PHYS_RigidBody 일 때, 리짓 바디의 디폴트는 피직스 애셋에 정의된 고정 상태를 따릅니다. 그러나 일반적으로 뛰어다니는 캐릭터의 경우 보통 PHYS_Walking 이나 PHYS_Falling 등으로 되어 있는데, 이 경우 피직스 애셋에 정의된 것과는 상관없이 모든 리짓 바디의 디폴트는 고정 상태가 됩니다. 방금의 '이러한 리짓 바디'에 피직스 시뮬레이션을 돌리려면, 코드에서 명시적으로 '고정해제'(unfix)해 줘야 합니다. PhysicsAssetInstant (피직스 애셋 인스턴스) 안에 SetFullAnimWeightBonesFixed (풀 애님 웨이트 본 고정으로 설정)이라는 함수가 있습니다. SkeletalMeshActor (스켈레탈 메시 액터)를 보시면 PostBeginPlay 안에서 플래깅된 리짓 바디를 고정해제하는 예제를 확인하실 수 있습니다.

SkeletalMeshActor.uc
simulated event PostBeginPlay()
{
  // 현재 메시를 리플리케이션 용으로 잡습니다.
  if (Role == ROLE_Authority && SkeletalMeshComponent != None)
  {
    ReplicatedMesh = SkeletalMeshComponent.SkeletalMesh;
  }

  // 'full anim weight' 로 플래깅된 바디를 고정해제합니다.
  if (SkeletalMeshComponent != None &&
      //SkeletalMeshComponent.bEnableFullAnimWeightBodies &&
      SkeletalMeshComponent.PhysicsAssetInstance != None)
  {
    SkeletalMeshComponent.PhysicsAssetInstance.SetFullAnimWeightBonesFixed(FALSE, SkeletalMeshComponent);
  }

  if(bHidden)
  {
    SkeletalMeshComponent.SetClothFrozen(TRUE);
  }
}

위의 모든 단계를 밟고나면, 지정한 부분이 게임에서 물리적으로 움직이는 것을 확인할 수 있을 것입니다. 문제가 있다면 nxvis collision 옵션을 사용해 보십시오. 하얀 모양이 보이지 않는다면 피직스 애셋이나 그 할당 방식에 문제가 있거나, 어떠한 이유로 PhysicsAssetInstance (피직스 애셋 인스턴스)가 없을 수도 있습니다. 하얀 모양이 보이는데 리짓 바디가 시뮬레이트되지 않는다면, 이 리짓 바디를 고정해제하는 데 무언가 문제가 있는 것입니다. 하얀 리짓 바디는 올바르게 움직이는데 본은 그렇지 않다면, 최종 결과물 블렌딩에 무언가 문제가 있는 것입니다.

언리얼 어태치먼트 메서드를 사용하여 다른 스켈레탈 메시에 붙인 스켈레탈 메시가 피직스 시뮬레이션을 했을 때는 조금 까다로울 수 있습니다. 베이스 스켈레탈 메시 컴포넌트도 어태치먼트와 마찬가지로 피직스를 사용할 때가 문제인데, 예를 들어 캐릭터 스켈레탈 메시에 포니테일 스켈레탈 메시를 붙인 상황에서 캐릭터가 넘어지는 것과 같은 상황입니다. 이렇게 되면 보통 포니테일과 머리 사이에 한 프레임 랙이 발생하는데, 그 이유는 포니테일 루트의 고정 바디 위치를 어태치된 위치로 업데이트하기 위해서, 바디가 시뮬레이트된 이후 언리얼 코드를 실행해야 하기 때문입니다. 이 문제를 피해가려면 언리얼 어태치먼트 메서드가 아닌, 코드로 포니테일과 머리 사이에 피직스 조인트를 만드는 것입니다. 물론 약간 복잡하긴 합니다. UT 호버보드 함수 SetHoverboardRiderPhysics 에서, 피직스 라이더의 발을 피직스 호버보드에 조인트시키는 예제를 확인하실 수 있습니다. 아니면 어태치먼트를 다시 업데이트하기 위해 bForceUpdateAttachmentsInTick (틱에서 어태치먼트 강제 업데이트) 옵션을 설정해도 되지만, 비용이 비쌀 수 있습니다.

마지막으로 조절할 것은 콜리전이 되겠습니다. 리짓 바디가 피직스 바디 애니메이트되는 나머지 캐릭터와 충돌하게 하려면 먼저 BlockRigidBody (리짓 바디 블록)을 참으로 설정해 줘야 합니다. 그런 다음 RBChannel (강체 채널)이 어떻게 설정되었든 RBCollideWithChannels (강체가 충돌하는 채널) 콘테이너에 있는 것도 참으로 설정해 줘야 합니다. 캐릭터의 경우 보통은 RBCC_Pawn 입니다. 현재 리짓 바디 부분이 오너의 팔다리(limb)와만 충돌하게 하고, 다른 캐릭터의 팔다리와는 충돌하지 않게 만드는 것이 쉽지 않습니다. 이 예제에서는 캐릭터의 팔에 붙은 체인 중 하나가 그 아래 있는 상자와 충돌하고 있습니다.

RigidBodyCollision.jpg

스켈레탈 메시에 풀 피지컬 시뮬레이션


  • bHasPhysicsAssetInstance (피직스 애셋 인스턴스 있음)이 반드시 참이어야 합니다. 그러면 피직스 서브 시스템이 이 스켈레탈 메시에 대한 피직스 애셋을 확실히 인스턴싱하도록 합니다. 스켈레탈 메시의 본별 라인 체크에는 필수적이지 않습니다.
  • 스켈레탈 메시를 소유하는 액터는 피직스 모드를 PHYS_RigidBody 로 설정해 주지 않아도 됩니다. 그저 액터의 위치와 로테이션이 루트 바디의 위치와 로테이션에 일치하게끔 해 주는 것일 뿐입니다.
  • RB_BodyInstance (강체_바디 인스턴스)에 정의된 SetFixed(false) 를 호출하여 다이내믹으로 만들려는 리짓 바디 인스턴스를 고정해제합니다.
  • 그런 바디에 피직스가 블렌드 인 되는지 확인합니다.
    • 스켈레탈 메시 컴포넌트의 PhysicsWeight (피직스 웨이트)를 0 이상의 값으로 설정합니다.
    • 스켈레탈 메시 컴포넌트의 bEnableFullAnimWeightBodies (풀 애님 웨이트 바디 켜기)를 참으로 설정합니다.
    • RB_BodySetup 의 bAlwaysFullAnimWeight (항상 풀 애님 웨이트)를 참으로 설정합니다.

YourPawn.uc
simulated function bool Died(Controller Killer, class<DamageType> DamageType, vector HitLocation)
{
  if (Super.Died(Killer, DamageType, HitLocation))
  {
    Mesh.MinDistFactorForKinematicUpdate = 0.f;
    Mesh.SetRBChannel(RBCC_Pawn);
    Mesh.SetRBCollidesWithChannel(RBCC_Default, true);
    Mesh.SetRBCollidesWithChannel(RBCC_Pawn, false);
    Mesh.SetRBCollidesWithChannel(RBCC_Vehicle, false);
    Mesh.SetRBCollidesWithChannel(RBCC_Untitled3, false);
    Mesh.SetRBCollidesWithChannel(RBCC_BlockingVolume, true);
    Mesh.ForceSkelUpdate();
    Mesh.SetTickGroup(TG_PostAsyncWork);
    CollisionComponent = Mesh;
    CylinderComponent.SetActorCollision(false, false);
    Mesh.SetActorCollision(true, false);
    Mesh.SetTraceBlocking(true, true);
    SetPhysics(PHYS_RigidBody);
    Mesh.PhysicsWeight = 1.0;

    if (Mesh.bNotUpdatingKinematicDueToDistance)
    {
      Mesh.UpdateRBBonesFromSpaceBases(true, true);
    }

    Mesh.PhysicsAssetInstance.SetAllBodiesFixed(false);
    Mesh.bUpdateKinematicBonesFromAnimation = false;
    Mesh.SetRBLinearVelocity(Velocity, false);
    Mesh.ScriptRigidBodyCollisionThreshold = MaxFallSpeed;
    Mesh.SetNotifyRigidBodyCollision(true);
    Mesh.WakeRigidBody();

    return true;
  }

  return false;
}

래그돌 캐릭터를 다시 일으켜 세울 수도 있게 하려면, 프로세스를 역순으로 하면 됩니다.

언리얼 에디터


  • 스켈레탈 메시 액터와 그 자식은 피직스를 사용하여 스켈레탈 메시에 있는 약간의 본을 시뮬레이트할 수 있습니다.
  • KAsset 과 그 자식을 레벨에 놓아 스켈레탈 메시에 풀 피직스 시뮬레이션을 할 수 있습니다.

유용한 콘솔 명령


  • nxvis collision - 피직스 시뮬레이션이 사용하는 콜리전 바디를 표시합니다. show collision 은 언리얼 피직스 서브 시스템에 사용된 콜리전 헐만을 표시해 주므로, 이 옵션을 사용하는 것이 낫습니다.

NxvisCollisionConsoleCommand.jpg

  • show bones - 애니메이트되는 스켈레탈 메시를 렌더하는 데 사용되는 본 위치와 로테이션을 표시합니다.

ShowBonesConsoleCommand.jpg

  • show prephysbones - 애니메이션 블렌딩과 인버스 키네마틱스가 적용된 이후, 그러나 피직스는 적용하기 전 본의 위치와 로테이션을 표시합니다. 체인의 본이 어떻게 뻗어 있는지를 주목해 봐야 하는데, 그 용도로 만든 미리정의된 애니메이션이 없기 때문입니다. 그러나 피직스가 적용된 이후라면 체인 위치가 올바르게 됩니다.

ShowPrePhysicsBonesConsoleCommand.jpg

예제


아래 모든 예제에 대해 범용 액터를 만들었습니다. 각 예제에 대해 언리얼스크립트 로직 흐름을 나열합니다.

언리얼스크립트

HitReactionPawn.uc
class HitReactionPawn extends SkeletalMeshCinematicActor;

// 데쓰 애니메이션
var(HitReaction) Name DeathAnimName;
// 적중 반응을 시뮬레이트할 때 언픽스할 본 이름입니다.
var(HitReaction) array<Name> UnfixedBodyNames;
// 적중 반응을 시뮬레이트할 때 스프링을 켤 본 이름입니다.
var(HitReaction) array<Name> EnabledSpringBodyNames;
// 적중 반응을 시뮬레이트할 때 사용할 선형 본 스프링 세기입니다.
var(HitReaction) float LinearBoneSpringStrength;
// 적중 반응을 시뮬레이트할 때 사용할 각형 본 스프링 세기입니다.
var(HitReaction) float AngularBoneSpringStrength;
// 적용할 힘의 반경입니다.
var(HitReaction) float ForceRadius;
// 힘 증폭입니다.
var(HitReaction) float ForceAmplification;
// 적용할 수 있는 힘의 최대 양입니다.
var(HitReaction) float MaximumForceThatCanBeApplied;
// 적중 반응에 대한 블렌드 인 시간입니다.
var(HitReaction) float PhysicsBlendInTime;
// 적중 반응에 대한 피직스 시뮬레이션 시간입니다.
var(HitReaction) float PhysicsTime;
// 적중 반응에 대한 블렌드 아웃 시간입니다.
var(HitReaction) float PhysicsBlendOutTime;
// 풀 바디 래그 돌입니다.
var(HitReaction) bool FullBodyRagdoll;

var Name PreviousAnimName;

event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
  local AnimNodeSequence AnimNodeSequence;

  Super.TakeDamage(DamageAmount, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);

  if (SkeletalMeshComponent == None || SkeletalMeshComponent.PhysicsAssetInstance == None)
  {
    return;
  }

  if (IsTimerActive(NameOf(SimulatingPhysicsBlendIn)) || IsTimerActive(NameOf(SimulatingPhysics)) || IsTimerActive(NameOf(SimulatedPhysicsBlendOut)))
  {
    return;
  }

  if (FullBodyRagdoll)
  {
    if (DeathAnimName != '')
    {
      AnimNodeSequence = AnimNodeSequence(SkeletalMeshComponent.Animations);

      if (AnimNodeSequence != None)
      {
        PreviousAnimName = AnimNodeSequence.AnimSeqName;
        AnimNodeSequence.SetAnim(DeathAnimName);
        AnimNodeSequence.PlayAnim();
        AnimNodeSequence.bCauseActorAnimEnd = true;
        return;
      }
    }
    else
    {
      TurnOnRagdoll(Normal(Momentum) * FMin(DamageAmount * ForceAmplification, MaximumForceThatCanBeApplied));
    }
  }
  else
  {
    if (DeathAnimName != '')
    {
      AnimNodeSequence = AnimNodeSequence(SkeletalMeshComponent.Animations);

      if (AnimNodeSequence != None)
      {
        PreviousAnimName = AnimNodeSequence.AnimSeqName;
        AnimNodeSequence.SetAnim(DeathAnimName);
        AnimNodeSequence.PlayAnim();
        AnimNodeSequence.bCauseActorAnimEnd = true;
        return;
      }
    }
    else
    {
      TurnOnRagdoll(Vect(0.f, 0.f, 0.f));
      // 임펄스 적용
      SkeletalMeshComponent.AddRadialImpulse(HitLocation - (Normal(Momentum) * 16.f), ForceRadius, FMin(DamageAmount * ForceAmplification, MaximumForceThatCanBeApplied), RIF_Linear, true);
      // 리짓 바디 깨우기
      SkeletalMeshComponent.WakeRigidBody();
    }
  }

  BlendInPhysics();
}

event OnAnimEnd(AnimNodeSequence AnimNodeSequence, float PlayedTime, float ExcessTime)
{
  TurnOnRagdoll(Vect(0.f, 0.f, 0.f));
  BlendInPhysics();
  AnimNodeSequence.bCauseActorAnimEnd = false;
}

function TurnOnRagdoll(Vector RBLinearVelocity)
{
  // 스켈레톤을 강제 업데이트합니다.
  SkeletalMeshComponent.ForceSkelUpdate();

  // 물리적 적중 반응에서 역할을 담당할 필요가 없는 바디를 고정합니다.
  if (UnfixedBodyNames.Length > 0)
  {
    SkeletalMeshComponent.PhysicsAssetInstance.SetNamedBodiesFixed(false, UnfixedBodyNames, SkeletalMeshComponent,, true);
  }
  else
  {
    SkeletalMeshComponent.PhysicsAssetInstance.SetAllBodiesFixed(false);
  }

  // 물리적 적중 반응에 필요한 바디에 스프링 켜기
  if (EnabledSpringBodyNames.Length > 0)
  {
    SkeletalMeshComponent.PhysicsAssetInstance.SetNamedRBBoneSprings(true, EnabledSpringBodyNames, LinearBoneSpringStrength, AngularBoneSpringStrength, SkeletalMeshComponent);
  }

  SkeletalMeshComponent.bUpdateKinematicBonesFromAnimation = false;
  SkeletalMeshComponent.SetRBLinearVelocity(RBLinearVelocity, true);
  SkeletalMeshComponent.WakeRigidBody();
}

function BlendInPhysics()
{
  // 블렌드 인 할 피직스에 타이머 설정
  if (PhysicsBlendInTime > 0.f)
  {
    SetTimer(PhysicsBlendInTime, false, NameOf(SimulatingPhysicsBlendIn));
  }
  else
  {
    SkeletalMeshComponent.PhysicsWeight = 1.f;
    SimulatingPhysicsBlendIn();
  }
}


function SimulatingPhysicsBlendIn()
{
  if (PhysicsTime == 0.f)
  {
    SimulatingPhysics();
  }
  else
  {
    // 머무를 피직스에 대한 타이머를 설정합니다.
    SetTimer(PhysicsTime, false, NameOf(SimulatingPhysics));
  }
}

function SimulatingPhysics()
{
  local AnimNodeSequence AnimNodeSequence;

  // 블렌드 아웃 할 피직스에 대한 타이머를 설정합니다.
  SetTimer(PhysicsBlendOutTime, false, NameOf(SimulatedPhysicsBlendOut));

  if (PreviousAnimName != '')
  {
    AnimNodeSequence = AnimNodeSequence(SkeletalMeshComponent.Animations);

    if (AnimNodeSequence != None)
    {
      AnimNodeSequence.SetAnim(PreviousAnimName);
      AnimNodeSequence.PlayAnim(true);
    }
  }
}

function SimulatedPhysicsBlendOut()
{
  // 피직스 웨이트를 0 으로 설정합니다.
  SkeletalMeshComponent.PhysicsWeight = 0.f;
  SkeletalMeshComponent.ForceSkelUpdate();

  if (FullBodyRagdoll)
  {
    SkeletalMeshComponent.PhysicsAssetInstance.SetAllBodiesFixed(true);
    SkeletalMeshComponent.bUpdateKinematicBonesFromAnimation = true;
  }
  else
  {
    SkeletalMeshComponent.bUpdateKinematicBonesFromAnimation = true;

    if (UnfixedBodyNames.Length > 0)
    {
      SkeletalMeshComponent.PhysicsAssetInstance.SetNamedBodiesFixed(true, UnfixedBodyNames, SkeletalMeshComponent,, true);
    }
    else
    {
      SkeletalMeshComponent.PhysicsAssetInstance.SetAllBodiesFixed(true);
    }

    // 물리적 적중 반응에 필요한 바디에 스프링을 끕니다.
    if (EnabledSpringBodyNames.Length > 0)
    {
      SkeletalMeshComponent.PhysicsAssetInstance.SetNamedRBBoneSprings(false, EnabledSpringBodyNames, 0.f, 0.f, SkeletalMeshComponent);
    }
  }

  // 리짓 바디를 재웁니다.
  SkeletalMeshComponent.PutRigidBodyToSleep();
}

function Tick(float DeltaTime)
{
  Super.Tick(DeltaTime);

  if (IsTimerActive(NameOf(SimulatingPhysicsBlendIn)))
  {
    // 피직스 블렌드 인 하기
    SkeletalMeshComponent.PhysicsWeight = GetTimerCount(NameOf(SimulatingPhysicsBlendIn)) / GetTimerRate(NameOf(SimulatingPhysicsBlendIn));
  }
  else if (IsTimerActive(NameOf(SimulatedPhysicsBlendOut)))
  {
    // 피직스 블렌드 아웃 하기
    SkeletalMeshComponent.PhysicsWeight = 1.f - (GetTimerCount(NameOf(SimulatedPhysicsBlendOut)) / GetTimerRate(NameOf(SimulatedPhysicsBlendOut)));
  }
}

defaultproperties
{
  Begin Object Name=SkeletalMeshComponent0
    bHasPhysicsAssetInstance=true
    bUpdateJointsFromAnimation=true
  End Object

  ForceRadius=64.f
}

물리적으로 시뮬레이트되는 적중 반응

게임에 현실감을 더하기 위해 캐릭터는 그에 적용된 힘에 가급적 현실적으로 반응해야 할 것입니다. 월드에서 달려드는 오브젝트로 인한 것이거나, 총에 맞았을 때일 수도 있습니다. 캐릭터가 총에 맞았을 때 벌어질 수 있는 가능성은 두 가지 있는데, 게임에 현실성을 얼마나 강조할 것인가에 따라 달라집니다.

아케이드 시뮬레이션

아케이드 시뮬레이션만은 이동과 조준이 적중 반응에 의해 방해받지 않도록 해야할 때 사용됩니다. 즉 플레이어는 항상 최대 이동 및 조준 능력을 유지하는 것입니다. 플레이어를 항상 제어할 수 있어야 하는 언리얼 토너먼트 3 와 같은 게임에서 중요할 것입니다.

이 시뮬레이션은 바디의 윗부분에 대한 물리적 변화만 적용하고, 총과 머리는 올바른 방향으로 유지하기 위해 손과 머리에는 경직된 각 스프링을 켭니다.

이러한 기능을 내기 위한 HitReactionPawn (적중 반응 폰) 프로퍼티는:

ArcadeHitReaction.jpg

언리얼스크립트 로직 흐름은:

  • 플레이어가 HitReactionPawn (적중 반응 폰)을 쏠 때 TakeDamage (대미지 받음)이 호출됩니다.
  • 유효한 스켈레탈 메시 컴포넌트와 피지컬 애셋 인스턴스가 있는지, 그리고 스크립트가 현재 피직스 시뮬레이션 중간에 있지 않은지 검사합니다.
  • FullBodyRagdoll (풀 바디 래그돌)은 거짓이고 DeathAnimName (데쓰 애님 이름)은 비어 있으므로, 피직스를 켜고 래디얼 임펄스(반경에 충격)를 적용하여 무기 적중을 시뮬레이트하고 리짓 바디를 깨웁니다.
    • UnfixedBodyNames 에 정의된 바디를 고정해제합니다. 비어 있으면 모든 바디를 고정해제합니다.
    • EnabledSpringBodyNames (켜진 스프링 바디 이름)에 정의된 본 스프링을 켜고, LinearBoneSpringStrengthAngularBoneSpringStrength (선형/각형 본 스프링 세기)를 사용하여 그 세기를 설정합니다.
    • 더이상 애니메이션을 통해 본을 업데이트할 필요가 없으니, bUpdateKinematicBonesFromAnimation (애니메이션에서 키네마틱 본 업데이트)를 거짓으로 설정합니다.
  • 블렌드 인 타이머가 설정됩니다.
  • 블렌드 인 타이머가 호출되면 피직스 스테이 타이머를 호출합니다.
  • 피직스 스테이 타이머가 호출되면 블렌드 아웃 타이머를 호출합니다.
  • 블렌드 아웃 이후 위의 프로세스를 반대로 합니다.
  • 블렌드 인 타이머나 블렌드 아웃 타이머가 돌아가는 중이면, 타이머로 경과된 시간 비율을 알아내어 PhysicsWeight (피직스 웨이트)를 조절하는 식으로 틱이 애니메이션과 피직스를 블렌드합니다.

목과 쇄골은 플레이어의 샷에 영향을 덜받게 마련이지만, 나머지 상체 부분은 그에 따라 반응합니다.

ArcadeHitReactionShot.jpg

현실적인 시뮬레이션

현실감이 중요한 게임에서라면 플레이어가 적에게 발사하여 조준을 방해한다든가, 적의 다리를 쏘아 접근하거나 도망가지 못하게 할 수 있다면 좋을 것입니다.

이러한 기능을 내기 위한 HitReactionPawn (적중 반응 폰) 프로퍼티는:

RealisticHitReaction.jpg

언리얼스크립트 로직 흐름은 위와 같습니다만, 본 스프링과 그 세기가 설정되었습니다. 그래서 본이 너무 경직되지 않게 하면서도 애니메이션에 가급적 가깝게 유지하는 데 도움이 됩니다.

전체 바디가 플레이어의 샷에 영향을 받습니다. 부가적인 로직을 더해 영향받은 폰을 바라는 대로 걸려 넘어지게 할 수 있습니다 (다른 애니메이션으로 블렌드 인 한 다음 피직스를 블렌드 아웃 시키고, 약간의 시간이 지난 후 다시 피직스를 블렌드 인 시켜 낙하를 시뮬레이트합니다).

RealismHitReactionShot.jpg

래그돌로 블렌드 인 되는 데쓰 애니메이션

죽자마자 바로 래그돌로 만들어버리기 보다는, 짧은 애니메이션을 먼저 재생해 주는 것이 좋을 수도 있습니다. 애니메이션이 끝나면 래그돌을 블렌드 인 하는 것이지요.

이러한 기능을 내기 위한 HitReactionPawn (적중 반응 폰) 프로퍼티는 이렇습니다:

AnimatedRagdoll.jpg

언리얼스크립트 로직 흐름은:

  • 플레이어가 HitReactionPawn (적중 반응 폰)을 쏠 때 TakeDamage (대미지 받음)이 호출됩니다.
  • 유효한 스켈레탈 메시 컴포넌트, 피지컬 애셋 인스턴스가 있는지, 스크립트가 현재 피직스 시뮬레이션 한가운데 있지 않는지를 검사합니다.
  • FullBodyRagdoll (풀 바디 래그돌)은 거짓이고 DeathAnimName (데쓰 애님 이름)은 비어 있지 않습니다.
  • 데쓰 애니메이션을 재생하고, 애니메이션 종료시 그것을 재생하는 애님 노드 시퀸스가 OnAnimEnd (애님 종료시)를 호출하는지 확인합니다.
  • OnAnimEnd (애님 종료시)가 호출될 때 피직스를 켭니다. 최적의 결과를 위해 블렌드 인 시간을 설정하지 말아야 하며, 설정했다간 발연기가 작렬할 수 있습니다!
    • UnfixedBodyNames (고정해제된 바디 이름)에 정의된 바디를 고정해제합니다. 비어 있으면 모든 바디를 고정해제합니다.
    • EnabledSpringBodyNames (켜진 스프링 바디 이름)에 정의된 본 스프링을 켜고, LinearBoneSpringStrengthAngularBoneSpringStrength (선/각형 본 스프링 세기)를 사용하여 그 세기를 설정합니다.
    • 더이상 애니메이션을 통해 본을 업데이트할 필요가 없으니, bUpdateKinematicBonesFromAnimation (애니메이션에서 키네마틱 본 업데이트)를 거짓으로 설정합니다.
  • 블렌드 인 타이머가 설정됩니다.
  • 블렌드 인 타미어가 호출되면, 피직스 스테이 타이머를 호출합니다.
  • 피직스 스테이 타이머가 호출되면, 블렌드 아웃 타이머를 호출합니다.
  • 블렌딩 아웃 이후, 위의 프로세스를 반대로 합니다.
  • 블렌드 인 타미어나 블렌드 아웃 타이머가 돌고 있는 경우, 타이머로 경과된 시간 비율을 알아내어 PhysicsWeight (피직스 웨이트)를 조절하는 식으로 틱이 애니메이션과 피직스를 블렌딩합니다.

이렇게 하여 애니메이터가 초기 낙하를 다루는 애니메이트된 시퀸스를 만들어서, 바디 역시도 낙하에 반응하는 느낌을 낼 수 있도록 하는 것입니다. 이 경우 언리얼 토너먼트 3 의 무기 Stinger 로 바디를 계속해서 맞출 경우 재생하는 특수 애니메이션이 있습니다. 래그돌 시뮬레이션은 애니메이션 종료시 시작되는 것입니다.

AnimatedRagdollShot.jpg

내려받기


  • 이 문서에서 사용된 콘텐츠 내려받기. PhysicalAnimationBMap.udk 로드 전 UDN 패키지를 컴파일 해 주시기 바랍니다.