캐릭터

UE3를 활용하여 자신의 프로젝트에 맞는 캐릭터를 구현하기에 앞서 UE3에 이미 구현되어 있는 캐릭터 작동 방식을 분석하기로 한다. 그리하여 캐릭터 구현에 필요한 기능이 중복구현됨을 막고 효율적으로 UE3에 통합될 수 있도록 한다.

여기서 살펴볼 내용은 Core기능에 해당하는 AnimTree, AnimSet, Morph 등의 기능이 아니라, 로직에 해당하는 Pawn, PlayerController, PlayerReplicationInfo 등의 오버뷰와 이들이 엔진 내 어떤 포지션에 해당하는지를 중점으로 살펴본다.

캐릭터 Bone 구조

분석 요소

UE3에서 캐릭터를 구현하는데 필요한 요소들을 살펴본다.

Pawn

  • 특징
    • Actor→Pawn 상속
    • player 나 AI 가 컨트롤하기 위한 Base Actor 클래스에 해당한다.
    • mesh, collision, physics 등의 기능이 있고, 데미지를 주며 소리를 내고 무기나 다른 소지품을 들고 다니며, 월드상 다른 Pawn 과의 물리적 인터렉션 등을 담당한다.
  • 기능
    • 캐릭터가 돌아 다닐 수 있는 floor 에 대한 성격을 정의한다.
      • Pawn 이 서있는 지면의 Normal ( Floor, PHYS_Spider, PHYS_Walking 에서만 쓰인다. )
      • 올라갈 수 있는 floor 높이를 정의. ( MaxStepHeight, MaxJumpHeight )
      • 내려갈 수 있는 floor 높이를 정의. ( LedgeCheckThreshold )
      • 이동 가능한 floor 경사면 값 지정. ( WalkableFloorZ, 이는 floor 의 normal 벡터와 UpVector 를 내적한 스칼라 값 이며 Pawn은 기본적으로 0.7 (~= 45 degree) 로 초기화 되어 있다. )
      • 상기 3가지 사항은 캐릭터 이동 및 PathFinding 등에 활용된다.
    • 모든 Pawn을 링크드리스트로 관리. ( NextPawn, 월드 상 모든 Pawn 순회에 용이하다. )
    • Crouch 관련 정보 정의 가능
      • 웅크릴 수 있다. ( bCanCrouch )
      • 웅크리길 원한다. ( bWantsToCrouch )
      • 웅크린 상태이다. ( bIsCrouched )
      • 다시 일어서고 싶다. ( bTryToUncrouch )
      • Crouch 시, 실린더 정보 ( CrouchHeight, CrouchRadius )
      • 웅크리기 & 일어서기 이벤트 함수 제공.
        // APawn::performPhysics 로부터 호출
        void APawn::Crouch(INT bClientSimulation)
        {
            ...
        }
         
        void APawn::UnCrouch(INT bClientSimulation)
        {
            ...
        }
    • 이동 목적지에 다다르면 smooth하게 속도를 줄이면서 멈춘다. ( bReducedSpeed )
    • 여러 행위들에 대한 플래그
      • 점프기능이 있다. ( bJumpCapable )
      • 점프할 수 있다. ( bCanJump )
      • 데미지 입을 수 있다. ( bCanBeDamaged )
      • 웅크릴 수 있다. ( bCanCrouch )
      • 날 수 있다. ( bCanFly )
      • 수영할 수 있다. ( bCanSwim )
      • 텔레포트할 수 있다. ( bCanTeleport )
      • 걸을 수 있다. ( bCanWalk )
      • 사다리에 오를 수 있다. ( bCanClimbLadders )
      • 횡이동 할 수 있다. ( bCanStrafe )
      • 이 밖에 다양한 플래그 들 ( bAvoidLedges, bStopAtLedges, bAllowLedgeOverhang, bSimulatedGravity, bIgnoreForces, bCanWalkOffLedges, bCanBeBaseForPawns, bSimGravityDisabled, bDirectHitWall, bPushesRigidBodies, bForceFloorCheck, bForceKeepAnchor )
      • 곧 Pawn 에서 제거될 것 같은? 플래그 들 ( bCanMantle, bCanClimbUp, bCanClimbCeilings, bCanSwatTurn, bCanLeap, bCanCoverSlip )
    • AI 관련 변수
      • 성별 ( bIsFemale )
      • 아이템을 집을 수 있다. ( bCanPickupInventory )
      • AI가 날 무시하게 할 수 있다. ( bAmbientCreature )
      • 소리를 들을 수 있다. ( bLOSHearing )
      • 벽을 통해 들려오는 숨죽인 소리를 들을 수 있다. ( bMuffledHearing )
      • 게임 시작 시 controller 에 의해 소유되지 않도록 한다. vehicle 들은 게임 시작 시 controller 에 의해 선점되면 안되겠지.. ( bDontPossess )
      • 난 움직일 수 없다. ( bStationary )
      • 무기 사용할 수 없다. ( bNoWeaponFiring )
      • 뭔가를 사용할 수 있다. ( bCanUse )
      • 이 밖에 다양한 플래그 들 ( bModifyReachSpecCost, bModifyNavPointDest, bPathfindingsAsVehicle, )
      • Path Finding 관련
        • 내가 길을 찾는 방법 ( PathSearchType )
        • 기타 관련 변수 들 ( PathConstraintList, PathGoalList )
      • 들을 수 있는 거리 ( HearingThreshold )
      • 소리를 들을 수 있는 각성도 ( Alertness, -1 ~ 1 사이의 값으로써 높을 수록 소리를 더 잘 들을 수 있다. )
      • 시야 거리 ( SightRadius )
      • 시야 각 ( PeripheralVision, degree 의 cosine 값, default 로 -0.75 ~= 140 도 )
      • 물리적 업데이트할 모니터링 시간 ( AvgPhysicsTime, AI 가 목적지로 가기 위해 업데이트할 시간? 구불구불하게 가거나 똑바로 가게 하는 등의 처리를 위한 변수 )
    • 이동 관련 속성
      • 희망 무브먼트 속도 ( DesiredSpeed & MaxDesiredSpeed, GroundSpeed 에 곱해지므로 실질적으로 Pawn 의 전체적 무브먼트 스피드라고 보아도 무방 )
        // Pawn.uc
        defaultproperties
        {
            ...
            DesiredSpeed=+00001.00000
            ...
        }
         
        // UnController.cpp
        void AController::MoveTo( ... )
        {
            ...
            Pawn->DesiredSpeed = Pawn->MaxDesiredSpeed;
            ...
        }
         
        void AController::MoveToward( ... )
        {
            ...
            Pawn->DesiredSpeed = Pawn->MaxDesiredSpeed;
            ...
        }
         
        // UnPhysic.cpp
        FLOAT APawn::MaxSpeedModifier()
        {
            ...
            if ( !IsHumanControlled() )
            {
                Result *= DesiredSpeed;  // 사람이 조종하지 않는 녀석에 한해서만 적용
            }
            ...
        }
      • 가장 가까운 path ( Anchor )
      • nav mesh 인덱싱 ( AnchorItem, 헌데 사용되지는 않음 )
      • 최근에 도달한 가까운 path ( LastAnchor )
      • 마지막 path finding 실패 시기 ( FindAnchorFailedTime, FindPath() 함수 시도가 실패한 마지막 시간 )
      • 마지막 유효 path 발견 시기 ( LastValidAnchorTime )
      • 도착점 offset ( DestinationOffset )
      • 루트상 다음 지점의 반지름 ( NextPathRadius )
      • 구불구불 이동
        • 방향 ( SerpentineDir )
        • 거리 ( SerpentineDist )
        • 시간 ( SerpentineTime, 구불구불 이동 시 횡이동 시도하기 전까지 직진할 시간 )
    • 물리?
      • Pawn 의 질량 ( Mass )
        // Pawn.uc
        function CrushedBy( Pawn OtherPawn )
        {
            TakeDamage(
                ( 1 - OtherPawn.Velocity.Z / 400 ) * OtherPawn.Mass / Mass,   // 데미지, 위에서 내리누를 때 상대방과의 질량에 대비하여 데미지를 가감하는 용도.
                OtherPawn.Controller,                                         // 유발자 Controller
                Location,                                                     // HitLocation
                vect( 0, 0, 0 ),                                              // Momentum
                class'DmgType_Crushed' );                                     // 데미지 타입
        }
         
        event TakeDamage( ... )
        {
            ...
            momentum = momentum / Mass;     // 데미지가 가해질 때 전해진 충격량으로부터 움직일 속도를 구함. F=ma -> a=F/m
            ...
        }
      • 수영할 때 적용할 물 부양성 ( Buoyancy, 1=자연스러운 부양성 0=no부양성 )
        // UnPhysic.cpp
        void APawn::CalcVelocity( ... )
        {
            ...
            if ( bBuoyant )
            {
                Velocity.Z += GetGravityZ() * DeltaTime * ( 1.f - Buoyancy );  // Buoyancy 가 0 이면 중력을 그대로 적용한다.
            }
            ...
        }
      • 밀리어택 최대거리 ( MeleeRange, 일반적인 밀리어택이 아니라 이동하는 도중 목적지까지의 거리를 가늠하는데 사용하는 것 같음 )
    • 일반
      • 스폰 시간 ( SpawnTime, 스폰 후 일정시간동안 데미지 감소따위를 하는 데 사용. 관련변수 UTGame.SpawnProtectionTime )
      • view pitching 제한 ( MaxPitchLimit )
    • 무브먼트
      • controller 없이도 physics 돌려라~ ( bRunPhysicsWithNoController, acceleration 이 아닌 velocity 에 의해서만 움직이게 되겠다. )
      • 풀 악셀 ( bForceMaxAccel, 기존 acceleration 무시하고 풀악셀로 최대 velocity 를 이끌어 낸다. )
      • 최대 지형 이동 속도 ( GroundSpeed )
      • 최대 수영 이동 속도 ( WaterSpeed )
      • 최대 활강 이동 속도 ( AirSpeed )
      • 최대 등반 이동 속도 ( LadderSpeed )
      • 가속 비율 ( AccelRate, 이것이 곱해져 acceleration 을 구한다. )
      • 점프 속도 ( JumpZ, 수직 up 방향 속도 )
      • 수중 이탈 속도 ( OutofWaterZ, 점프로 물 밖으로 이탈할 때 z up 방향 속도. 물 근처 난간위로 올라가는 것을 보장하기 위해 설정하는 값인듯 )
        // Pawn.uc
        function JumpOutOfWater( vector jumpDir )
        {
            ...
            velocity.Z = OutofWaterZ;  // set here so physics uses this for remainder of tick
            ...
        }
      • 수중 이탈 가능 높이 ( MaxOutOfWaterStepHeight, 수영하며 수중이탈 가능한올라갈 수 있는 높이 )
      • 낙하 최대 가속도 제한할까? ( bLimitFallAccel )
      • 공중에서의 컨트롤 시간 factor ( AirControl, acceleration 을 이 시간만큼 적용한 후 테스트하는 용도 )
        // UnPhysic.cpp
        void APawn::physFalling( FLOAT deltaTime, INT Iterations )
        {
            ...
            if ( !bDoRootMotion && TickAirControl > 0.05f )
            {
                // 현재 velocity 에 TickAirControl 시간만큼 경과 후 delta velocity 까지 더한 후 이동거리를 체크한다.
                FVector TestWalk = ( TickAirControl * AccelRate * Acceleration.SafeNormal() + Velocity ) * deltaTime;
                TestWalk.Z = 0.f;
                ... // 이후는 현재 Location 으로부터 TestWalk 만큼 이동한 곳에 특정 world 오브젝트가 있는지 (지형 포함) 체크한다.
            }
            ...
        }
      • 걷기&웅크리기 속도 퍼센티지 ( WalkingPct & CrouchedPct, 기본 이동 속도에 곱하여 걷기속도 및 웅크리기속도를 구하는 방식에 사용 )
        // UnPhysic.cpp
        FLOAT APawn::MaxSpeedModifier()
        {
            ...
            if ( bIsCrouched )
            {
                Result *= CrouchedPct;
            }
            else if ( bIsWalking )
            {
                Result *= WalkingPct;   // 바로 위에서 Pawn 의 무브먼트 속도를 Result 에 누적하여 구하고 그것을 Walking 상태여부에 따라 곱하여 현재 무브먼트 속도를 구한다.
            }
            ...
        }
      • 데미지 없이 낙하 가능한 속도 ( MaxFallSpeed, velocity 와 비교된다. )
      • AI들은 이보다 적은 낙하속도가 가능한 길을 택할 것이다. ( AIMaxFallSpeedFactor, 바로 위 변수와 곱하여 AI를 위한 낙하 속도를 구함 MaxFallSpeed * AIMaxFallSpeedFactor )
    • Camera 관련
      • Pawn 카메라 높이 ( BaseEyeHeight )
        // UnPawn.cpp
        FVector APawn::GetPawnViewLocation()
        {
            return Location + FVector( 0.f, 0.f, 1.f ) * BaseEyeHeight;
        }
      • 계산된/조절된 Pawn 카메라 높이 ( EyeHeight )
    • 숨쉬기/HP
      • 숨을 쉬기 위한 머리 ( HeadVolume )
      • HP ( Health )
      • 최대 HP ( HealthMax )
      • HP 를 모든 클라에게 복제할까? ( bReplicateHealthToAll )
      • 숨쉬기 타이머 ( BreathTime, 물에 빠졌을 때 일정 시간마다 데미지를 주기 위한 주기 )
        // UnLevTic.cpp
        void APawn::TickSpecial( FLOAT DeltaSeconds )
        {
            // Authority 이고 BreathTime 중이라면
            if ( Role == ROLE_Authority && BreathTime > 0.f )
            {
                BreathTime -= DeltaSeconds;
         
                if ( BreathTime < 0.001f )
                {
                    // 때가 됐다면 BreathTimer 호출 (바로 아래)
                    BreathTime = 0.0f;
                    eventBreathTimer();
                }
            }
         
            ...
        }
         
        // Pawn.uc
        event BreathTimer()
        {
            if ( HeadVolume.bWaterVolume )
            {
                if ( Health < 0 || WorldInfo.NetMode == NM_Client || DrivenVehicle != None )
                    return;    // 죽었거나 클라이언트거나 무엇인가를 타고있다면 무시
         
                TakeDrowningDamage();    // 익사 피해
         
                if ( Health > 0 )
                    BreathTime = 2.0;    // 2초 후 다시 BreathTimer 호출
            }
            else
            {
                BreathTime = 0.0;        // 더 이상 피해는 없음
            }
        }
      • 얼마만큼 숨을 참을 수 있는가? ( UnderWaterTime, 최초 입수 시 BreathTime 에 대입되는 값. 이 시간이 지나면 위의 BreathTimer 함수에 나와있는대로 2초마다 데미지 )
        // UTPawn.uc
        event HeadVolumeChange( PhysicsVolume newHeadVolume )
        {
            ...
         
            else if ( ... )
            {
                BreathTime = UnderWaterTime;    // 입수 시 숨쉬기 타이머 발동
            }
        }
         
        defaultproperties
        {
            ...
            UnderWaterTime=+00020.000000    // 20초
            ...
        }
      • 마지막으로 피해를 입은 시간 ( LastPainTime, BOT 의 경우 피격받고 일정 시간동안 Aim 을 불안정하게 하기위한 용도로써 활용된다. )
        // Pawn.uc
        function PlayHit( ... )
        {
            ...
            LastPainTime = WorldInfo.TimeSeconds;
        }
    • 루트모션
      • 루트모션 속도 ( RMVelocity, 클라이언트에서 루트모션을 재현하기 위한 용도의 변수. 또는 Controller.bPreciseDestination정확한 이동 기능이 활성화일 때 사용되기도 함. )
      • 강제 루트모션 속도 사용 ( bForceRMVelocity, 이 값이 true 면 APawn::CalcVelocity() 에서 RMVelocity 값을 직접적으로 사용 )
        // UnPhysic.cpp
        void APawn::CalcVelocity( ... )
        {
            ...
            if ( bForceRMVelocity )
            {
                Velocity = RMVelocity;
                return;
            }
            ...
        }
      • 강제 일반적인 속도계산 ( bForceRegularVelocity, 이 값이 true 면 APawn::CalcVelocity() 에서 RMVelocity 값을 절대로 사용하지 않는다. )
    • 사운드와 노이즈
      • 여러 변수들 ( noise1spot, noise1time, noise1other, noise1loudness, noise2spot, noise2time, noise2other, noise2loudness )
      • 사운드 음 꺾기 ( SoundDampening )
    • 데미지
      • 데미지 증가 ( DamageScaling ) *

Controller

PlayerController

PlayerReplicationInfo

UTGame 관련

샘플로 제공되는 언리얼토너먼트3 (이하 UTGame) 의 캐릭터를 구현하는데 활용된 여러 요소들을 추가적으로 살펴본다.

UTFamilyInfo

UTPlayerReplicationInfo

이동

정확한 이동

엔진에서 목표지점destination까지 정확하게 이동시켜주는 로직이 존재한다.

대략적인 방법은 이렇다.

  1. 정확히 도달해야 하는 목표지점을 설정한다. (by controller)
  2. 매 프레임마다 도착했는지 여부를 검사하고 아직 도달하지 못했다면 적절한 velocity 를 계산한다.
  3. 목표지점destination에서의 허용 Offset 안에 도달하면 이벤트함수를 호출한다. ( Controller.ReachedPreciseDestination() )

관련 변수/함수는 아래와 같다.

  • 변수
    // Pawn.uc
    var float             DestinationOffset;          // 목표지점으로부터의 허용 Offset
     
    // Controller.uc
    var bool              bPreciseDestination;        // 목표지점에 맞는 velocity 를 강제할 것인지의 여부, 정확한 이동을 수행할 것인지 여부와 상통
    var BasedPosition     DestinationPosition;        // 목표지점
  • 함수
    // UnPhysic.cpp
    void Pawn::CalcVelocity( ... )
    {
        ...
        // RooMotion 일 경우를 제외하고 '정확한 이동' 처리를 수행한다.
        if ( !bDoRootMotionAccel && Controller && Controller->bPreciseDestination )
        {
            FVector Dest = controller->GetDestinationPosition();   // Controller.DestinationPosition 을 Vector 로 형변환하여 리턴
            if ( ReachedDestination( Location, Dest, NULL ) )
            {
                Controller->bPreciseDestination = FALSE;           // '정확한 이동'을 종료
                Controller->eventReachedPreciseDestination();      // 종료 이벤트 호출
     
                Velocity = FVector( 0.f );
                Acceleration = FVector( 0.f );
            }
            else if ( bForceMaxAccel )
            {
                const FVector Dir = (Dest - Location).SafeNormal();
                Acceleration      = Dir * MaxAccel;
                Velocity          = Dir * MaxSpeed;
            }
            else
            {
                Velocity = (Dest - Location) / DeltaTime;
            }
            ...
        }
        ...
    }
     
    // UnPawn.cpp
    UBOOL APawn::ReachedDestination( ... )
    {
        ...
     
        return ReachThresholdTest( ... );
    }
     
    UBOOL APawn::ReachThresholdTest( ... )
    {
        ...
        FLOAT Threshold = ThresholdAdjust + CylinerComponent->CollisionRadius + DestinationOffset;    // 도착으로 인정할 유효 반지름을 계산
        ...
        if ( Dir.SizeSquared() > Threshold * Threshold )
            return FALSE;
        ...
        // 적절하게 테스트하고
        return TRUE;        // 도착했다고 판정
    }

상기 APawn::ReachThresholdTest 함수의 동입부에 도착지점으로부터의 허용 반지름을 계산하는 부분이 있는데, 이 값에 음수를 주어 좀 더 정확한 목표지점에 도달하게 할 수 있다.

무브먼트

언리얼엔진3에 기본적으로 제공되는 UTGame 을 기반으로 한 분석내용이다.

웅크리기 Crouch

코드플로우

  • 발생
    1. DefaultInput.ini
      • .Bindings=(Name=“C”,Command=“GBA_Duck”)
      • .Bindings=(Name=“GBA_Duck”,Command=“Duck | onrelease UnDuck | Axis aUp Speed=-1.0 AbsoluteAxis=100”)
    2. simulated exec function Duck() (UTPlayerInput.uc)
      • bDuck = true;
  • 루프1
    1. state PlayerWalking::ProcessMove(…) (PlayerController.uc) ← state PlayerWalking::ProcessMove(…) (UTPlayerController.uc) ← state PlayerWalking::PlayerMove(float DeltaTime) (PlayerController.uc) ← state PlayerWalking::PlayerMove(float DeltaTime) (UTPlayerController.uc) ← event PlyaerTick(float DeltaTime) (PlayerController.uc) ← event PlayerTick(float DeltaTime) (UTPlayerController.uc)
      • CheckJumpOrDuck();
    2. function CheckJumpOrDuck() (UTPlayerController.uc)
      • 점프나 더블점프를 수행하는 함수를 호출
      • Pawn.ShouldCrouch(bDuck != 0);
    3. funciton SouldCrouch( bool bCrouch ) (Pawn.uc)
      • bWantsToCrouch = bCrouch;
  • 루프2
    1. AActor::TickAuthoritative ← AActor::Tick ← APawn::Tick ← TickActors<FDeferredTickList::FGlobalActorIterator>
      • performPhysics 호출
    2. APawn::performPhysics(float DeltaSeconds) (UnPhysic.cpp)
      • 적절한 조건을 체크한 후 (bWantsToCrouch, bIsCrouched 따위) Crouch()UnCrouch() 를 호출
    3. APawn::Crouch(INT bClientSimulation) (UnPawn.cpp)
      • Collision Cylinder 의 반지름과 높이를 CrouchRadius 와 CrouchHeight 로 변경
      • Crouch Collision Cylinder 가 더 커졌다면 지면을 침범했는지 체크한다.
      • eventStartCrouch( 높이차이 ) 호출
    4. simulated event StartCrouch(float HeightAdjust) (UTPawn.uc)
      • Super.StartCrouch(HeightAdjust);
      • CrouchMeshZOffset = HeightAdjust;
    5. simulated event StartCrouch(float HeightAdjust) (Pawn.uc)
      • EyeHeight 조절
      • OldZ 조절
      • BaseEyeHeight 조절

특이사항

  • 웅크리기에 사용될 충돌영역 정보인 CrouchRadius, CrouchHeight 를 세팅해야 한다.
  • 이때 MaxStepHeight 가 CollisionHeight - CrouchHeight 보다는 커야 한다. MaxStepHeight 는 캐릭터가 자연스럽게 이동 가능한 최대 허용높이이다. 웅크리기가 발동되면 내부적으로 캐릭터 바운딩실린더 높이를 조절하는데 그 갭이 MaxStepHeight 보다 크면 캐릭터는 Falling 운동을 하게끔 처리된다. (엔진 내부적으로) 때문에 주의가 필요하다.

점프 Jump

코드 플로우

  1. 발동
    // DefaultInput.ini
    .Bindings=(Name="GBA_Jump",Command="Jump | Axis aUp Speed=+1.0 AbsoluteAxis=100")
  2. 플래그 체크
    // UTPlayerInput.uc
    exec function Jump()
    {
        ...
        Super.Jump();
        ...
    }
     
    // PlayerInput.uc
    exec function Jump()
    {
        ...
        bPressedJump = true;
    }
  3. 지연 점프
    // PlayerController.uc
    state PlayerWalking
    {
        ...
        function PlayerMove( float DeltaTime )
        {
            ...
            // 지금 점프할 수 없는 상황이면 지연시킨다.
            if ( bPressedJump && Pawn.CannotJumpNow() )
            {
                bSaveJump = true;
                bPressedJump = false;
            }
            ...
        }
        ...
    }
  4. 점프 시도여부 체크 (매 프레임)
    // UTPlayerController.uc
    function CheckJumpOrDuck()
    {
        ...
        else if ( bPressedJump )
        {
            Pawn.DoJump( bUpdateing );
        }
        ...
    }
     
    // PlayerController.uc
    function CheckJumpOrDuck()
    {
        if ( bPressedJump && (Pawn != None) )
        {
            Pawn.DoJump( bUpdating );
        }
    }
  5. 실제 점프로직 처리
    // UTPawn.uc
    function bool DoJump( bool bUpdating )
    {
        ...
        if ( Physics ==PHYS_Spider )
            Velocity = JumpZ * Floor;
        else if ( Physics == PHYS_Ladder )
            Velocity.Z = 0
        else if ( bIsWalking )
            Velocity.Z = Default.JumpZ;
        else
            // 보통 이부분에 걸린다.
            Velocity.Z = JumpZ;
        ...
    }

부가기능

TurnInPlace

Pawn 의 현재 Rotation 을 기준으로 좌우 일정 반경을 회전할 동안 발이 땅에 접지한 상태로 있는 기능을 지원한다. UnrealEngine3 에서는 이 기능을 Turn-In-Place 라고 지칭한다. 이를 수행하는 레이어는 UDKPawn 이다.

분석 포인트

  • AnimTree 노드 캐싱
    // UTPawn.uc
    simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
    {
        ...
        // 허리쪽 Bone 을 제어하는 컨트롤 캐싱 (RootRot 이란 이름을 가진 SkelControlSingleBone 컨트롤)
        RootRotControl = SkelControlSingleBone( mesh.FindSkelControl( 'RootRot' ) );
        ...
        // 조준 노드 캐싱
        AimNode = AnimNodeAimOffset( mesh.FindAnimNode( 'AimNode' ) );
        ...
    }
  • 업데이트 로직
    // UDKPawn.cpp
    void AUDKPawn::TickSpecial( FLOAT DeltaSeconds )
    {
        ...
        // 현재 Aim pitch 와 yaw 를 얻는다.
        INT PawnAimPitch;
        if ( Controller )
        {
            // 컨트롤러의 Pitch를 얻는다.
            PawnAimPitch = Controller->Rotation.Pitch;
        }
        else
        {
            // Pawn 의 Pitch를 얻는다.
            PawnAimPitch = Rotation.Pitch;
     
            if ( PawnAimPitch == 0 )
            {
                PawnAimPitch = RemoteViewPitch << 8;
            }
        }
     
        // Pawn 의 최종 조준 Pitch
        PawnAimPitch = UnwindRot( PawnAimPitch );
     
        INT PawnAimYaw = UnwindRot( Rotation.Yaw );
        // Pawn 의 최종 조준 Yaw (가 될 값)
        INT AimYaw = 0;
     
        if ( Physics == PHYS_Walking && Velocity.Size() < KINDA_SMALL_NUMBER )
        {
            // PawnAimYaw 는 손이 향하는 방향, RootYaw 는 발이 향하는 방향이라 생각하면 이해가 쉽다.
            INT CurrentAimYaw = UnwindRot( PawnAimYaw - RootYaw );
            INT RootRot = 0;
     
            if ( CurrentAimYaw > MaxYawAim )
            {
                RootRot = ( CurrentAimYaw - MaxYawAim );
            }
            else if ( CurrentAimYaw < -MaxYawAim )
            {
                RootRot = ( CurrentAimYaw - (-MaxYawAim) );
            }
     
            RootYaw += RootRot;
            RootYawSpeed += ( (FLOAT)RootRot ) / DeltaSeconds;
     
            // 최종 손과 발의 offset. 이것이 곧 Aim 노드에 적용될 Yaw
            AimYaw = UnwindRot( PawnAimYaw - RootYaw );
        }
        else
        {
            RootYaw = Rotation.Yaw;
            RootYawSpeed = 0.f;
            AimYaw = 0;
        }
     
        // 좌우 90 도 회전을 각각 -1, 1 로 매핑시킨 값으로 변환
        if ( !bNoWeaponFiring )
        {
            CurrentSkelAim.X = Clamp<FLOAT>( ( (FLOAT)AimYaw / 16384.f ), -1.f, 1.f );
            CurrentSkelAim.Y = Clamp<FLOAT>( ( (FLOAT)PawnAimPitch / 16384.f ), -1.f, 1.f );
        }
     
        // 허리를 Aim 의 반대쪽으로 회전. 즉, 손의 방향이 Pawn Rotation 과 일치하고 발의 방향을 보정하는 방식
        if ( RootRotControl )
        {
            RootRotControl->BoneRotation.Yaw = -AimYaw;
        }
     
        // Aim 업데이트
        if ( AimNode )
        {
            AimNode->Aim = CurrentSkelAim;
        }
        ...
    }

분석

참고