This document is about: QUANTUM 2
SWITCH TO

운동학적 캐릭터 컨트롤러 (KCC)

소개

KCC(Kinematic Character Controller)는 간단히 말해서 KCC(Kinematic Character Controller)는 자신만의 규칙에 따라 세계 내에서 캐릭터를 이동시키는 데 사용됩니다. 물리/힘 기반 이동이 아닌 KCC를 사용하면 보다 엄격한 제어와 빠른 이동이 가능합니다. 이러한 개념은 모든 게임의 핵심이지만, 전체적인 게임 플레이와 관련이 있기 때문에 정의상 매우 다양합니다. 따라서 Quantum SDK에 포함된 KCC는 출발점으로 간주되어야 하지만, 게임 개발자들은 특정 상황에 맞는 최상의 결과를 얻기 위해 자신만의 KCC를 만들어야 할 것입니다.

Quantum은 2D(측면 스크롤)용과 3D 이동을 위한 두 개의 사전 빌드 KCC를 제공합니다. API를 통해 캐릭터는 지형을 통과하고, 계단을 오르고, 경사면을 미끄러져 내려가고, 움직이는 플랫폼을 사용할 수 있습니다.

KCC는 움직임 벡터를 계산할 때 정적 및 동적 물체의 물리 데이터를 모두 고려합니다. 객체가 캐릭터의 이동을 차단하고 정의합니다. 환경 객체와의 충돌 콜백도 트리거 됩니다.

필요 요건

KCC를 사용하거나 엔티티에 추가하려면 트랜스포 컴포넌트가 이미 있어야 합니다. Physics Body사용할 수 있지만 필수는 아닙니다. 일반적으로 물리 시스템에 영향을 미쳐 의도하지 않은 이동이 발생할 수 있으므로 KCC와 함께 물리 바디를 사용하지 않는 것이 좋습니다.

Quantum의 물리에 대해 아직 잘 모르는 경우 물리 설명서를 먼저 검토하십시오.

Raycasts & ShapeOverlap

KCC는 움직임을 계산하기 위해 2D의 경우 원, 3D의 경우 구 등 형상 중첩만 사용합니다. 따라서 KKC 컴포넌트만 있는 컴포넌트는 레이 캐스트에 의해 무시됩니다. 레이 캐스팅 대상인 경우 물리 충돌기도 장착해야 합니다.

노트

이 페이지는 2D 및 3D KCC에 대해 다룹니다.

캐릭터 컨트롤러 컴포넌트

다음 중 하나를 사용하여 엔티티에 캐릭터 컨트롤러 컴포넌트를 추가할 수 있습니다.

  • 유니티에서 엔티티 프로토타입에 "캐릭터 컨트롤러" 컴포넌트를 추가합니다.
  • 코드를 통해 "캐릭터 컨트롤러" 컴포넌트를 추가합니다.
kcc 2d and 2d components in unity
유니티 편집기의 엔티티 프로토타입에 연결된 캐릭터 컨트롤러 2D 및 3D 컴포넌트입니다.

코드를 통해 캐릭터 컨트롤러를 추가하려면 아래 예를 따르십시오.

C#

// 2D KCC
var kccConfig = FindAsset<CharacterController2DConfig>(KCC_CONFIG_PATH);
var kcc = new CharacterController2D();
kcc.Init(f, kccConfig)
f.Add(entity, kcc);

// 3D KCC
var kccConfig = FindAsset<CharacterController3DConfig>(KCC_CONFIG_PATH);
var kcc = new CharacterController3D();
kcc.Init(f, kccConfig)
f.Add(entity, kcc);

노트

컴포넌트를 만든 후 초기화해야 합니다. 사용 가능한 초기화 옵션은 다음과 같습니다.

  • (코드) 파라미터가 없는 Init() 메소드, Assets/Resources/DB/Configs에서 DefaultCharacterController를 로드합니다.
  • (코드) 파라미터가가 있는 Init() 메소드, CharacterControllerConfig로 전달된 것을 로드합니다.
  • (에디터) Character Controller 컴포넌트 내의 Config 슬롯에 CharacterControllerConfig 추가.

캐릭터 컨트롤러 구성

Create > Quantum > Assets > Physics > CharacterController2D/3D의 컨텍스트 메뉴를 통해 나만의 KCC 구성 에셋을 생성합니다.

기본 구성 에셋

기본 2D 및 3D KCC 구성 에셋은 Assets/Resources/DB/Configs 폴더 안에 있습니다. 3D KCC 구성은 다음과 같습니다:

kcc 3d default config
DefaultCharacterController3D 구성 에셋.

구성 필드에 대한 간략한 설명

  • Offset 엔티티 위치를 기준으로 KCC 로컬 위치를 정의하는 데 사용됩니다. 일반적으로 KCC의 중심을 캐릭터의 발에 위치시킬 때 사용됩니다. KCC는 캐릭터를 이동하는 데 사용되므로 반드시 캐릭터의 전체 몸체를 캡슐화할 필요는 없습니다.
  • Radius 캐릭터의 경계를 정의하며 캐릭터의 수평 크기를 포함해야 합니다. 이것은 캐릭터가 특정 방향으로 움직일 수 있는지, 벽이 움직임을 막고 있는지, 스텝이 올라가는지, 또는 미끄러질 경사인지를 알기 위해 사용됩니다.
  • Max Penetration 캐릭터가 다른 물리적인 물체를 통과할 때 움직임을 부드럽게 합니다. 캐릭터가 최대 삽입 거리를 통과하면 하드 픽스가 적용되어 올바른 위치에 스냅 됩니다. 이 값을 0으로 줄이면 모든 보정이 완전하고 즉시 적용되며, 이로 인해 들쭉날쭉한 이동이 발생할 수 있습니다.
  • Extent 충돌이 선제적으로 탐지되는 반경을 정의합니다.
  • Max Contacts KCC가 계산한 접점의 양을 선택하는 데 사용됩니다. 1은 일반적으로 잘 작동하며 가장 성능이 좋은 옵션입니다. jerky 이동이 발생하는 경우 2로 설정하십시오. 추가 오버헤드는 무시할 수 있습니다.
  • Air Control True로 전환하면 KCC는 지면에 닿지 않을 때 움직임을 조정할 수 있습니다.
  • Acceleration 캐릭터의 가속 속도를 정의합니다.
  • Base Jump Impulse KCC Jump() 메소드를 호출할 때의 임펄스의 강도를 정의합니다. 메소드에 전달된 값이 없으면 이 값이 사용됩니다.
  • Max Speed 캐릭터의 최대 수평 속도를 제한합니다.
  • Gravity KCC에 중력을 가합니다.
  • Max Slope 캐릭터가 위아래로 걸을 수 있는 최대 각도(도)를 정의합니다.
  • Max Slope Speed 이동 유형이 수평 하강 대신 경사 하강일 때 캐릭터가 경사 아래로 미끄러지는 속도를 제한합니다.

캐릭터 컨트롤러 API

아래 API는 3D KCC에 초점을 맞춥니다. 하지만 2D API와 3D API는 매우 유사합니다.

속성과 필드

각 CharacterController 컴포넌트에는 다음과 같은 필드들이 있습니다.

C#

public FP MaxSpeed { get; set;}
public FPVector3 Velocity { get; set;} 
public bool Grounded { get; set;}
public FP CurrentSpeed { get;}
public AssetGUID ConfigId { get;}

MaxSpeed는 초기화 후 캐시 된 값입니다. 따라서 대시 수행과 같은 런타임에 수정할 수 있습니다.

API

각 KCC 구성 요소에는 다음과 같은 방법이 있습니다:

C#

// Initialization
public void Init(FrameBase frame, CharacterController3DConfig config = null);

// Jump
public void Jump(FrameBase frame, bool ignoreGrounded = false, FP? impulse = null);

// Move
public void Move(FrameBase frame, EntityRef entity, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, Boolean? useManifoldNormal = null, FP? deltaTime = null);

// Raw Information
public static CharacterController3DMovement ComputeRawMovement(Frame frame, EntityRef entity, Transform3D* transform, CharacterController3D* kcc, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, bool? useManifoldNormal = null);

JumpMove 메소드는 프로토 타이핑에 편리하며, ComputeRawMovement은 사용자 정의 이동을 만들기 위한 핵심 정보를 제공합니다. Quantum이 제공하는 KCC의 예에서, ComputeRawMovement의 정보는 내부 스티어링 방식인 ComputeRawSteer에 의해 Move에 사용된 스티어링을 계산하는 데 사용됩니다.

중요: 아래에 Jump(), Move()ComputeRawSteer()의 구현이 나와 있어 이해를 증진시키고 게임의 요구 사항에 맞는 맞춤형 구현을 만들 수 있습니다.

CharacterController3Movement

ComputeRawMovement()는 Shape Overlap을 수행하고 데이터를 처리하여 스티어링에 필요한 환경 데이터를 계산합니다. 메소드는 문자 이동에 적용할 수 있는 CharacterController3Movement 구조체를 반환합니다. 제공된 이동 데이터를 사용하여 사용자 지정 스티어링 구현을 만들 수도 있습니다.

CharacterController3Movement 구조체에는 다음 정보가 들어 있습니다.

C#

public enum CharacterMovementType
{
    None, // grounded with no desired direction passed
    FreeFall, // no contacts within the Radius
    SlopeFall, // there is at least 1 ground contact within the Radius, specifically a contact with a normal angle vs -gravity <= maxSlopeAngle). It is possible to be "grounded" without this type of contact (see Grounded property in the CharacterController3DMovement)
    Horizontal, // there is NO ground contact, but there is at least one lateral contact (normal angle vs -gravity > maxSlopeAngle)
}

public struct CharacterController3DMovement
{
  public CharacterMovementType Type;
  
  // the surface normal of the closest unique contact
  public FPVector3 NearestNormal;
  
  // the average normal from all contacts
  public FPVector3 AvgNormal;
  
  // the normal of the closest contact that qualifies as ground
  public FPVector3 GroundNormal;
  
  // the surface tangent (from GroundNormal and the derived direction) for Horizontal move, or the normalized desired direction when in CharacterMovementType.FreeFall
  public FPVector3 Tangent;
  
  // surface tangent computed from closest the contact normal vs -gravity (does not consider current velocity of CC itself).
  public FPVector3 SlopeTangent;
  
  // accumulated projected correction from all contacts within the Radius. It compensates with dot-products to NOT overshoot.
  public FPVector3 Correction;
  
  // max penetration of the closest contact within the Radius
  public FP Penetration;
  
  // uses the EXTENDED radius to assign this Boolean AND the GroundedNormalas to avoid oscilations of the grounded state when moving over slightly irregular terrain
  public Boolean Grounded;
  
  // number of contacts within Radius
  public int Contacts;
}

ComputeRawMovement()Move() 메소드에서 사용됩니다.

Jump()

이것은 참조용 구현입니다.

Jump는 단순히 KCC의 현재 Velocity에 임펄스를 추가하고 내부 ComputeRawSteer 메소드에 의해 처리될 Jumped 부울 값을 전환합니다.

C#

public void Jump(FrameBase frame, bool ignoreGrounded = false, FP? impulse = null) {

  if (Grounded || ignoreGrounded) {
  
    if (impulse.HasValue)
      Velocity.Y.RawValue = impulse.Value.RawValue;
    else {
      var config = frame.FindAsset(Config);
      Velocity.Y.RawValue = config.BaseJumpImpulse.RawValue;
    }
    
    Jumped = true;
  }
}

Move()

이것은 참조 구현입니다.

Move()은 캐릭터의 새 위치를 계산할 때 다음 사항을 고려합니다.

  • 현재 위치
  • 방향
  • 중력
  • 점프
  • 경사
  • 등등

이러한 모든 측면은 Init() 메소드에 전달된 구성 에셋에서 정의할 수 있습니다. 이는 지형, 메쉬 충돌기 및 프리미티브가 있는 FPS/TPS/Action 게임을 프로토 타이핑하는 데 편리합니다.

노트: 모든 것을 계산하고 최종 FPVector3 결과를 리턴하기 때문에 이동 자체를 제어할 수 있는 것은 아닙니다. 이동을 보다 엄격하게 제어하려면 ComputeRawMovement()를 사용하여 사용자 지정 조향 + 이동을 생성해야 합니다.

C#

public void Move(Frame frame, EntityRef entity, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, Boolean? useManifoldNormal = null, FP? deltaTime = null) {
  Assert.Check(frame.Has<Transform3D>(entity));

  var transform = frame.GetPointer<Transform3D>(entity);
  var dt        = deltaTime ?? frame.DeltaTime;

  CharacterController3DMovement movementPack;
  fixed (CharacterController3D* thisKcc = &this) {
    movementPack = ComputeRawMovement(frame, entity, transform, thisKcc, direction, callback, layerMask, useManifoldNormal);
  }

  ComputeRawSteer(frame, ref movementPack, dt);

  var movement = Velocity * dt;
  if (movementPack.Penetration > FP.EN3) {
    var config = frame.FindAsset<CharacterController3DConfig>(Config.Id);
    if (movementPack.Penetration > config.MaxPenetration) {
      movement += movementPack.Correction;
    } else {
      movement += movementPack.Correction * config.PenetrationCorrection;
    }
  }

  transform->Position += movement;
}

ComputeRawSteer()

스티어링은 캐릭터의 위치, 반경 및 속도에 따라 움직임을 계산하고 필요한 경우 움직임을 보정합니다.

이것은 참조 구현입니다.

ComputeRawSteer는 캐릭터가 현재 수행하고 있는 움직임의 유형에 따라 대부분의 움직임을 계산하는 내부 방법입니다. KCC 예제에서 MoveComputeRawMovement에서 movementPack 값을 요청하여 ComputeRawSteer로 전달합니다.

C#

private void ComputeRawSteer(FrameThreadSafe f, ref CharacterController3DMovement movementPack, FP dt) {

  Grounded = movementPack.Grounded;
  var config = f.FindAsset(Config);
  var minYSpeed = -FP._100;
  var maxYSpeed = FP._100;
  
  switch (movementPack.Type) {
  
    // FreeFall
    case CharacterMovementType.FreeFall:
    
      Velocity.Y -= config._gravityStrength * dt;
      
      if (!config.AirControl || movementPack.Tangent == default(FPVector3)) {
        Velocity.X = FPMath.Lerp(Velocity.X, FP._0, dt * config.Braking);
        Velocity.Z = FPMath.Lerp(Velocity.Z, FP._0, dt * config.Braking);
      } else {
        Velocity += movementPack.Tangent * config.Acceleration * dt;
      }
      
      break;
      
    // Grounded movement
    case CharacterMovementType.Horizontal:
      
      // apply tangent velocity
      Velocity += movementPack.Tangent * config.Acceleration * dt;
      var tangentSpeed = FPVector3.Dot(Velocity, movementPack.Tangent);
      
      // lerp current velocity to tangent
      var tangentVel = tangentSpeed * movementPack.Tangent;
      var lerp = config.Braking * dt;
      Velocity.X = FPMath.Lerp(Velocity.X, tangentVel.X, lerp);
      Velocity.Z = FPMath.Lerp(Velocity.Z, tangentVel.Z, lerp);
      
      // we only lerp the vertical velocity if the character is not jumping in this exact frame,
      // otherwise it will jump with a lower impulse
      if (Jumped == false) {
        Velocity.Y = FPMath.Lerp(Velocity.Y, tangentVel.Y, lerp);
      }
      
      // clamp tangent velocity with max speed
      if (tangentSpeed > MaxSpeed) {
        Velocity -= movementPack.Tangent * (tangentSpeed - MaxSpeed);
      }
      
      break;
      
    // Sliding due to excessively steep slope
    case CharacterMovementType.SlopeFall:
    
      Velocity += movementPack.SlopeTangent * config.Acceleration * dt;
      minYSpeed = -config.MaxSlopeSpeed;
      
      break;
      
    // No movement, only deceleration
    case CharacterMovementType.None:
    
      var lerpFactor = dt * config.Braking;
      
      if (Velocity.X.RawValue != 0) {
        Velocity.X = FPMath.Lerp(Velocity.X, default, lerpFactor);
        if (FPMath.Abs(Velocity.X) < FP.EN1) {
          Velocity.X.RawValue = 0;
        }
      }
      
      if (Velocity.Z.RawValue != 0) {
        Velocity.Z = FPMath.Lerp(Velocity.Z, default, lerpFactor);
        if (FPMath.Abs(Velocity.Z) < FP.EN1) {
          Velocity.Z.RawValue = 0;
        }
      }
      
      // we only lerp the vertical velocity back to 0 if the character is not jumping in this exact frame,
      // otherwise it will jump with a lower impulse
      if (Velocity.Y.RawValue != 0 && Jumped == false) {
        Velocity.Y = FPMath.Lerp(Velocity.Y, default, lerpFactor);
        if (FPMath.Abs(Velocity.Y) < FP.EN1) {
          Velocity.Y.RawValue = 0;
        }
      }
      
      minYSpeed = 0;
      
      break;
  }
  
  // horizontal is clamped elsewhere
  if (movementPack.Type != CharacterMovementType.Horizontal) {
    var h = Velocity.XZ;
    
    if (h.SqrMagnitude > MaxSpeed * MaxSpeed) {
      h = h.Normalized * MaxSpeed;
    }
    
    Velocity.X = h.X;
    Velocity.Y = FPMath.Clamp(Velocity.Y, minYSpeed, maxYSpeed);
    Velocity.Z = h.Y;
  }
  
  // reset jump state
  Jumped = false;
}

충돌 콜백

KCC가 콜라이더와의 교차점을 감지할 때마다 콜백이 트리거 됩니다.

C#

public interface IKCCCallbacks2D
{
    bool OnCharacterCollision2D(FrameBase f, EntityRef character, Physics2D.Hit hit);
    void OnCharacterTrigger2D(FrameBase f, EntityRef character, Physics2D.Hit hit);
}

public interface IKCCCallbacks3D
{
    bool OnCharacterCollision3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit);
    void OnCharacterTrigger3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit);
}

콜백을 수신하고 해당 정보를 사용하려면 시스템에서 해당 IKCCCallbacks 인터페이스를 구현하십시오.

중요 충돌 콜백은 부울 값을 반환합니다. 이렇게 하면 충돌을 무시할지 여부를 결정할 수 있습니다. false를 반환하면 캐릭터가 충돌한 물리적인 물체를 통과하게 됩니다.

콜백을 구현하는 것 외에도 이동 방법은 IKCCCallbacks 객체를 전달해야 합니다. 아래는 충돌 콜백을 사용하는 코드입니다.

C#

public unsafe class SampleSystem : SystemMainThread, IKCCCallbacks3D
{
  public bool OnCharacterCollision3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit) {
    // read the collision information to decide if this should or not be ignored
    return true;
  }

  public void OnCharacterTrigger3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit) {
  }
  
  public override void Update(Frame f) {
    // [...]
    // adding the IKCCCallbacks3D as the last parameter (this system, in this case)
    var movement = CharacterController3D.Move((Entity*)character, input->Direction, this);
    // [...]
}
Back to top