[UE] Behavior Tree: 활 조준 및 발사하기

Player에 구현한 화살 발사를 궁수 AI에도 적용한다. SetFocus를 사용하여 적을 지속적으로 바라보게 만든다. 몽타주를 재생시켜 활의 시위를 당기고 화살 발사 준비를 마친 후 CSubAction_Bow 코드를 재사용하여 화살을 발사한다. CSubAction_Bow 코드 내에 AIController일 때도 Press()와 Released(), 즉 조준 후 발사가 가능하도록 코드를 추가한다.
목차
Plugins |
||||
Weapon |
||||
Resource |
||||
Icon128.png weapon_thumbnail_icon.png |
||||
Source |
||||
Weapon | ||||
SWeaponCheckBoxes.h .cpp SWeaponDetailsView.h .cpp SWeaponDoActionData.h .cpp SWeaponEquipmentData.h .cpp SWeaponHitData.h .cpp SWeaponLeftArea.h .cpp Weapon.Build.cs WeaponAssetEditor.h .cpp WeaponAssetFactory.h .cpp WeaponCommand.h .cpp WeaponContextMenu.h .cpp WeaponModule.h .cpp WeaponStyle.h .cpp |
||||
Source | ||
BehaviorTree | ||
CBTService_Melee.h .cpp CBTService_Range.h .cpp CBTTaskNode_Action.h .cpp CBTTaskNode_Equip.h .cpp CBTTaskNode_Hitted.h .cpp CBTTaskNode_Patrol.h .cpp CBTTaskNode_Speed.h .cpp CEnvQueryContext_Target.h .cpp 생성 CPatrol.h .cpp |
||
Characters | ||
CAIController.h .cpp CAnimInstance.h .cpp CEnemy.h .cpp CEnemy_AI.h.cpp CPlayer.h.cpp ICharacter.h .cpp |
||
Components | ||
CAIBehaviorComponent.h .cpp CFeetComponent.h .cpp CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CStatusComponent.h .cpp CWeaponComponent.h .cpp CZoomComponent.h .cpp |
||
Notifies | ||
CAnimNotifyState_BeginAction.h .cpp CAnimNotifyState_BowString.h .cpp CAnimNotify_CameraShake.h .cpp CAnimNotify_End_Parkour.h .cpp CAnimNotifyState_EndAction.h .cpp CAnimNotify_EndState.h .cpp CAnimNotifyState.h .cpp CAnimNotifyState_CameraAnim.h .cpp CAnimNotifyState_Collision.h .cpp CAnimNotifyState_Combo.h .cpp CAnimNotifyState_Equip.h .cpp CAnimNotifyState_SubAction.h .cpp |
||
Parkour | ||
CParkourComponent.h .cpp | ||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CArrow.h .cpp CAura.h .cpp CCamerModifier.h .cpp CGhostTrail.h .cpp CRotate_Object.h .cpp CThornObject.h .cpp CAnimInstance_Bow.h .cpp CAttachment_Bow.h .cpp CDoAction_Around.h .cpp CDoAction_Bow.h .cpp CDoAction_Combo.h .cpp CDoAction_Warp.h .cpp CSubAction_Around.h .cpp CSubAction_Bow.h .cpp CSubAction_Fist.h .cpp CSubAction_Hammer.h .cpp CSubAction_Sword.h .cpp CDoAction_Warp.h .cpp CAttachment.h .cpp CDoAction.h .cpp CEquipment.h .cpp CSubAction.h .cpp CWeaponAsset.h .cpp CWeaponData.h .cpp CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
방법1. 활을 쏘는 Enemy: 화살 발사하기
CWeaponComponent
CWeaponComponent.h
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "CWeaponComponent.generated.h" UENUM(BlueprintType) enum class EWeaponType : uint8 { Fist, Sword, Hammer, Warp, Around, Bow, Max, }; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType); UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) class U2212_06_API UCWeaponComponent : public UActorComponent { GENERATED_BODY() private://DataAsset을 받아온다. UPROPERTY(EditAnywhere, Category = "DataAsset") class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max]; public: FORCEINLINE EWeaponType GetWeaponType() { return Type; } public: //무기 Type이 맞는지 확인해주는 함수들 FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; } FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; } FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; } FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; } FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; } FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; } FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; } public: UCWeaponComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; private: bool IsIdleMode();//StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다. public: class ACAttachment* GetAttachment(); class UCEquipment* GetEquipment(); class UCDoAction* GetDoAction(); public: UFUNCTION(BlueprintCallable) class UCSubAction* GetSubAction(); public: //무기 세팅 void SetUnarmedMode(); void SetFistMode(); void SetSwordMode(); void SetHammerMode(); void SetWarpMode(); void SetAroundMode(); void SetBowMode(); void DoAction(); public: UFUNCTION(BlueprintCallable) void SubAction_Pressed(); UFUNCTION(BlueprintCallable) void SubAction_Released(); private: void SetMode(EWeaponType InType); void ChangeType(EWeaponType InType); public: //무기가 바뀌었을때 통보해줄 delegate FWeaponTypeChanged OnWeaponTypeChange; private: class ACharacter* OwnerCharacter; EWeaponType Type = EWeaponType::Max; private: //가비지 콜랙터가 삭제하지 않도록 UPROPERTY를 붙여 직렬화 시켜준다. 직렬화되면 가비지 콜렉터가 삭제하지 않는다. UPROPERTY가 없으면 터진다. UPROPERTY() class UCWeaponData* Datas[(int32)EWeaponType::Max];//실제로 생성된 데이터 };
직렬화하여 블루프린트에서 보이게 하기
- UFUNCTION(BlueprintCallable)
class UCSubAction* GetSubAction(); - UFUNCTION(BlueprintCallable)
void SubAction_Pressed(); - UFUNCTION(BlueprintCallable)
void SubAction_Released();
CWeaponComponent.cpp
#include "Components/CWeaponComponent.h" #include "Global.h" #include "CStateComponent.h" #include "GameFramework/Character.h" #include "Weapons/CWeaponAsset.h" #include "Weapons/CWeaponData.h" #include "Weapons/CAttachment.h" #include "Weapons/CEquipment.h" #include "Weapons/CDoAction.h" #include "Weapons/CSubAction.h" UCWeaponComponent::UCWeaponComponent() { //Tick을 실행시켜주는 코드 PrimaryComponentTick.bCanEverTick = true; } void UCWeaponComponent::BeginPlay() { Super::BeginPlay(); OwnerCharacter = Cast<ACharacter>(GetOwner()); for (int32 i=0; i < (int32)EWeaponType::Max; i++) { if (!!DataAssets[i]) //DataAssets[i]이 있다면(=무기가 할당되어 있다면) DataAssets[i]->BeginPlay(OwnerCharacter, &Datas[i]);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다. } } void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!!GetDoAction())//DoAction이 있다면 GetDoAction()->Tick(DeltaTime);//DoAction의 Tick을 콜 해준다. if (!!GetSubAction())//SubAction이 있다면 GetSubAction()->Tick(DeltaTime);//SubAction의 Tick을 콜 해준다. } bool UCWeaponComponent::IsIdleMode() { return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode(); } ACAttachment* UCWeaponComponent::GetAttachment() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!Datas[(int32)Type], nullptr); return Datas[(int32)Type]->GetAttachment();//실제 데이터를 리턴 } UCEquipment* UCWeaponComponent::GetEquipment() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!Datas[(int32)Type], nullptr); return Datas[(int32)Type]->GetEquipment();//실제 데이터를 리턴 } UCDoAction* UCWeaponComponent::GetDoAction() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!Datas[(int32)Type], nullptr); return Datas[(int32)Type]->GetDoAction();//실제 데이터를 리턴 } UCSubAction* UCWeaponComponent::GetSubAction() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!Datas[(int32)Type], nullptr); return Datas[(int32)Type]->GetSubAction();//실제 데이터를 리턴 } void UCWeaponComponent::SetUnarmedMode() { GetEquipment()->Unequip(); ChangeType(EWeaponType::Max); } void UCWeaponComponent::SetFistMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Fist); } void UCWeaponComponent::SetSwordMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Sword); } void UCWeaponComponent::SetHammerMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Hammer); } void UCWeaponComponent::SetWarpMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Warp); } void UCWeaponComponent::SetAroundMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Around); } void UCWeaponComponent::SetBowMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Bow); } void UCWeaponComponent::SetMode(EWeaponType InType) { if (Type == InType) { SetUnarmedMode(); return; } else if (IsUnarmedMode() == false) { GetEquipment()->Unequip(); } if (!!Datas[(int32)InType])//실제 데이터가 있다면 { Datas[(int32)InType]->GetEquipment()->Equip(); ChangeType(InType); } } void UCWeaponComponent::ChangeType(EWeaponType InType) { EWeaponType prevType = Type; Type = InType; if (OnWeaponTypeChange.IsBound()) OnWeaponTypeChange.Broadcast(prevType, InType); } void UCWeaponComponent::DoAction() { if (!!GetDoAction()) GetDoAction()->DoAction(); } void UCWeaponComponent::SubAction_Pressed() { if (!!GetSubAction()) GetSubAction()->Pressed(); } void UCWeaponComponent::SubAction_Released() { if (!!GetSubAction()) GetSubAction()->Released(); }
변경사항 없음
CSubAction_Bow
CSubAction_Bow.h
#pragma once #include "CoreMinimal.h" #include "Weapons/CSubAction.h" #include "Components/TimelineComponent.h" #include "CSubAction_Bow.generated.h" USTRUCT() struct FAimData { GENERATED_BODY() public: UPROPERTY(EditAnywhere) float TargetArmLength = 100; UPROPERTY(EditAnywhere) FVector SocketOffset = FVector(0, 30, 10); UPROPERTY(EditAnywhere) bool bEnableCameraLag; UPROPERTY(EditAnywhere) FVector CameraLocation; }; UCLASS(Blueprintable) class U2212_06_API UCSubAction_Bow : public UCSubAction { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Aiming") class UCurveVector* Curve; UPROPERTY(EditAnywhere, Category = "Aiming") FAimData AimData; UPROPERTY(EditAnywhere, Category = "Aiming") float AimingSpeed = 200; public: UCSubAction_Bow(); public: virtual void Pressed() override; virtual void Released() override; public: void BeginPlay(class ACharacter* InOwner, class ACAttachment* InAttachment, class UCDoAction* InDoAction) override; public: void Tick_Implementation(float InDeltaTime) override; private: UFUNCTION() void OnAiming(FVector Output); private: class USpringArmComponent* SpringArm; class UCameraComponent* Camera; private: FTimeline Timeline; //SubAction는 Actor가 아니기 때문에 Component를 가질 수 없다. SubAction은 UObject 상속이다. //그래서 TimelineComponent가 아닌 Timeline으로 작업한다. private: FAimData OriginData; private: float* Bend; };
변경사항 없음.
CSubAction_Bow.cpp
#include "Weapons/SubActions/CSubAction_Bow.h" #include "Global.h" #include "AIController.h" #include "GameFramework/Character.h" #include "GameFramework/PlayerController.h" #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" #include "Components/CStateComponent.h" #include "Weapons/Attachments/CAttachment_Bow.h" UCSubAction_Bow::UCSubAction_Bow() { CHelpers::GetAsset<UCurveVector>(&Curve, "CurveVector'/Game/Weapons/Bow/Curve_Aiming.Curve_Aiming'");//Editor에서 만든 CurveVector를 할당한다. } void UCSubAction_Bow::Pressed() { CheckTrue(State->IsSubActionMode()); //Enemy AI if (!!Owner->GetController<AAIController>()) { Super::Pressed(); State->OnSubActionMode(); return; } CheckNull(SpringArm); CheckNull(Camera); Super::Pressed(); State->OnSubActionMode(); OriginData.TargetArmLength = SpringArm->TargetArmLength; OriginData.SocketOffset = SpringArm->SocketOffset; OriginData.bEnableCameraLag = SpringArm->bEnableCameraLag; OriginData.CameraLocation = Camera->GetRelativeLocation(); SpringArm->TargetArmLength = AimData.TargetArmLength; SpringArm->SocketOffset = AimData.SocketOffset; SpringArm->bEnableCameraLag = AimData.bEnableCameraLag; Camera->SetRelativeLocation(AimData.CameraLocation); Timeline.PlayFromStart();//Timeline 동작 시작. } void UCSubAction_Bow::Released() { CheckFalse(State->IsSubActionMode()); //Enemy AI if (!!Owner->GetController<AAIController>()) { Super::Pressed(); State->OffSubActionMode(); return; } CheckNull(SpringArm); CheckNull(Camera); Super::Released(); State->OffSubActionMode(); SpringArm->TargetArmLength = OriginData.TargetArmLength; SpringArm->SocketOffset = OriginData.SocketOffset; SpringArm->bEnableCameraLag = OriginData.bEnableCameraLag; Camera->SetRelativeLocation(OriginData.CameraLocation); Timeline.ReverseFromEnd();//Timeline 뒤집기 } void UCSubAction_Bow::BeginPlay(ACharacter* InOwner, ACAttachment* InAttachment, UCDoAction* InDoAction) { Super::BeginPlay(InOwner, InAttachment, InDoAction); SpringArm = CHelpers::GetComponent<USpringArmComponent>(InOwner); Camera = CHelpers::GetComponent<UCameraComponent>(InOwner); FOnTimelineVector timeline; timeline.BindUFunction(this, "OnAiming");//OnAiming함수를 묶어서 콜한다. Timeline.AddInterpVector(Curve, timeline); Timeline.SetPlayRate(AimingSpeed); ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment);//CAttachment_Bow의 멤버를 사용하기 위해 캐스팅한다. if (!!bow) Bend = bow->GetBend();//CSubAction_Bow에서 선언한 Bend 변수에 CAttachment_Bow의 Bend값을 GetBend()로 가져와서 넣어준다. } void UCSubAction_Bow::Tick_Implementation(float InDeltaTime) { Super::Tick_Implementation(InDeltaTime); //TimelineComponent로 사용하는 경우 자기자신이 Tick이 있기 때문에 아래 작업을 안 해도 상관없으나 CSubAction_Bow의 경우 TimelineComponent가 없기 때문에 Tick을 반드시 넣어주어야 한다. Timeline.TickTimeline(InDeltaTime); } void UCSubAction_Bow::OnAiming(FVector Output) { Camera->FieldOfView = Output.X;//조준 활성화와 해제에 앞뒤 ZoomIn&Out에 Output.X값이 쓰인다. if (!!Bend) *Bend = Output.Y;//에디터의 Curve_Aiming에서 설정한 Y값. 0~100까지 나온다. }
헤더 추가
- #include "AIController.h"
Enemy_AI에서 화살 쏘기를 실행하기 위해 코드를 추가. 아래의 코드가 없으면 활 조준을 하지 않아 화살을 발사하지 않는다.
- void UCSubAction_Bow::Pressed()
- if (!!Owner->GetController<AAIController>())
{
Super::Pressed();
State->OnSubActionMode();
return;
}- 명령을 수행하는 주체(Owner)의 Controller가 AIIController 캐스팅이 가능하다면 Enemy라는 의미다. 해당 경우, 화살을 쏘고 리턴한다.
- if (!!Owner->GetController<AAIController>())
- void UCSubAction_Bow::Released()
- if (!!Owner->GetController<AAIController>())
{
Super::Pressed();
State->OffSubActionMode();
return;
}
- if (!!Owner->GetController<AAIController>())
BP_CEquipment_Bow 생성



Event Graph

DA_Bow

BT_Range

실행화면

Avoid 구현하기
CEnvQueryContext_Target 생성
새 C++ 클래스 - EnvQueryContext - CEnvQueryContext_Target 생성


CEnvQueryContext_Target.h
#pragma once #include "CoreMinimal.h" #include "EnvironmentQuery/EnvQueryContext.h" #include "CEnvQueryContext_Target.generated.h" UCLASS() class U2212_06_API UCEnvQueryContext_Target : public UEnvQueryContext { GENERATED_BODY() private: void ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const override; };
CEnvQueryContext_Target.cpp
#include "BehaviorTree/CEnvQueryContext_Target.h" #include "Global.h" #include "EnvironmentQuery/EnvQueryTypes.h" #include "EnvironmentQuery/Items/EnvQueryItemType_Actor.h" #include "Characters/CEnemy_AI.h" #include "Characters/CAIController.h" #include "BehaviorTree/BlackboardComponent.h" void UCEnvQueryContext_Target::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const { Super::ProvideContext(QueryInstance, ContextData); //Query를 수행하는 Actor인 ACEnemy_AI 캐스팅 ACEnemy_AI* ai = Cast<ACEnemy_AI>(QueryInstance.Owner.Get());//QueryInstance.Owner.Get()는 UObject 리턴이다. Querier가 Object, Actor, Location 등이 될 수 있으니 어느것이든 다 받을수 있도록 최상위형인 UObject로 받기 위해QueryInstance.Owner.Get()으로 받았다. ACAIController* controller = ai->GetController<ACAIController>();//Blackboard를 가져오기 위해 먼저 controller를 캐스팅한다. UBlackboardComponent* blackboard = controller->GetBlackboardComponent();//controller 내의 Blackboard를 가져온다. AActor* target = Cast<AActor>(blackboard->GetValueAsObject("Target"));//Blackboard의 Target를 가져다쓴다. //target을 리턴할 것이기 때문에 Actor return이 되는 UEnvQueryItemType_Actor를 사용한다. UEnvQueryItemType_Actor::SetContextHelper(ContextData, target); //위치를 리턴해야 되는 경우 아래처럼 UEnvQueryItemType_VectorBase를 사용하면 된다. //UEnvQueryItemType_VectorBase:: }
void UCEnvQueryContext_Target::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
- ACEnemy_AI* ai = Cast<ACEnemy_AI>(QueryInstance.Owner.Get());
- Query를 수행하는 Actor인 ACEnemy_AI 캐스팅
- QueryInstance.Owner.Get()는 UObject 리턴이다. Querier가 Object, Actor, Location 등이 될 수 있으니 어느것이든 다 받을수 있도록 최상위형인 UObject로 받기 위해QueryInstance.Owner.Get()으로 받았다.
- ACAIController* controller = ai->GetController<ACAIController>();
- Blackboard를 가져오기 위해 먼저 controller를 캐스팅한다.
- UBlackboardComponent* blackboard = controller->GetBlackboardComponent();
- controller 내의 Blackboard를 가져온다.
- AActor* target = Cast<AActor>(blackboard->GetValueAsObject("Target"));
- Blackboard의 Target를 가져다쓴다.
- UEnvQueryItemType_Actor::SetContextHelper(ContextData, target);
- target을 리턴할 것이기 때문에 Actor return이 되는 UEnvQueryItemType_Actor를 사용한다.
Querier가 Object, Actor, Location 등이 될 수 있으니 어느것이든 다 받을수 있도록 최상위형인 UObject로 받기 위해
QueryInstance.Owner.Get()
으로 받았다.
EQ_Range 생성
인공 지능 - 인바이런먼트 쿼리 - EQ_Range 생성




BB_Range

Avoid_Location 변수 추가
BT_Range

실행화면

'⭐ Unreal Engine > UE RPG AI' 카테고리의 다른 글
[UE] Behavior Tree: 워프(Warp), EQS (0) | 2023.08.09 |
---|---|
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기 (0) | 2023.08.07 |
[UE] Behavior Tree: 무기 장착 Abort (0) | 2023.08.01 |
[UE] Behavior Tree: 피격(Hitted) (0) | 2023.07.31 |
[UE] Behavior Tree: 무기 장착 및 공격 (0) | 2023.07.27 |
댓글
이 글 공유하기
다른 글
-
[UE] Behavior Tree: 워프(Warp), EQS
[UE] Behavior Tree: 워프(Warp), EQS
2023.08.09 -
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
[UE] Behavior Tree: 활 Enemy 생성, Player 바라보기
2023.08.07 -
[UE] Behavior Tree: 무기 장착 Abort
[UE] Behavior Tree: 무기 장착 Abort
2023.08.01 -
[UE] Behavior Tree: 피격(Hitted)
[UE] Behavior Tree: 피격(Hitted)
2023.07.31
댓글을 사용할 수 없습니다.