Unreal - Hash 기반 AI/Enemy 데이터 설계하기

2025. 8. 23. 04:24·Unreal 프로젝트 다이어리/두번째 프로젝트

rpg같은 게임에는 수많은 적, ai가 존재합니다

만약 AI한마리한마리 직접 코딩한다면 코드가 어마어마하게 많아지고 유지보수가 힘듭니다

Hash알고리즘을 사용하여 수많은 적, 데이터 를 관리할수있습니다

그래서 추가, 및 삭제 즉 유지보수가 편한 구조가 필요합니다

 

제가 선택한 방식은 데이터테이블의 Key : 행 Value : 값

을 사용한 Hash알고리즘으로 구현하였습니다

 

데이터테이블에서 값을 설정하고

Base가되는 Enemy에서 데이터테이블의 값을 인식한뒤

그값으로 동기화를 해주는방식으로 구현하였습니다

 

데이터테이블의 값이 될 구조체를 선언해주었습니다


#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "EnemyDataTable.generated.h"

USTRUCT(BlueprintType)
struct FEnemyDataTable : public FTableRowBase
{
    GENERATED_BODY()

public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    int32 enemyId;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    FName name;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    int32 teamIdNumber;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    USkeletalMesh* skeletalMesh;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    TSubclassOf<UAnimInstance> animBP;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    int32 maxHP;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    float maxPosture;
};

 

해당 구조체로 데이터테이블을 만들어 EnemyId를 1 

그리고 사용할 변수값 및 스켈레탈메시나 애니메이션 블루프린트를 설정해주었습니다

 

애니메이션 블루프린트는 캐릭터의 speed와 방향을 구하는 공식을 사용하였습니다

UCLASS()
class PORTFOLIOMS_API UEnemyBaseAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
public:

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Movement")
	float speed;

	// 이동 방향
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Movement")
	float direction;

	virtual void NativeUpdateAnimation(float DeltaSeconds) override;
};
#include "EnemyBaseAnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "KismetAnimationLibrary.h"

void UEnemyBaseAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    APawn* owningPawn = TryGetPawnOwner();
    if (!owningPawn) return;

    FVector velocity = owningPawn->GetVelocity();
    velocity.Z = 0.f; // 점프 높이 같은 Z값 제외

    // 속력 크기
    speed = velocity.Size();

    FVector forwardVec = owningPawn->GetActorForwardVector();
    FVector rightVec = owningPawn->GetActorRightVector();

    velocity.Normalize();

    float forwardDot = FVector::DotProduct(forwardVec, velocity);
    float rightDot = FVector::DotProduct(rightVec, velocity);
    float angle = FMath::RadiansToDegrees(FMath::Acos(forwardDot));

    if (rightDot < 0)
    {
        angle *= -1.f;
    }
    //-180 ~ 180
    direction = angle;
}

 

해당 애님인스턴스에 StateMachine내에서 구한 변수값으로 블렌드스페이스를 연동해주었습니다

 

캐릭터에서 uproperty로 선언해서 할당해줄 EnemyDataTable을 선언해주고

키값을 분류할 enemyId를 선언해줍니다

virtual void OnConstruction(const FTransform& Transform) override;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EnemyData")
UDataTable* enemyDataTable;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EnemyData")
int32 enemyId;

 

적의 베이스가 될 baseCharacter c++ 캐릭터를 만들어주고 OnConstruction함수로 데이터테이블의

키값이되는 id를 찾아 할당해줍니다

 

UPROPERTY에 할당해줄 ID넘버와 데이터테이블을 할당해줍니다 해당 적 블루프린트의 ID는 1번

 

행을 추가한뒤 EnemyId를 1번으로 설정

 

OnConstruction은 언리얼 엔진에서 Actor가 생성될 때 호출되는 함수 입니다.

해당 함수를 사용하여 데이터테이블에 있는 스켈레탈메시, 애님블루프린트를 바꾸는 구조를 설계하였습니다

void AEnemyBaseCharacter::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);
	if (!enemyDataTable) return;

    FName rowName = FName(*FString::FromInt(enemyId));

	TArray<FEnemyDataTable*> enemyDataTableAllRows;
	enemyDataTable->GetAllRows<FEnemyDataTable>(TEXT("OnConstruction"), enemyDataTableAllRows);

	for (FEnemyDataTable* row : enemyDataTableAllRows)
	{

		if (!row) return;

		if (row->enemyId == enemyId)
		{
			GetMesh()->SetSkeletalMesh(row->skeletalMesh);
			if (row->animBP)
			{
				GetMesh()->SetAnimInstanceClass(row->animBP);
			}
		}
		break;
	}

}

 

 

ID를 1번으로 할당한 BaseCharacter 를 종속받은 블루프린트는 꺼내면 데이터테이블에 할당된 캐릭터

스켈레탈메시와 블루프린트로 설정이 됩니다

 

또한 무기마다 사용할 소켓을 만들어준뒤 위치를 잡아줍니다

 

또한 무기를 담당할 데이터테이블또한 만들어주었습니다


#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "WeaponDataTable.generated.h"

USTRUCT(BlueprintType)
struct FWeaponDataTable : public FTableRowBase
{
    GENERATED_BODY()

public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    int32 weaponId;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    FName name;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    TSubclassOf<AActor> weaponBP;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseData")
    FString attachSocket;
};

 

 

Onconstruction함수에 무기 데이터테이블에있는 원하는 BP를 원하는 소켓 위치에 Attach되도록 설계하였습니다

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EnemyData")
int32 weaponId;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon")
UChildActorComponent* swordActorComp = nullptr;
//weapon data
TArray<FWeaponDataTable*> weaponDataTableAllRow;
weaponDataTable->GetAllRows<FWeaponDataTable>(TEXT("OnConstruction"), weaponDataTableAllRow);

for (FWeaponDataTable* row : weaponDataTableAllRow)
{
	if (!row) return;

	if (row->weaponId == weaponId)
	{
		if (!row->weaponBP) break;

		// 기존 ChildActorComponent가 있으면 제거
		if (swordActorComp)
		{
			swordActorComp->DestroyComponent();
			swordActorComp = nullptr;
		}

		swordActorComp = NewObject<UChildActorComponent>(this, UChildActorComponent::StaticClass(), 
			TEXT("SwordActorComp"));
		swordActorComp->RegisterComponent();
		swordActorComp->SetChildActorClass(row->weaponBP);

		// attachSocket 위치에 Attach
		FName socketName(*row->attachSocket);
		swordActorComp->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, socketName);

		break;
	}
}

 

무기를 들고있을때와 안들고있을때의 블렌드스페이스를 별도로 만들어 스테이트머신에서 변수로 제어했습니다

 

각 블렌드스페이스에는 이렇게 별도의 8방향 블렌드스페이스가 들어있습니다

 

 

결과물

1번의 캐릭터에셋과 1번의 무기 에셋을 적용해주겠습니다

 

1번의 캐릭터에셋입니다

 

1번의 무기 에셋입니다

 

캐릭터 uproperty에서의 enemyId와 WeaponId를 데이터테이블에 존재하는 원하는 id 숫자로 입력해줍니다

 

데이터테이블에서 설정한 값들이 적한테 바로 적용되는것을 확인할수있습니다

 

'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글

Unreal - Posture Progress / Hit Direction  (0) 2025.08.29
Unreal - HitInterface 상호작용  (0) 2025.08.23
Unreal - 콤보 공격  (0) 2025.08.22
Unreal - 무기 Draw  (0) 2025.08.19
Unreal - Layered Per Bone (상 하체 애니메이션 분리)  (0) 2025.08.17
'Unreal 프로젝트 다이어리/두번째 프로젝트' 카테고리의 다른 글
  • Unreal - Posture Progress / Hit Direction
  • Unreal - HitInterface 상호작용
  • Unreal - 콤보 공격
  • Unreal - 무기 Draw
lucodev
lucodev
커피와 노트북 그리고 개발
  • lucodev
    루코 개발테이블
    lucodev
  • 전체
    오늘
    어제
    • 분류 전체보기 (209) N
      • Unreal 프로젝트 다이어리 (106) N
        • 첫번째 프로젝트 (73)
        • 두번째 프로젝트 (33) N
      • Unreal 팁 (8)
      • Unreal 디버깅 (8)
      • C++ 프로그래머스 (52)
        • Stack,Queue (7)
        • Hash (4)
        • Heap (2)
        • Sort (5)
        • Exhaustive search (5)
        • Greedy (2)
        • BFS , DFS (7)
        • Graph (2)
        • Dynamic Programming (1)
        • C++ Math (2)
        • 기타 문제 (14)
      • C++ 백준 (4)
      • C++ 팁 (1)
      • 개인 코테 & 스타디 <비공개> (29)
        • 코드 개인보관함 (9)
        • 코딩테스트+@ (11)
        • 알고리즘 스타디 (6)
        • 알고리즘 스타디 과제 (3)
        • 비공개 (0)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 링크

  • 공지사항

  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 태그

    unreal inventory
    언리얼
    언리얼 ui
    언리얼 모션매칭
    unreal 파쿠르
    언리얼 컷씬
    언리얼 behaviortree
    언리얼 파쿠르
    언리얼 behavior tree
    Unreal Parkour
    언리얼 상호작용
    언리얼 motionmatching
    unreal 인벤토리
    언리얼 parkour
    언리얼 시퀀스
    언리얼 프로그래스바
    unreal 시퀀스
    언리얼 비헤이비어트리
    언리얼 인벤토리
    unreal 모션매칭
  • hELLO· Designed By정상우.v4.10.3
lucodev
Unreal - Hash 기반 AI/Enemy 데이터 설계하기
상단으로

티스토리툴바