Post

2026-04-08 TIL (32일차)

2026-04-08 TIL (32일차)

Unreal

언리얼의 GameMode = 내가 알고 있는 GameManager

  • 기능 1: “현재 플레이할 캐릭터를 세팅하고 받아온다.” -> 언리얼 GameMode: DefaultPawnClass 속성을 통해 이번 판에 유저가 조종할 몸체(캐릭터)가 무엇인지 결정하고 스폰을 관리합니다.
  • 기능 2: “플레이어의 컨트롤 방식을 설정하고 관리한다.” -> 언리얼 GameMode: PlayerControllerClass 속성을 통해 플레이어의 키보드/마우스 입력을 처리할 두뇌(컨트롤러)를 지정하고 할당해 줍니다.


• 입력 함수를 ‘눌렀을 때’와 ‘뗐을 때’로 나누는 이유

입력에서 ‘눌렀을 때(Pressed/Started)’와 ‘뗐을 때(Released/Completed)’ 함수를 따로 만드는 이유는 캐릭터의 ‘상태(State)’와 ‘액션의 지속 시간’을 제어하기 위해서입니다.

단순해 보이는 입력이라도 이 두 가지를 분리하면 게임의 조작감이 디테일해집니다.


1. 상태의 지속과 해제 (Toggle State)

가장 직관적인 이유입니다. 키를 누르고 있는 동안에만 특정 상태를 유지해야 할 때 사용합니다.

입력 상태대쉬/달리기(Sprint) 처리 로직
눌렀을 때MaxWalkSpeed를 1000으로 올리고, bIsSprinting = true로 상태를 바꿉니다.
뗐을 때MaxWalkSpeed를 다시 500으로 내리고, bIsSprinting = false로 바꿉니다.

‘눌렀을 때’ 함수만 있다면 한 번 달리기 시작한 캐릭터는 게임이 끝날 때까지 멈추지 못하고 달려야 합니다!


가변적인 점프 높이 제어 (Variable Jump Height)

슈퍼 마리오나 일반적인 플랫폼 게임을 생각해보세요. 점프 키를 짧게 누르면 낮게 뛰고, 길게 누르고 있으면 높게 뜁니다.

언리얼 점프 로직동작 방식
눌렀을 때 (Jump)캐릭터에게 위쪽으로 상승하는 속도(Z Velocity)를 강제로 부여합니다.
뗐을 때 (StopJumping)최고점에 도달하지 않았더라도, 상승하는 속도를 그 즉시 깎아버립니다. (더 이상 올라가지 않고 중력의 영향을 강하게 받아 떨어지기 시작)
  • 이 두 함수가 짝을 이뤄야 유저가 원하는 타이밍에 점프를 끊을 수 있는 조작감이 완성됩니다.


3. 차징 시스템과 액션 제어

기를 모아서 때리거나, 활을 쏘는 액션에 필수적입니다.

차징 공격 로직동작 방식
눌렀을 때bIsCharging = true로 만들고 타이머를 돌려 에너지를 모으기 시작합니다. (활시위를 당기는 애니메이션 재생)
뗐을 때모인 에너지의 양(시간)을 계산해서 그에 맞는 데미지의 화살을 발사합니다.


• MaxWalkSpeed

CharacterMovementComponent에는 MaxWalkSpeed라는 속성이 있습니다. 이 MaxWalkSpeed 값을 변경하면, 캐릭터의 최대 이동 속도가 즉시 바뀝니다.

• Apply Additive

애니메이션 블루프린트를 다루다 보면 반드시 마주치게 되는 마법 같은 노드입니다. ‘Apply Additive(애디티브 적용)’ 노드는 이름 그대로 “기본 동작(Base) 위에 추가 동작(Additive)을 수학적으로 더해주는 기능”을 합니다.

Apply Additive 노드의 구조

노드를 꺼내보면 크게 3개의 핀이 있습니다.

핀 (Pin)역할
Base (기본 포즈)뼈대가 되는 전체 몸의 기본 동작입니다. (예: 뛰기 애니메이션)
Additive (애디티브 포즈)얹고 싶은 추가 동작입니다. (예: 총을 조준하는 상체 동작, 피격 움찔)
Alpha (알파)이 토핑을 얼마나 강하게 얹을 것인가? 결정하는 수치입니다. (0.0 ~ 1.0)

Alpha 값에 따른 변화

  • 0.0: 추가 동작을 하나도 적용하지 않음 (그냥 뜀)
  • 0.5: 추가 동작을 절반만 섞음
  • 1.0: 추가 동작을 100% 반영함 (뛰면서 완벽하게 총을 조준함)

br>

• Control Rig: ‘Set Initial Transforms From Mesh’


차이: “나의 시작점은 어디인가?”

Control Rig 안에서 뼈를 움직이기 위해 수학 계산을 할 때, 엔진은 종종 “이 뼈의 원래 위치(Initial Transform)가 어디야?”라고 묻습니다. 이때 이 체크박스가 ‘원래 위치’의 기준을 완전히 바꿔버립니다.

옵션 상태‘원래 위치’의 기준점비유
체크 해제 (기본값)캐릭터를 처음 임포트했을 때의 뻣뻣한 T-포즈 (또는 A-포즈)“나는 멈춰있는 마네킹이야.”
체크 시T-포즈가 아니라, 컨트롤 리그 노드 바로 앞까지 재생되어 들어온 ‘지금 현재 애니메이션 포즈’“나는 지금 뛰고(걷고) 있는 중이야.”


안 켰을 때 발생하는 현상

캐릭터가 ‘쭈구려 앉아서(Crouch) 걷는 애니메이션’을 재생 중이고, 계단 높낮이에 맞춰 발목을 꺾어주는 Foot IK를 Control Rig로 구현했다고 가정해 봅시다.

  • ❌ 체크를 안 했다면? (문제 발생)
    • 컨트롤 리그가 발 IK 계산을 시작하며 “내 발의 원래 위치가 어디지?” 묻습니다. 엔진은 서 있는 T-포즈의 발 위치를 알려줍니다.
    • 캐릭터는 분명 쭈구려 앉아 있는데 발은 서 있는 위치로 계산되니, 다리가 기괴하게 늘어나거나 뼈가 우주로 날아가 버립니다.
  • ✅ 체크를 했다면? (정상 작동)
    • 컨트롤 리그가 쭈구려 앉아있는 현재 애니메이션의 발 위치를 정확히 기준점으로 잡습니다.
    • 거기서부터 계단 높이만큼만 살짝 발을 들어 올리기 때문에 아주 자연스러운 IK가 완성됩니다.


알고리즘


• Map

  • 형태: [Key, Value] 쌍으로 저장합니다.
  • 특징: 내부가 이진 탐색 트리(Red-Black Tree) 구조로 되어 있어, 데이터를 넣을 때마다 Key를 기준으로 자동으로 오름차순 정렬됩니다.
  • 속도: 탐색 속도는 $O(\log N)$ 입니다.


카운팅 패턴

1
2
3
4
map<int, int> countMap;
for (int n : numbers) {
    countMap[n]++;  // 이 한 줄이 전부!
}

동작 원리

  • 처음 등장할 때: countMap[n]을 찾았을 때 없는 키라면? -> 자동으로 0으로 생성한 뒤, ++를 만나 1이 됩니다.
  • 이미 있을 때: 기존 값을 가져와서 ++ 연산으로 값을 1 증가시킵니다.


[] 자동생성

1
2
3
4
5
6
7
map<string, int> m;
m["apple"] = 3;
m["banana"] = 5;
// 현재 m.size() == 2

cout << m["grape"]; // 화면에는 0이 출력됨. "음, 없으니까 0이구나" 생각하지만
cout << m.size();   // 현재 m.size() == 3 

없는 키를 []로 읽기만 해도 기본값(int의 경우 0)으로 자동 생성됩니다 의도치 않게 map의 크기가 늘어나는 가장 흔한 원인입니다.
데이터가 있는지 확인만 하고 싶을 때는 [] 대신 반드시 find()나 count()를 사용해야 합니다.


insert()

  • [] 연산자와 완전히 반대되는 성질을 가진 것이 바로 insert() 함수입니다.
  • m.insert({“key”, value}); : 만약 기존에 똑같은 키가 이미 존재한다면, 새로 넣으려는 값을 무시하고 기존 데이터를 보존합니다.
1
2
3
map<string, int> m;
m.insert({"apple", 10}); // 정상적으로 삽입 완료
m.insert({"apple", 99}); // 이미 "apple" 키가 있으므로, 99는 조용히 무시됨! (기존 값 10 유지)


map의 count()

map에서 특정 Key가 존재하는지 ‘확인만’ 하고 싶을 때 사용하는 함수입니다.


기능 및 동작 원리

count(key) 함수는 맵 안에 해당 Key를 가진 데이터가 몇 개 있는지 그 개수를 반환합니다.

  • 핵심 특징: std::map은 태생적으로 중복 Key를 허용하지 않는 컨테이너입니다.
  • 결과: 따라서 count()의 반환값은 무조건 1 (존재함) 아니면 0 (존재하지 않음) 둘 중 하나만 나옵니다. 사실상 true/false를 반환하는 bool 함수처럼 쓰입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
map<string, int> m;
m["apple"] = 3;

// ❌ 나쁜 예 (자동 생성 함정 발동!)
if (m["grape"] > 0) { 
    // "grape"가 없었지만, 이 순간 값이 0인 "grape"가 맵에 강제 생성됨.
}

// 🟢 좋은 예 (count 활용)
if (m.count("grape") == 1) { // 혹은 if (m.count("grape"))
    cout << "포도가 존재합니다! 값: " << m["grape"];
} else {
    cout << "포도는 맵에 아예 없습니다! (자동 생성도 안 됨)";
}


count() vs find() 비교

Key의 존재 여부를 확인할 때 쓰이는 두 가지 대표적인 함수입니다. 상황에 맞게 골라 쓰면 됩니다.

함수반환값특징 및 추천 용도
count(key)1 또는 0 (개수)단순히 “이 데이터가 맵에 있어? 없어?” (존재 여부)만 빠르게 묻고 싶을 때 직관적이고 편함.
find(key)iterator (반복자/위치)데이터가 있는지 확인한 다음, 그 데이터의 Value값까지 이어서 바로 조작/출력해야 할 때 성능상 더 유리함. (못 찾으면 map.end() 반환)


• Set

  • 형태: Value 없이 [Key] 하나만 저장합니다.
  • 특징: map에서 Value만 빼버린 형태입니다. 중복을 허용하지 않으며, 넣으면 자동으로 정렬됩니다. 주로 “이 데이터가 여기 있나?” (존재 여부)만 빠르게 확인할 때 씁니다.
  • 속도: 탐색 속도는 $O(\log N)$ 입니다.


• unordered_map

  • 형태: [Key, Value] 쌍으로 저장합니다.
  • 특징: 내부가 해시 테이블(Hash Table) 구조입니다. 정렬을 아예 포기한 대신, 탐색 속도에 모든 것을 걸었습니다. * 속도: 평균 탐색 속도는 $O(1)$ 로 압도적으로 빠릅니다.
This post is licensed under CC BY 4.0 by the author.