언리얼엔진5 GAS - 기본 공격 판정방법 변경

조창근·2024년 7월 13일
0

언리얼엔진5 GAS

목록 보기
6/8

어떤 방법으로 충돌판정을 하는게 좋을까?

내가 생각한 충돌처리 방법

  1. 칼에 캡슐컬리젼을 추가하여 컬리젼오버랩으로 충돌처리 판별하기
  2. 공격 방향에 캡슐 컬리전을 월드에 소환하여 충돌처리 판별하기
  3. 칼 이동 위치에 라인트레이스를 남겨 충돌처리 판별하기

각 충돌처리의 장단점

1. Capsule Collision을 이용하여 칼에 캡슐 컬리젼을 붙이기

장점: 칼에 캡슐 콜리젼을 붙이므로 공격 애니메이션에 맞춰 칼이 적 객체에 충돌할 때 충돌처리 판정을 진행합니다.

단점: BeginOverlap으로 충돌처리 판정을 진행하므로 충돌이 됐다는 사실은 알 수 있지만 충돌된 위치는 알 수 없습니다.
->충돌처리 된 Location이 (0,0,0)인것을 확인해 볼 수 있다.

2. 공격 방향에 캡슐 컬리젼 소환

장점: 공격 방향에 맞춰 내 앞에 캡슐컬리젼을 소환하므로 구현이 쉽고 충돌처리 판정이 비교적 넓은 범위에 되어서 판정처리가 후합니다.

단점: 위에서 말한 장점이 단점이 되는데 판정처리가 후하다는 점이 섬세한 충돌처리를 요구하는 소울류 게임에는 어울리지 않는다.

3. AnimNotify를 이용하여 칼 이동 위치에 라인트레이스를 남기기

장점: 틱마다 라인트레이스를 남기기 때문에 1번과 비슷하게 적 객채에 충돌할 때 충돌처리 판정을 진행할 수 있으며 충돌처리 된 위치를 알 수 있습니다.

단점: 게임프레임이 떨어지면 틱마다 라인트레이스를 남기는 숫자가 줄어들어 정확한 판정이 어려울 수 있습니다.

충돌처리 방법 선택

충돌처리에 꼭 필요한 요소

  1. 정확한 판정

    소울류 게임은 정확한 판정으로 공격을 진행할 때 공격범위에 대해서 신경쓰며 이를 통해 공격에 성공했을 때 게이머가 느낄 수 있는 쾌감을 높일 수 있습니다.

  2. 충돌처리 된 위치

    충돌처리 된 위치로 파티클 생성 및 위치에 맞는 충돌처리 모션 등 다양한 요소가 필요할 수 있기 때문입니다

위와 같은 이유로 충돌처리 된 위치를 알 수 있고 정확하게 판정을 진행할 수 있는 AnimNotify를 이용하여 칼 이동 위치에 라인트레이스를 선택하였습니다.

그럼 AnimNotify를 이용한 라인트레이스 생성 방법은 문제가 없는거야? NO!

  1. AnimNotify를 사용하면 프레임에 영향을 안받고 라인트레이스를 생성 가능하나 제작을 진행함에 있어서 반복행동이 요구됩니다.
  2. AnimNotifyState를 사용하여 시작과 끝 범위를 지정하여 AnimNotify를 여러개 생성하지 않고 범위동안 노티파이를 실행할 수 있습니다.
  3. AnimNotifyState는 Tick을 통해 노티파이에서 무엇을 실행할 지 알리는데 이는 게임프레임에 영향을 받으므로 프레임이 떨어지면 노티파이 또한 실행숫자가 줄어들 수 있습니다.
  4. 정확한 판정을 필요로 하니 AnimNotify를 선택해야겠다!가 아닌 게임 프레임에 강제적으로 영향을 주었을 때 라인트레이스 생성이 60프레임에서 56번,40프레임에서 38번 정도 처리 되는 것을 확인 할 수 있었습니다.

    결론 : 프레임이 10까지 떨어지지 않는 이상 판정에는 문제가 없을거다라고 판단하고 AnimNotifyState를 사용하기로 하였습니다.

UEnableCollisionNotifyState 제작하기

1. UEnableCollisionNotifyState 헤더파일

UCLASS()
class SOULLIKE_API UEnableCollisionNotifyState : public UAnimNotifyState
{
	GENERATED_BODY()
    
public:
	virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
	virtual void NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime) override;
	virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

private:
	void MakeLineTrace(class ASoulLikeCharacter* Character);

	UPROPERTY()
	TSet<AActor*> HitActors;

	UPROPERTY(EditAnywhere,Category = "GAS")
	TSubclassOf<class UGameplayEffect> AttackDamageEffect;
};

2. NotifyTick() 정의하기

void UEnableCollisionNotifyState::NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime)
{
    if (AActor* Owner = MeshComp->GetOwner())
    {
        if (ASoulLikeCharacter* Character = Cast<ASoulLikeCharacter>(Owner))
        {
            if (Character->GetSwordWeapon())
            {
                MakeLineTrace(Character);
            }
        }
    }
}

3. 라인트레이스를 생성하는 MakeLineTrace() 정의하기

void UEnableCollisionNotifyState::MakeLineTrace(ASoulLikeCharacter* Character)
{
    if (Character->GetSwordWeapon())
    {
        USkeletalMeshComponent* SwordMesh = Character->GetSwordWeapon()->Weapon;
        if (SwordMesh && SwordMesh->DoesSocketExist(FName("SowrdBoneStart")) && SwordMesh->DoesSocketExist(FName("SowrdBoneEnd")))
        {
            FVector StartLocation = SwordMesh->GetSocketLocation(FName("SowrdBoneStart"));
            FVector EndLocation = SwordMesh->GetSocketLocation(FName("SowrdBoneEnd"));
            FHitResult HitResult;
            FCollisionQueryParams CollisionParams;
            CollisionParams.AddIgnoredActor(Character);
            if(Character->GetWorld()){
                bool bHit = Character->GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_WorldStatic, CollisionParams);
		
       
                if (bHit&&!HitActors.Contains(Character))
                {
                    HitActors.Add(Character);
                    DrawDebugLine(Character->GetWorld(), StartLocation, EndLocation, FColor::Red, false, 1.0f);
                
                    DrawDebugSphere(Character->GetWorld(), HitResult.ImpactPoint, 10, 12, FColor::Yellow, false, 1.0f);
                    if (ASoulNoneCharacter* HitCharacter = Cast<ASoulNoneCharacter>(HitResult.GetActor()))
                    {
                        UAbilitySystemComponent* ASC= UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitCharacter);
                        if (ASC)
                        {
                            float PlayRate = 1.0f; // 몽타주 재생 속도

                            if (HitCharacter->HitAnimMontage)
                            {
                      
                                ASC->PlayMontageSimulated(HitCharacter->HitAnimMontage, PlayRate);
                               
                               ... 게임 이펙트 적용
                                
                            }
                        }
                    }
                }
                else
                {
                    DrawDebugLine(Character->GetWorld(), StartLocation, EndLocation, FColor::Blue, false, 2.0f);
                }
            }
        }
    }
}

4. Sword 본에 Start와 End추가해주기

라인트레이스를 생성할 때 StartLocation과 EndLocation위치를 설정하기 위함입니다.

5. 라인트레이스 충돌처리 진행하기

 bool bHit = Character->GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_WorldStatic, CollisionParams);

-> LineTraceSingleByChannel를 통해 WorldStatic인 요소들에게 충돌처리를 진행합니다.

6. 오브젝트 중복되지 않게 처리하기

TSet<AActor*> HitActors;//.h

if (bHit&&!HitActors.Contains(Character))//.CPP

->Tset을 이용하여 중복된 오브젝트가 여러개 들어가지 않게 하고 !HitActors.Contains(Character)를 통해 한 번 충될된 오브젝트는 다시 충돌될 수 없게 하였습니다.(여러번 충돌되게 되면 조건발동이 많이 되기 때문입니다.)

->Tset을 사용하지 않고 라인트레이스 충돌처리(충돌시 생성되는 노란 구체가 여러개 생성되는 것을 확인해볼 수 있습니다.)

->Tset을 사용했을 때 라인트레이스 충돌처리(충돌시 생성되는 노란 구체가 하나만 생성되는 것을 확인해 볼 수 있습니다.)

마무리

충돌 처리 방법을 바꿈으로서 보다 정확하게 판정할 수 있게 되었습니다.
다음에는 공격시 게임이펙트(상대 캐릭터에 데미지 적용)적용 콤보와 방어를 구현해보도록 하겠습니다.
마지막으로 게임제작에 도움이 되고 있는 피의 거짓 영상으로 마무리하겠습니다.

0개의 댓글