내일배움캠프_언리얼9기

내일배움캠프 59일차 TIL 2026/07/15

begin-play 2026. 7. 15. 22:14

TIL - Unreal Engine Skill System 구조 설계 및 DataTable 기반 스킬 구현

1. ActorComponent를 활용한 기능 분리 설계

학습 내용

언리얼에서는 하나의 Actor 클래스에 모든 기능을 넣기보다 ActorComponent를 활용하여 기능을 분리하는 방식이 중요하다는 것을 학습했다.

기존 방식:

PlayerCharacter
 └─ 스킬 사용 코드
 └─ 투사체 생성 코드
 └─ 데이터 검색 코드
 └─ 쿨타임 관리
 

변경한 구조:

PlayerCharacter
 └─ PlayerSkillComponent
       └─ Skill Data 검색
       └─ Skill 실행 요청

Skill Actor
 └─ 실제 스킬 동작

Projectile Actor
 └─ 투사체 이동
 └─ 충돌 처리
 

핵심 코드 패턴

 
void APlayerCharacter::ActivateSkill(FName SkillID)
{
	if (PlayerSkillComponent)
	{
		PlayerSkillComponent->UseSkillByID(SkillID);
	}
}
 

캐릭터는 "스킬을 어떻게 실행하는지" 알 필요 없이 컴포넌트에게 요청만 한다.

장점:

  • PlayerCharacter 코드가 간결해짐
  • 스킬 시스템 재사용 가능
  • 다른 캐릭터에도 쉽게 추가 가능

2. DataTable 기반 데이터 관리

학습 내용

스킬 정보를 코드에 직접 작성하지 않고 DataTable에서 관리하는 구조를 구현했다.

기존:

 
Damage = 10;
Speed = 500;
Cooldown = 3;
 

변경:

DT_SkillData

SkillName | Damage | Speed | ProjectileClass
---------------------------------------------
FireBall  | 10     | 500   | BP_FireBall
 

코드는 데이터를 읽어서 사용하는 역할만 담당한다.


FindRow를 이용한 데이터 검색

사용한 패턴:

 
const FSkillData* SkillData =
SkillDataTable->FindRow<FSkillData>(
	SkillID,
	TEXT("Skill Search")
);
 

구조:

 
FindRow<찾을 구조체 타입>(
	찾을 Row 이름,
	디버그용 이름
)
 

예:

 
FindRow<FSkillData>("IronBody")
 

→ DT_SkillData에서 IronBody라는 Row 검색


3. DataTable Row Type 오류 해결

발생한 오류:

UDataTable::FindRow :
specified incorrect type for DataTable
 

원인:

DataTable이 가지고 있는 Row 구조체와 코드에서 검색하는 구조체가 다름.

예:

DataTable:

Row Type:
HOTRELOAD_SkillData_0
 

코드:

 
FindRow<FSkillData>()
 

서로 다른 타입이라 검색 실패.

해결 방법:

  • DataTable Row Type 확인
  • 구조체 변경 후 Hot Reload 문제가 발생하면
    • 에디터 재시작
    • DataTable 재생성
    • 구조체 재지정

4. Skill → Projectile 구조 설계

스킬 실행 흐름:

PlayerCharacter
      |
      v
PlayerSkillComponent
      |
      v
SkillData 검색
      |
      v
PlayerProjectileSkillBase
      |
      v
SpawnActor()
      |
      v
PlayerProjectileBase
 

역할 분리:

Skill Actor

담당:

  • 스킬 실행 요청
  • 투사체 생성

Projectile Actor

담당:

  • 이동
  • 충돌
  • 삭제 조건

5. 가상 함수(Virtual Function)를 활용한 확장 구조

부모 클래스:

 
virtual void ExecuteSkill();
 

자식 클래스:

 
override
void ExecuteSkill();
 

구조:

PlayerSkillBase
      |
      |
PlayerProjectileSkillBase
      |
      |
FireBallSkill
DashSkill
ExplosionSkill
 

부모는 공통 인터페이스 제공.

자식은 각자의 방식으로 구현.

장점:

  • 새로운 스킬 추가 시 기존 코드 수정 최소화
  • 다형성 활용 가능

6. Projectile 초기화 방식

생성 후 필요한 데이터를 전달하는 방식 학습.

예:

 
Projectile->InitializeProjectile(Direction);
 

투사체는 생성될 때 기본 상태만 가지고 있고,

스킬에서:

  • 방향
  • 데미지
  • 속도
  • 최대 거리
  • 충돌 타입

등을 전달한다.

구조:

SpawnActor()
      |
      v
InitializeProjectile()
      |
      v
Movement 시작
 

7. Collision 문제 해결 경험

발생 문제:

EXCEPTION_STACK_OVERFLOW
Chaos Collision
 

원인:

몬스터 Static Mesh Collision이 Block 상태라서 예상하지 않은 충돌 발생.

해결:

Static Mesh Collision:

Collision Enabled
↓
No Collision
 

중요한 점:

언리얼에서는 Mesh Collision과 Component Collision이 별도로 존재하기 때문에 둘 다 확인해야 한다.


8. Monster Skill 시스템 설계 방향

몬스터 스킬은 AI와 분리하는 방향으로 설계.

목표 구조:

Monster
 |
 +-- BossSkillManagerComponent
          |
          |
          +-- FireBallSkillComponent
          +-- DashSkillComponent
          +-- SlamSkillComponent
 

역할:

AI

담당:

  • 언제 사용할지 결정
  • 공격 판단
  • Behavior Tree 관리

Skill Component

담당:

  • 실제 공격 실행

즉:

AI
 |
"FireBall 사용"
 |
Skill Component 실행
 

9. 오늘 배운 핵심 설계 원칙

1) 데이터와 로직 분리

DataTable:

수치 관리
 

C++:

동작 관리
 

2) 책임 분리

좋은 구조:

Character
→ 요청

Component
→ 관리

Actor
→ 실행
 

3) 확장 가능한 구조 만들기

새로운 스킬 추가:

기존 방식:

PlayerCharacter 수정
PlayerSkillComponent 수정
Projectile 수정
 

개선 방식:

새 Skill 클래스 추가
DataTable Row 추가
 

오늘의 정리

오늘은 단순히 "투사체를 발사하는 기능"을 만든 것이 아니라,

DataTable 기반 데이터 관리 + Component 기반 기능 분리 + 상속과 가상 함수 활용을 통해 확장 가능한 스킬 시스템 구조를 설계하는 방법을 학습했다.