동작에 맞는 콜리전 및 Hit 관련 정보 매핑 구조
- 현재 키 입력에 따라 시행되는 동작은 Enum 타입으로 관리 중
- Enum 타입의 문자열과 Data Table의 RowName이 일치하도록 작성
- CharacterType + Enum 타입 문자열 형태로 알맞은 RowName 형성
- RowName으로 각 데이터 테이블에서 공격 범위에 관련된 데이터 구조체 AttackCollision, 피격 효과에 관련된 데이터 구조체 HitBoxData를 로드
- 이 과정에서 함께 로드된 두 정보가 매핑된 형태여야 함
- TMap 또는 TArray를 2개 쓰는 방식 중 선택
- TMap의 경우 작성자 입장에서 간편하게 사용할 수 있겠지만 성능에 민감한 게임에서 동작이 시행될때마다 피격 관련 정보를 찾기 위해 시간복잡도 logN만큼의 시간을 쓰는 것은 굉장한 낭비로 보임
- 결과적으로 TArray 2개를 이용하되 같은 인덱스에 있는 정보들이 서로 매핑된 구조로 사용할 수 있도록 구성
- 이전에 이미 Enum 값을 기준으로 TArray에 콜리전을 저장해둔 구조였기 때문에 AttackCollision을 로드하는 과정에서 함께 HitBoxData를 로드해서 다른 TArray의 같은 인덱스에 관련 정보를 저장하는 형태로 설계
변수 선언
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox/Collision")
int32 CurrentActivatedCollision;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox/Collision")
TArray<UShapeComponent*> AttackCollisions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HitBox/Data")
TArray<FHitBoxData> HitBoxList;
- CurrentActivatedCollision의 경우 현재 활성화된 콜리전의 인덱스를 나타내는 변수
ㅡ> 이전 포스팅(#9)에서 ApplyDamage, TakeDamage를 구현하는 과정에서 HitBoxData 를 함께 전달하도록 사용자 정의 함수를 새로 만들었는데, 이때 알맞은 HitBoxData를 전달하기 위해선 어떤 콜리전이 활성화 되었는지 알아야 하므로, 콜리전을 활성화시킬 때, CurrentActivatedCollision 값을 활성화된 콜리전의 인덱스로 바꾸어 HitBoxList에서 알맞은 값을 골라 매개변수로 전달할 수 있음 - AttackCollisions는 동작에 맞는 콜리전들이 저장된 배열
- HitBoxList는 동작에 맞는 콜리전들에 피격되었을 때, 넉백 거리나 데미지 등의 피격 관련 정보(HitBoxData)가 저장된 배열
PreLoadAttackCollisions 수정
void ABaseCharacter::PreLoadAttackCollisions()
{
if (UGameInstance* GameInstance=GetGameInstance())
{
if (UDataLoaderSubSystem* Loader=GameInstance->GetSubsystem<UDataLoaderSubSystem>())
{
if (UEnum* Type=StaticEnum<EAttackType>())
{
const int32 n=Type->NumEnums();
AttackCollisions.SetNum(n-1);
HitBoxList.SetNum(n-1);
for (int32 i=0;i<n-1;i++)
{
FString TypeName=Type->GetNameStringByIndex(i);
UE_LOG(LogTemp,Warning,TEXT("Current RowName: %s, Current Index: %d"),*TypeName,i);
const FName RowName=FName(CharacterType+"_"+TypeName);
FAttackCollisionData Data=Loader->InitializeAttackCollisionData(RowName);
FHitBoxData HitBoxData=Loader->InitializeHitBoxData(RowName);
if (Data.Scale!=FVector::ZeroVector)
{
//Create Collision and Attacth to mesh
UShapeComponent* AttackCollision = NewObject<UShapeComponent>(this,USphereComponent::StaticClass());
AttackCollision->SetupAttachment(GetMesh());
AttackCollision->SetRelativeLocation(Data.Location);
AttackCollision->SetRelativeScale3D(Data.Scale);
AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//Bind Overlap Event
AttackCollision->OnComponentBeginOverlap.AddDynamic(this,&ABaseCharacter::OnAttackOverlap);
//Activate Components
AddInstanceComponent(AttackCollision);
AttackCollision->RegisterComponent();
AttackCollisions[i]=AttackCollision;
HitBoxList[i]=HitBoxData;
}
}
}
}
}
}
- Enum 값에 따라 알맞은 AttackCollision 값을 로드할 때, HitBoxData를 함께 로드한 후 같은 인덱스로 배열에 저장하도록 수정
AttackNotify 수정
void ABaseCharacter::AttackNotify(const FName NotifyName, const FBranchingPointNotifyPayload& Payload)
{
if (!NotifyName.IsValid()||AttackCollisions.IsEmpty()) return;
// Determin the type of attack by name
FString NotifyNameString=NotifyName.ToString();
// Hitbox
if(NotifyNameString.Contains(TEXT("Hitbox")))
{
TCHAR LastChar = NotifyNameString[NotifyNameString.Len() - 1];
int32 AttackNumber = FCString::Atoi(&LastChar)-1;
//UE_LOG(LogTemp, Log, TEXT("Parsed Attack Number: %d"), AttackNumber);
// Activate Collision in proper location
if (AttackCollisions[AttackNumber])
{
// Deactivate Collision
if (NotifyNameString.Contains(TEXT("End")))
{
CurrentActivatedCollision=-1;
DeactivateAttackCollision(AttackNumber);
UE_LOG(LogTemp,Warning,TEXT("Deactivate Collision! (Index: %d)"),AttackNumber);
}
else // Activate Collision
{
CurrentActivatedCollision=AttackNumber;
AttackCollisions[AttackNumber]->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
UE_LOG(LogTemp,Warning,TEXT("Activate Collision! (Index: %d)"),AttackNumber);
}
}
else
{
UE_LOG(LogTemp,Warning,TEXT("Activate Failed!"));
}
return;
}
}
- 콜리전을 활성화해야 할때는 CurrentActivatedCollision의 값을 현재 활성화된 콜리전의 인덱스로 변경
- 비활성화 할때는 CurrentActivatedCollision의 값을 -1로 변경하여 아무것도 활성화되지 않았음을 명시
OnAttackOverlap 수정
void ABaseCharacter::OnAttackOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// Except self
if (!OtherActor || OtherActor == this) return;
float DamageAmount=BalanceStats.DamageModifier*HitBoxList[CurrentActivatedCollision].Damage;
UE_LOG(LogTemp,Warning,TEXT("Damage: %f, Overlapped Actor: %s"),DamageAmount,*OtherActor->GetName());
UDamageHelper::ApplyDamage(
OtherActor, // 피해 대상
DamageAmount, // 공격력 (Character Stats 기반)
GetController(), // 공격한 플레이어의 컨트롤러
this, // 공격한 액터 (자기 자신)
UDamageType::StaticClass(),// 기본 데미지 타입)
HitBoxList[CurrentActivatedCollision]
);
}
- 데미지 계산할 때, HitBoxList에서 현재 활성화된 콜리전의 인덱스인 CurrentActivatedCollision 값을 이용하여 값 특정
참고자료
'언리얼엔진(UE) > 프로젝트' 카테고리의 다른 글
미친 선인장과 분노의 버섯 - #12 네트워크 환경에서의 동기화 및 UX(사용자 경험) 개선 (0) | 2025.04.10 |
---|---|
미친 선인장과 분노의 버섯 - #11 싱글 플레이 -> 멀티 플레이 전환 (네트워크 기반 설계) (0) | 2025.04.09 |
미친 선인장과 분노의 버섯 - #9 격투게임을 위한 사용자 정의 ApplyDamage(Helper class), TakeDamage(Interface) 구현 (0) | 2025.04.07 |
[Project] 미친 선인장과 분노의 버섯 - #8 애니메이션을 이용한 공격 판정 로직 (0) | 2025.04.04 |
[Project] 미친 선인장과 분노의 버섯 - #7 공격시 콜리전 생성 방식 결정 + 캐릭터별 알맞은 데이터 로딩 로직 (0) | 2025.04.03 |