이것은 문서의 이전 버전입니다!


개요

GoW2 의 큰 특징중 하나는 '잔혹한 표현' 이다.

GoW 아이콘중 하나인 체인소우 (일명 랜서) 에는 톱이 달려있는데 이를 이용한 액션이 아주 잔혹하다. 사지가 절단되고 피가 피부를 타고 흐르는 등의 연출은 이 게임의 백미라고 할 수 있다.

여기서는 이것들을 엔진에서 어떤 식으로 표현했는지 살펴본다.

사지 절단

5226340582_d1f49f878d.jpg 5225797424_3610fd357a.jpg

와 같은 액션을 하기 위해 와 같이 쪼개놓는다.

에서 쪼개진 조각들은 각각의 Bone 에 스키닝 되어 있고, chainsaw 이벤트 발생 시 적절하게 해당부위를 쪼갠다.

변수

  • 직접적인 변수
    // GearPawn.uc
    var bool           bUsingNewSoftWeightedGoreWhichHasStretchiesFixed;
    var SkeletalMesh   GoreSkeletalMesh;              // Gore 연출 시 사용할 SkeletalMesh
    var PhysicsAsset   GorePhysicsAsset;              // Gore 연출 시 사용할 PhysicsAsset
    var Array<Name>    GoreBreakableJoints;           // Gore 연출 시 쪼갤 뼈대 이름을 담은 배열
    var Array<Name>    HostageHealthBuckets;          // Hostage 상태일 때, 각 파츠가 떨어지는 Gore 효과를 위해 Health 를 가지는 뼈대 이름을 담은 배열
  • 간접적인 변수
    // DamageType.uc
    var(RigidBody)             float       kDeathUpKick;         // 죽었을 경우 위로 솟아오르는 힘
     
    // GearDamageType.uc
    var config const           float      DistFromHitLocToGib;   // HitLocation 으로부터 HitDirection 으로 이 길이만큼 실린더를 만들고, 그 사이에 있는 constraints 를 모두 깨버린다. 이 값의 default 는 -1 로 body 를 모두 깨부숴버린다.
    var const config           bool       bAllowHeadShotGib;     // 이 무기로 머리를 맞출 때, 뽀개지는 (Gore) 연출을 할까?

함수

// SkeletalMeshComponent.uc
BreakConstraint
 
// GearPawn.uc
PlayDying
        PlayDeath
ReplicatedPlayTakeHitEffects
PlayDeath
        GoreExplosion
GoreExplosion
        ChainsawGore   // 데미지 타입이 GDT_Chainsaw 일 경우 호출
        BreakConstraint 호출
BreakDependantBones
        BreakConstraint 호출
UpdateHostageHealth
        BreakConstraint 호출
BreakConstraint
        SkeletalMeshComponent.BreakConstraint
        BreakDependantBones 호출
 
// GearPawn_COGGear.uc, GearPawn_COGDom.uc, GearPawn_COGMarcus.uc, GearPawn_Locust_Base.uc
ChainSawGore
        BreakConstraint
 
// GearPC.uc
GoreTest
        GearPawn.BreakConstraint
GoreTestJointList
        GearPawn.BreakConstraint

코드 플로우

로커스트 그런트를 예로 설명

초기화

// GearPawn_LocustDroneBase.uc
defaultproperties
{
    ...
    bUsingNewSoftWeightedGoreWhichHasStretchiesFixed=TRUE
    GoreSkeletalMesh=SkeletalMesh'Locust_Grunt.Mesh.Locust_Grunt_Gore_SoftWeights'
    GorePhysicsAsset=PhysicsAsset'Locust_Grunt.PhysicsAsset.Locust_Grunt_CamSkel_Physics'
    GoreBreakableJointsTest=("b_MF_Face","b_MF_Head","b_MF_Spine_03","b_MF_Armor_Crotch","b_MF_Armor_Sho_R","b_MF_UpperArm_R","b_MF_Hand_R","b_MF_Calf_R","b_MF_Calf_L")
    GoreBreakableJoints=("b_MF_Face","b_MF_Head","b_MF_Spine_03","b_MF_Armor_Crotch","b_MF_Armor_Sho_R","b_MF_UpperArm_R","b_MF_Hand_R","b_MF_Calf_R","b_MF_Calf_L")
    ...
}

톱질

  1. 발동
    // GearPawn.uc
    simulated function GoreExplosion( Vector Momentum, Vector HitLocation, class<GearDamageType> GearDamageType, optional bool bRandomizeGibImpluse )
    {
        ...
        if ( GearDamageType == class'GDT_Chainsaw' )
        {
            ChainsawGore();            // 톱질 Gore 발동
            bHasGoreExploded = TRUE;   // 고어폭발되었음
            return;
        }
        ...
    }
  2. 쪼개짐 시작 호출
    // GearPawn_COGMarcus.uc
    simulated function ChainSawGore()
    {
        BreakConstraint( vect( 100, 0, 0 ), vect( 0, 10, 0 ), 'b_MF_Spine_03' );
        BreakConstraint( vect( 0, 100, 0 ), vect( 0, 0, 10 ), 'b_MF_UpperArm_R' );
    }
  3. Constraint 해제
    // GearPawn.uc
    simulated final function BreakConstraint( Vector Impulse, Vector HitLocation, Name InBoneName, optional bool bVelChange )
    {
        ...
        // Mesh 의 Constraint 해제 함수 호출
        Mesh.BreakConstraint( Impulse, HitLocation, InBoneName, bVelChange );
     
        // InBoneName 에 의존되어 있는 관련 Bone의 Constraint 를 모두 해제
        BreakDependantBones( Impulse, HitLocation, InBoneName );
        ...
    }
  4. 관련 Bone의 Constraint 모두 해제
    // GearPawn.uc
    struct native DependantBreakInfo
    {
        var Name   ParentBone;           // 아래 배열에 포함된 Bone들이 존속된 부모Bone 이름
        var Array<Name> DependantBones;  // 부모Bone 이 쪼개질 때, 같이 쪼개져야 하는 Bone 리스트
    };
    var Array<DependantBreakInfo>  JointsWithDependantBreaks;    // 내가 쪼개질 때, 같이 쪼개질 Bone들의 정보를 담은 배열
     
    simulated final function BreakDependantBones( Vector Impulse, Vector HiLocation, Name InBoneName, optional bool bVelChange )
    {
        for ( i = 0; i < JointsWithDependantBreaks.length; i++ )
        {
            if ( JointsWithDependantBreaks[i].ParentBone == InBoneName )
            {
                for ( j = 0; j < JointsWithDependantBreaks[i].DependantBones.length; J++ )
                {
                    // InBoneName 에 의존적인 모든 Bone 의 Constraint 역시 해제
                    BreakConstraint( Impulse, HitLocation, JointsWithDependantBreaks[i].DependantBones[j], bVelChange );
                }
            }
        }
    }
  5. 실제 Mesh 로부터 Constraint 해제
    // SkeletalMeshComponent.uc
    simulated final function BreakConstraint( Vector Impulse, Vector HitLocation, Name InBoneName, optional bool bVelChange )
    {
        // 실제 코드 참조
    }

3215812168_36d1b754c5_o.jpg

피를 적절한 곳에 Decal 로써 뿌리는 처리는 고어게임에서 가장 기본적인 양념이다. 위 이미지는 다음과 같은 피 처리를 하고 있다.

  1. 개틀링건을 들고 있는 Dizzy 란 캐릭터가 지금 총을 맞으며 피가 튀고 있다.
  2. 양 캐릭터 가운데 벽을 보면 피를 흘린채 cover 를 이동할 때 남은 핏자국을 볼 수 있다. 둘 중 누가 이동했는지는 모르겠다.
  3. 땅을 보면 떨어진 핏자국을 볼 수 있다.
  4. 이 이미지에는 잘 보이진 않지만 치명상을 입을 경우 자신의 몸에서 흐르는 피를 볼 수도 있다. 등을 타고 내려오는 피의 흐름

기본적으로는 엔진이 지원하는 Emitter 와 Decal 을 사용하지만 GoW2 에서는 좀 더 최적화된 모습으로 이들을 처리하고 있다.

변수

함수

// GearPawnFX.uc
이 클래스는 캐릭터가 피를 흘리는 다양한 상황에 대한 처리를 담당하고 있다.
DBNO 시 지면에 피가 남는 Decal 처리
MeatBag? 시 지면에 피가 남는 Decal 처리
치명상인 상태로 Cover 이동 시 벽에 피가 남는 Decal 처리

코드 플로우

참조