스킬마다 다른 충돌 확인 타입을 지정하기
개별 스킬마다 (TankerQ, TankerW, HealerQ, HealerW, ….) 스킬 RPC를 만드는 대신, GOSkillBase에서 공통적인 동작을 하도록 추상화할 수 있을 것이고, 재활용성도 높을 것이다.
스킬 실행과 관련된 데이터는 UGOSkillBase
를 통해 관리하고, AGOPlayerCharacter
는 스킬 실행을 요청하는 역할을 할 수 있다.
enum class ESkillCollisionType : uint8
{
LineTraceSingle UMETA(DisplayName = "Line Trace Single"),
LineTraceMulti UMETA(DisplayName = "Line Trace Multi"),
SweepSingle UMETA(DisplayName = "Sweep Single"),
SweepMulti UMETA(DisplayName = "Sweep Multi"),
OverlapMulti UMETA(DisplayName = "Overlap Multi")
};
위와 같이 나눈 여러 스킬 충돌 유형에 따라 처리할 수 있도록,
충돌에 대한 구조체를 담는 구조체를 생성하여 애니메이션 노티파이 시 이 충돌 타입에 따라서 처리하도록 설정했다.
USTRUCT()
struct FGOOutHitCollisionStructure {
GENERATED_BODY()
FHitResult OutHitResult;
TArray<FHitResult> OutHitResults;
TArray<FOverlapResult> OutOverlaps;
};
그리고 모든 스킬이 상속받는 UObject인 UGOSkillBase에서, 아래와 같은 함수를 특정 지점에서 호출하도록 해준다.
void UGOSkillBase::ExecuteSkill(ESkillCollisionType SkillCollisionType)
{
switch (SkillCollisionType)
{
case ESkillCollisionType::LineTraceSingle:
PerformLineTraceSingle(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResult);
break;
case ESkillCollisionType::LineTraceMulti:
PerformLineTraceMulti(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResults);
break;
case ESkillCollisionType::SweepSingle:
PerformSweepSingle(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResult);
break;
case ESkillCollisionType::SweepMulti:
PerformSweepMulti(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResults);
break;
case ESkillCollisionType::OverlapMulti:
PerformOverlapMulti(GetTotalSkillStat(), OutHitCollisionStruct.OutOverlaps);
break;
}
}
void UGOSkillBase::PerformLineTraceSingle(const FGOSkillStat& Stats, FHitResult& OutHitResult)
{
if (!SkillOwnerCharacter) return;
AGOCharacterBase* OwnerActor = Cast<AGOCharacterBase>(SkillOwnerCharacter);
FVector Forward = OwnerActor->GetActorForwardVector();
FVector Start = OwnerActor->GetActorLocation() + Forward * OwnerActor->GetCapsuleComponent()->GetScaledCapsuleRadius();
FVector End = Start + Forward * Stats.DamageRange;
//FHitResult HitResult; // 원래 이렇게 하는 방식 대신에 Out 파라미터를 사용
FCollisionQueryParams CollisionParams;
CollisionParams.AddIgnoredActor(OwnerActor); // 스킬 사용자는 무시
HitDetected = OwnerActor->GetWorld()->LineTraceSingleByChannel(OutHitResult, Start, End, CCHANNEL_GOACTION, CollisionParams);
...
}
앞서 기재한 애니메이션 노티파이 시 스킬의 타입에 따라 처리가 되도록 아래와 같이 함수를 작성했다.
void AGOPlayerCharacter::SkillAttackHitCheck()
{
// 소유권을 가진 클라이언트가 공격 판정을 진행합니다.
if (IsLocallyControlled())
{
GO_LOG(LogGONetwork, Log, TEXT("%s"), TEXT("Begin"));
TObjectPtr<UGOSkillBase> CurrentSkill = GetSkillCastComponent()->GetCurrentSkill();
bool HitDetected = CurrentSkill->GetHitDetected();
UE_LOG(LogTemp, Warning, TEXT("CurrentSkill Hit: %s"), *CurrentSkill->GetName());
FGOOutHitCollisionStructure& OutHitCollisionStructure = CurrentSkill->GetOutHitCollisionStructure();
FHitResult OutHitResult = OutHitCollisionStructure.OutHitResult;
TArray<FHitResult> OutHitResults = OutHitCollisionStructure.OutHitResults;
TArray<FOverlapResult> OutOverlaps = OutHitCollisionStructure.OutOverlaps;
ESkillCollisionType CurrentSkillCollisionType = CurrentSkill->GetSkillCollisionType();
float SkillDamage = CurrentSkill->GetTotalSkillStat().DamageMultiplier * Stat->GetTotalStat().BaseDamage;
float HitCheckTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
if (!HasAuthority())
{
if (HitDetected)
{
AActor* HitActor = nullptr;
switch (CurrentSkillCollisionType)
{
case ESkillCollisionType::LineTraceSingle:
HitActor = OutHitCollisionStructure.OutHitResult.GetActor();
if (IsValid(HitActor)) {
ServerRPCNotifySkillHitTest(OutHitResult, SkillDamage);
}
break;
...
스킬 타입을 고려
SkillDataTable → SkillAffectType 에 따라서 스킬 적용이 달라지도록 해주기
UENUM(BlueprintType)
enum class ESkillAffectType : uint8
{
None UMETA(Hidden),
Damage UMETA(DisplayName = "Damage"),
Defense UMETA(DisplayName = "Defense"),
Heal UMETA(DisplayName = "Heal"),
Buff UMETA(DisplayName = "Buff"),
Debuff UMETA(DisplayName = "Debuff"),
Max UMETA(Hidden)
};
궤적 및 Cosmetic 시각화
진행 중