일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Replication
- animation
- Security
- stride
- Race condition
- 게임개발
- ret2libc
- MLFQ
- 메카님
- gameplay ability system
- CTF
- 운영체제
- DSP
- gameplay effect
- 언리얼 엔진
- gas
- Unreal Engine
- DP
- MAC
- unity
- Multiplay
- dirty cow
- Delegate
- ability task
- 언리얼엔진
- photon fusion2
- 게임 개발
- 유스케이스
- Rr
- 유니티
- Today
- Total
Replicated
[ChronoSpace] Behavior Tree + Ability & Effect + Camera Shake 본문
[ChronoSpace] Behavior Tree + Ability & Effect + Camera Shake
라구넹 2025. 2. 17. 02:48계획: 저번 게시글에서 만들었던 Patrol이 캐릭터에 어느 정도 가까워지면 어빌리티를 발동해서 Trace 하고, Effect를 적용해서 에너지를 감소시킬 거임
예전에 중력 반전 만들 때 Trace로 처리하려다가 말았던 적이 있는데 해당 코드 재활용할 거임
(어빌리티 특성 상 코드가 좀 길어 헤더 파일은 안 넣을 것인데, 참고하실 분들은 깃허브 봐주세요)
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/CSGA_GiveDamage.h"
#include "GA/AT/CSAT_MultiTrace.h"
#include "GA/TA/CSTA_MultiTrace.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "ChronoSpace.h"
UCSGA_GiveDamage::UCSGA_GiveDamage()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerOnly;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
static ConstructorHelpers::FClassFinder<UGameplayEffect> DamageEffectRef(TEXT("/Game/Blueprint/GA/GE/BPGE_PatrolDamage.BPGE_PatrolDamage_C"));
if ( DamageEffectRef.Succeeded() )
{
DamageEffect = DamageEffectRef.Class;
}
}
void UCSGA_GiveDamage::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
//UE_LOG(LogCS, Log, TEXT("ActivateAbility - GiveDamage"));
UCSAT_MultiTrace* DamageTraceTask = UCSAT_MultiTrace::CreateTask(this, ACSTA_MultiTrace::StaticClass());
DamageTraceTask->OnComplete.AddDynamic(this, &UCSGA_GiveDamage::OnTraceResultCallback);
DamageTraceTask->ReadyForActivation();
}
void UCSGA_GiveDamage::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
int32 idx = 0;
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(DamageEffect);
//UE_LOG(LogCS, Log, TEXT("OnTraceResultCallback"));
if (EffectSpecHandle.IsValid())
{
//UE_LOG(LogCS, Log, TEXT("OnTraceResultCallback - EffectSpecHandle Is Valid"));
ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
}
/*while (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, idx))
{
FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, idx);
UE_LOG(LogCS, Log, TEXT("HitResult : %s"), *HitResult.GetActor()->GetName());
++idx;
}*/
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
게임플레이 어빌리티이다
주석 처리한 부분 제외하면 Trace로 얻어낸 액터들 확인 가능하다
이펙트는 30의 대미지를 주도록 설정한다
저기 Add라 되어 있는데 Override로 바꿔야 한다
기억해둘 부분은, TargetDataHandle에 여러 데이터가 들어 있는 경우
이펙트 적용 시 알아서 각각 적용해 준다
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/AT/CSAT_MultiTrace.h"
#include "GA/TA/CSTA_MultiTrace.h"
#include "AbilitySystemComponent.h"
UCSAT_MultiTrace::UCSAT_MultiTrace()
{
}
UCSAT_MultiTrace* UCSAT_MultiTrace::CreateTask(UGameplayAbility* OwningAbility, TSubclassOf<class ACSTA_MultiTrace> TargetActorClass)
{
UCSAT_MultiTrace* NewTask = NewAbilityTask<UCSAT_MultiTrace>(OwningAbility);
NewTask->TargetActorClass = TargetActorClass;
return NewTask;
}
void UCSAT_MultiTrace::Activate()
{
Super::Activate();
SpawnAndInitializeTargetActor();
FinalizeTargetActor();
SetWaitingOnAvatar();
}
void UCSAT_MultiTrace::OnDestroy(bool AbilityEnded)
{
if (SpawnedTargetActor)
{
SpawnedTargetActor->Destroy();
}
Super::OnDestroy(AbilityEnded);
}
void UCSAT_MultiTrace::SpawnAndInitializeTargetActor()
{
SpawnedTargetActor = Cast<ACSTA_MultiTrace>(GetWorld()->SpawnActorDeferred<ACSTA_MultiTrace>(TargetActorClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
if (SpawnedTargetActor)
{
SpawnedTargetActor->SetOwner(GetOwnerActor());
SpawnedTargetActor->TargetDataReadyDelegate.AddUObject(this, &UCSAT_MultiTrace::OnTargetDataReadyCallback);
}
}
void UCSAT_MultiTrace::FinalizeTargetActor()
{
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (ASC)
{
const FTransform SpawnTransform = ASC->GetAvatarActor()->GetTransform();
SpawnedTargetActor->FinishSpawning(SpawnTransform);
ASC->SpawnedTargetActors.Add(SpawnedTargetActor);
SpawnedTargetActor->StartTargeting(Ability);
SpawnedTargetActor->ConfirmTargeting();
}
}
void UCSAT_MultiTrace::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& DataHandle)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnComplete.Broadcast(DataHandle);
}
EndTask();
}
어빌리티 태스크이다
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/TA/CSTA_MultiTrace.h"
#include "Abilities/GameplayAbility.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GameFramework/Character.h"
#include "Components/CapsuleComponent.h"
#include "DrawDebugHelpers.h"
#include "Physics/CSCollision.h"
ACSTA_MultiTrace::ACSTA_MultiTrace()
{
bShowDebug = true;
}
void ACSTA_MultiTrace::StartTargeting(UGameplayAbility* Ability)
{
Super::StartTargeting(Ability);
SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
}
void ACSTA_MultiTrace::ConfirmTargetingAndContinue()
{
if (SourceActor)
{
FGameplayAbilityTargetDataHandle DataHandle = MakeTargetData();
TargetDataReadyDelegate.Broadcast(DataHandle);
}
}
FGameplayAbilityTargetDataHandle ACSTA_MultiTrace::MakeTargetData() const
{
ACharacter* Character = CastChecked<ACharacter>(SourceActor);
UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(SourceActor);
if (ASC == nullptr) return FGameplayAbilityTargetDataHandle();
TArray< FHitResult > OutHitResults;
const float AttackRange = 50.0f;
const float AttackRaduis = 100.0f;
FCollisionQueryParams Params(SCENE_QUERY_STAT(UCSAT_ReverseGravityTrace), false, Character);
const FVector Forward = Character->GetActorForwardVector();
const FVector Start = Character->GetActorLocation() + Forward * Character->GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + Forward * AttackRange;
bool HitDetected = GetWorld()->SweepMultiByChannel(OutHitResults, Start, End, FQuat::Identity, CCHANNEL_CSACTION, FCollisionShape::MakeSphere(AttackRaduis), Params);
FGameplayAbilityTargetDataHandle DataHandle;
if (HitDetected)
{
for (auto OutHitResult : OutHitResults)
{
FGameplayAbilityTargetData_SingleTargetHit* TargetData = new FGameplayAbilityTargetData_SingleTargetHit(OutHitResult);
DataHandle.Add(TargetData);
}
}
#if ENABLE_DRAW_DEBUG
if (bShowDebug)
{
FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
float CapsultHalfHeight = AttackRange * 0.5f;
FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsultHalfHeight, AttackRaduis, FRotationMatrix::MakeFromZ(Forward).ToQuat(), DrawColor, false, 5.0f);
}
#endif
return DataHandle;
}
타겟 액터이다
MakeTargetData에서 SeepMultiByChannel을 이용해 여러 액터들을 얻어낸다
이렇게 만들어진 어빌리티를
Patrol에서 부여해주고
외부(정확히는 행동 트리)에서 Active 시킬 수 있도록 public 함수로 하나 만들어준다
그리고 행동 트리를 수정해준다
MoveTo의 Acceptable Radius에 도달하면 MoveTo는 성공 판전이 된다
왼쪽 시퀀스에서 MoveTo성공 시 플레이어를 어느 정도 따라 잡았다는 이야기이다
그렇다면 이제 어빌리티를 발동해서 Energy를 깎아주면 된다
(그 후 잠시 Wait해서 난이도를 조정했다. 없으면 그대로 플레이어가 죽을 것이다)
Acceptable Radius는 MoveTo 노드에서 조정 가능
// Fill out your copyright notice in the Description page of Project Settings.
#include "BT/BTTask_ActivateGiveDamage.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Player/CSAIController.h"
#include "Character/CSCharacterPatrol.h"
#include "ChronoSpace.h"
UBTTask_ActivateGiveDamage::UBTTask_ActivateGiveDamage()
{
NodeName = TEXT("ActivateGiveDamage");
}
EBTNodeResult::Type UBTTask_ActivateGiveDamage::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
ACSCharacterPatrol* Patrol = Cast<ACSCharacterPatrol>(OwnerComp.GetAIOwner()->GetPawn());
if (Patrol)
{
Patrol->ActivateGiveDamage();
}
return EBTNodeResult::Succeeded;
}
ActivateAGiveDamage 태스크 노드이다
대미지를 주었든 안주었든 일단 공격은 한 거니까 성공 처리를 해준다
작동 확인 완료
그런데 멀티 테스트를 하려니까 크래시난다
트리거 오버랩 콜백에 권한 체크 넣어줘야 한다
행동 트리 모델은 서버에서만 실행되니 관련 코드에는 안해줘도 된다
그런데, 에너지가 깎일 때 플레이어의 카메라를 흔들고 싶다
CameraShake 라는데 언리얼 엔진에 있다고 해서 그걸 써볼 것이다
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/camera-shakes-in-unreal-engine
찾아보면 뭔가 레거시 쓰는게 많은데 굳이 싶어서 그냥 공식 문서 보고 만들자
CameraShakeBase를 상속받아 블루프린트를 만들어준다
Pattern은 Perlin Noise를 써보자
옵션 조절도 해주고
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/CSPlayerController.h"
#include "ChronoSpace.h"
ACSPlayerController::ACSPlayerController()
{
static ConstructorHelpers::FClassFinder<UCameraShakeBase> CameraShakeRef(TEXT("/Game/Blueprint/Camera/BP_CameraShake.BP_CameraShake_C"));
if ( CameraShakeRef.Succeeded() )
{
CameraShake = CameraShakeRef.Class;
}
}
void ACSPlayerController::ShakeCamera()
{
ClientStartCameraShake(CameraShake);
}
플레이어 컨트롤러를 만들어준다
// Fill out your copyright notice in the Description page of Project Settings.
#include "Attribute/CSAttributeSet.h"
#include "ChronoSpace.h"
#include "GameplayEffectExtension.h"
#include "Player/CSPlayerController.h"
#include "CoreMinimal.h"
UCSAttributeSet::UCSAttributeSet() : MaxEnergy(100.0f), Damage(0.0f)
{
InitEnergy(GetMaxEnergy());
}
void UCSAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
if (Attribute == GetDamageAttribute())
{
NewValue = NewValue < 0.0f ? 0.0f : NewValue;
}
// -> 최소 데미지 0.0f 으로 설정.
}
bool UCSAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
{
return true;
}
void UCSAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
float MinimumEnergy = 0.0f;
if (Data.EvaluatedData.Attribute == GetEnergyAttribute())
{
UE_LOG(LogTemp, Warning, TEXT("Direct Health Access : %f"), GetEnergy());
SetEnergy(FMath::Clamp(GetEnergy(), MinimumEnergy, GetMaxEnergy()));
}
if ( Data.EvaluatedData.Attribute == GetDamageAttribute() )
{
UE_LOG(LogCS, Log, TEXT("Damage Detected : %f"), GetDamage());
SetEnergy(FMath::Clamp(GetEnergy() - GetDamage(), MinimumEnergy, GetMaxEnergy()));
SetDamage(0.0f);
AActor* TargetActor = Data.Target.GetAvatarActor();
if (TargetActor == nullptr) return;
if (APawn* Pawn = Cast<APawn>(TargetActor))
{
ACSPlayerController* PC = Cast<ACSPlayerController>(Pawn->GetController());
if (PC)
{
PC->ShakeCamera();
}
}
}
}
그리고 어트리뷰트 셋에 PostGameplatEffectExecute에서 카메라를 컨트롤러를 가져와서 카메라를 흔든다
정상 작동 확인 가능하다
영상으로 찍진 않았는데 멀티플레이에서도 카메라 멀쩡히 흔들린다
'언리얼 엔진 > ChronoSpace' 카테고리의 다른 글
[ChronoSpace] Patrol 배치, Clockwork Labyrinth 프로토타입 완성 (0) | 2025.02.17 |
---|---|
[ChronoSpace] ** Attribute Replication ** (0) | 2025.02.17 |
[ChronoSpace] NavMesh와 Behavior Tree를 이용한 AI NPC (Clockwork Labyrinth) (3) | 2025.02.16 |
[ChronoSpace] Key 흩뿌리기 (Clorkwork Labyrinth) (0) | 2025.02.15 |
[ChronoSpace] Change Level in Multiplay (0) | 2025.02.15 |