Mrhoony / UE4_StillTheyAlive

UE4 [3D, CO-OP] C++Game TeamProject
0 stars 0 forks source link

Ultimate #66

Closed ooweaJ closed 1 year ago

ooweaJ commented 1 year ago

Ultimate Component 생성 , 라이플, 매직, 대검의 특수 공격과 궁극기 생성 라이플은 우클릭 후 줌이되어 그 상태에선 연사가 가능하게 특수 공격 생성

Zoom ? OffZoom() : OnZoom(); 
void ACRifle::OnZoom()
{
    Zoom = true;
    DataObject->GetDoAction()->Datas[0].Diversity = true;
    OwnerSpringArm->TargetArmLength = 150.f;
    OwnerSpringArm->SocketOffset = FVector(0, 50, 20);
}

void ACRifle::OffZoom()
{
    Zoom = false;
    DataObject->GetDoAction()->Datas[0].Diversity = false;
    OwnerSpringArm->TargetArmLength = 200.f;
    OwnerSpringArm->SocketOffset = FVector(0, 0, 0);
}

void ACRifle::DivAction()
{
    DataObject->GetDoAction()->Begin_DoAction();
    GetWorld()->GetTimerManager().SetTimer(AutoFireTimer, this, &ACRifle::Fire, 0.1f, true);
}

줌 상태를 삼항연산자로 체크 후 .Diversity옵션에 따라 DoAction에서 다른 함수가 호출 되는데 거기서 타이머를 이용한 연사 구현

매직의 특수 공격과 궁극기로 매 틱 데미지를 입히는 소환형 파이어볼 과 블랙홀이 있다.

void ASpawnMove::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    DamageAction();

}

void ASpawnMove::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    ACEnemy* enemy = Cast<ACEnemy>(OtherActor);
    if (!!enemy)
        HittedCharacters.AddUnique(enemy);

}

void ASpawnMove::OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    ACEnemy* enemy = Cast<ACEnemy>(OtherActor);
    if (!!enemy)
        HittedCharacters.Remove(enemy);
}

void ASpawnMove::DamageAction()
{
    ACWeapon* OwnerWeapon = Cast<ACWeapon>(GetOwner());
    TArray<FTechDoAction> techdata = OwnerWeapon->GetCurrent()->GetDoAction()->TechDatas;
    ACharacter* OwnerCharacter = Cast<ACharacter>(OwnerWeapon->GetOwner());
    FDamageEvent e;
    for (ACharacter* enemy : HittedCharacters)
    {
        enemy->GetCharacterMovement()->StopMovementImmediately();
        enemy->TakeDamage(5, e, OwnerCharacter->GetController(), this);

        if (!!ImpactParticle)
        {
            FTransform transform = ImpactParticleTransform;
            transform.AddToTranslation(enemy->GetActorLocation());

            UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticle, transform);
        }
    }
}

파이어 볼은 이런식으로 HittedCharacters 배열에 담고 지우고를 설정해주고 Overlap 조건으로 처음에는 반복문을 HittedCharacters.Num() 값 만큼 돌리는 식으로 했었는데 배열의 수가 바뀌면서 반복문이 돌아 터지는 경우가 있어 방식을 저렇게 바꾸었다.


void ACFloorBlackhole::BeginPlay()
{
    Super::BeginPlay();
    Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    UKismetSystemLibrary::K2_SetTimer(this, "DamageAction", 0.5f, true);
    End_Action();
}

void ACFloorBlackhole::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    Box->UpdateOverlaps();
}

void ACFloorBlackhole::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    ACEnemy* enemy = Cast<ACEnemy>(OtherActor);
    if (!!enemy)
        HittedCharacters.AddUnique(enemy);
}

void ACFloorBlackhole::OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    ACEnemy* enemy = Cast<ACEnemy>(OtherActor);
    if (!!enemy)
        HittedCharacters.Remove(enemy);
}

void ACFloorBlackhole::End_Action()
{
    FTimerDynamicDelegate timer;
    timer.BindUFunction(this, "Finish");
    UKismetSystemLibrary::K2_SetTimerDelegate(timer, 4.f, false);
}

void ACFloorBlackhole::Finish()
{
    UKismetSystemLibrary::K2_ClearTimer(this, "DamageAction");
}

void ACFloorBlackhole::DamageAction()
{
    FDamageEvent e;
    for (ACharacter* enemy : HittedCharacters)
    {
        bool bIgnore = false;
        bIgnore |= (enemy->IsPendingKill());

        if (bIgnore) continue;

        if (OwnerWeapon->GetCurrent()->GetDoAction())
        {
            TArray<FTechDoAction> techdata = OwnerWeapon->GetCurrent()->GetDoAction()->TechDatas;
            enemy->TakeDamage(techdata[0].Power, e, OwnerCharacter->GetController(), this);

            FVector start = GetActorLocation();
            FVector target = enemy->GetActorLocation();
            FVector direction = start - target;
            direction.Normalize();

            enemy->GetCharacterMovement()->StopMovementImmediately();
            enemy->LaunchCharacter(direction * techdata[0].Power * 100, true, false);

            UParticleSystem* hitEffect = techdata[0].Effect;
            if (!!hitEffect)
            {
                FTransform transform = techdata[0].EffectTransform;
                transform.AddToTranslation(enemy->GetActorLocation());
                UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), hitEffect, transform);
            }
        }
    }
}

블랙홀은 별 차이 없지만 비긴플레이에서 타이머로 공격 간격을 조절했고 매 틱마다 UpdateOverlaps 해서 처음부터 겹쳐있던 액터들도 추출할 수 있게 조건을 주었고 스폰시간과 비슷하게 Finish 타이머로 만들어 종료시킨다. 객체가 사라지고 난후에 타이머가 돌면 터질 가능성도 있어 저렇게 관리했다.

그 다음 대검의 특수공격과 궁극기로 차징공격과 소환형 다단히트이다.

void UCAnimNotify_Charging::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
    Super::Notify(MeshComp, Animation);
    CheckNull(MeshComp);

    UCDeckComponent* deck = CHelpers::GetComponent<UCDeckComponent>(MeshComp->GetOwner());
    CheckNull(deck);

    ACDoAction* doAction = Cast<ACDoAction>(deck->GetCurrentPerk()->GetCurrent()->GetDoAction());
    CheckNull(doAction);

    if (doAction->IsCharging() == false) return;
    else
    {
        ACWeapon* weapon = Cast<ACWeapon>(deck->GetCurrentPerk());
        if (!!weapon)
        {
            weapon->ChargingMontage();
        }
    }
}

void ACGreatSword::ChargingMontage()
{
    Super::ChargingMontage();

    ChargingStack++;
    if (ChargingStack >= 4)
    {
        EndTechAction();
        return;
    }

    OwnerCharacter->StopAnimMontage();
    TArray<FTechDoAction> datas = Data->TechDoActionDatas;
    if (datas.Num() != 0)
        OwnerCharacter->PlayAnimMontage(datas[0].Montage.AnimMontage, datas[0].Montage.PlayRate, "Start");
}

차징은 노티파이로 차징상태를 검사해 ChargingMontage 함수를 실행시키는 방식이고 몽타주의 StartSection 이용해서 구현했다.

void ACFloorGreat::BeginPlay()
{
    Super::BeginPlay();
    Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    UKismetSystemLibrary::K2_SetTimer(this, "DamageAction", 0.1f, true);
    End_Action();
}

void ACFloorGreat::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    Box->UpdateOverlaps();
}

void ACFloorGreat::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    ACEnemy* enemy = Cast<ACEnemy>(OtherActor);
    if(!!enemy)
    HittedCharacters.AddUnique(enemy);
}

void ACFloorGreat::End_Action()
{
    FTimerDynamicDelegate timer;
    timer.BindUFunction(this, "Finish");
    UKismetSystemLibrary::K2_SetTimerDelegate(timer, 2.f, false);
}

void ACFloorGreat::Finish()
{
    UKismetSystemLibrary::K2_ClearTimer(this, "DamageAction");
}

void ACFloorGreat::DamageAction()
{
    FDamageEvent e;
    for (int32 i = 0; i < HittedCharacters.Num(); i++)
    {
        bool bIgnore = false;
        bIgnore |= (HittedCharacters[i]->IsPendingKill());

        if (bIgnore) continue;

        if (OwnerWeapon->GetCurrent()->GetDoAction())
        {
            TArray<FTechDoAction> techdata = OwnerWeapon->GetCurrent()->GetDoAction()->TechDatas;
            HittedCharacters[i]->GetCharacterMovement()->StopMovementImmediately();
            HittedCharacters[i]->TakeDamage(techdata[0].Power, e, OwnerCharacter->GetController(), this);
            HittedCharacters[i]->LaunchCharacter(FVector(0, 0, 100), false, false);

            UParticleSystem* hitEffect = techdata[0].Effect;
            if (!!hitEffect)
            {
                FTransform transform = techdata[0].EffectTransform;
                transform.AddToTranslation(HittedCharacters[i]->GetActorLocation());
                UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), hitEffect, transform);
            }
        }
    }
}

이 궁극기는 그 범위에 들어온 적은 무조건 맞는 설정을 위해 블랙홀과 비슷하지만 배열에서 지우는 역할은 없다.

궁극기 게이지를 채우용도로 Enemy가 죽었을 때 스폰하는 액터로 간단하게 구현했다.

void ACUltimate::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    ACPlayer* player = Cast<ACPlayer>(OtherActor);
    if (player == nullptr) return;
    UCUltimateComponent* ultimate = CHelpers::GetComponent<UCUltimateComponent>(player);
    if (ultimate == nullptr) return;

    ultimate->IncreaseUltimate(UltimateGauge);

    Destroy();
}