- 장착 하려고하는 무기타입과, 현재 장착 되어있는 무기 타입이 같다면? : 성공 리턴시키고 액션으로 넘어가야한다.
- 타입이 달라서 무기를 장착하면 성공 리턴을 해야하나? : 아니다.
- CWeaponData
- 웨폰 에셋 클래스의 각 클래스 저장 변수와 각 변수값 가져오기 함수를 붙여넣는다.
- friend 키워드 : 상속받지 않아도 지정한 클래스와 서로서로 private 선언한 변수, 함수를 사용할 수 있도록 하는 키워드다.
- 남발하면 안되는 이유 : private가 깨지기 때문이다.
- 하지만 종종 사용이 되고있다.
- WeaponData 클래스에 접근할 수 있는 유일한 클래스를 웨폰 에셋으로 해야한다.
- 헤더 파일 - 각 클래스가 저장될 Attachment, Equipment, DoAction, SubAction 변수 선언 - 각 변수들 가져오기 함수를 각 클래스 포인터로 선언하고 각 변수들을 반환하는것으로 정의한다. - friend 키워드로 WeaponAsset 클래스를 지정한다.
//header
private:
//friend : 웨폰 에셋의 private에 접근이 가능해지는 키워드다. (C#에는 Internal이 있다.(의미가 다르다.))
friend class UCWeaponAsset;
public:
FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }
FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }
FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }
FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; }
private:
UPROPERTY()
class ACAttachment* Attachment;
UPROPERTY()
class UCEquipment* Equipment;
UPROPERTY()
class UCDoAction* DoAction;
UPROPERTY()
class UCSubAction* SubAction;
- CWeaponAsset
- 플레이 시작때 웨폰 데이터를 받는다.
- 헤더 파일 - BeginPlay 매개변수 수정
- CPP 파일 - BeginPlay 매개변수 수정 및 코드 수정 - 각 클래스 저장 변수를 WeaponData 클래스로 옮겼으니 해당 변수들이 없다. : 직접 포인터 변수(소문자)로 선언하고 nullptr로 초기화한다. - 저장 변수를 함수 내부에서 선언한 변수로 바꾼다. - 이제 웨폰 데이터 클래스의 ‘새 오브젝트 생성’ 함수를 호출하고 매개변수로 들어온 ‘웨폰 데이터’ 의 포인터 값에 저장한다. (각 객 체마다 새로운 웨폰 데이터가 생성된다.) - 해당 클래스의 각 클래스 저장 변수에 ‘웨폰 에셋’ 클래스에서 선언하고 저장한 각 클래스 저장 변수를 저장한다.
- 정리
- 웨폰 에셋 첫 시작 - 각 클래스 내용 초기화 후 저장 - 매개변수로 들어온 웨폰 데이터 ‘새 오브젝트 생성’ - 저장한 각 클래스 변수를 웨폰 데이터 변수에 저장
//CPP
void UCWeaponAsset::BeginPlay(ACharacter* InOwner, class UCWeaponData** OutWeaponData)
{
ACAttachment* attachment = nullptr;
if (!!AttachmentClass)
{
FActorSpawnParameters params;
params.Owner = InOwner;
attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params);
}
UCEquipment* equipment = nullptr;
if (!!EquipmentClass)
{
equipment = NewObject<UCEquipment>(this, EquipmentClass);
equipment->BeginPlay(InOwner, EquipmentData);
if (!!attachment)
{
equipment->OnEquipmentBeginEquip.AddDynamic(attachment, &ACAttachment::OnBeginEquip);
equipment->OnEquipmentUnequip.AddDynamic(attachment, &ACAttachment::OnUnequip);
}
}
UCDoAction* doAction = nullptr;
if (!!DoActionClass)
{
doAction = NewObject<UCDoAction>(this, DoActionClass);
doAction->BeginPlay(InOwner, attachment, equipment, DoActionDatas, HitDatas);
if (!!attachment)
{
attachment->OnAttachmentBeginCollision.AddDynamic(doAction, &UCDoAction::OnAttachmentBeginCollision);
attachment->OnAttachmentEndCollision.AddDynamic(doAction, &UCDoAction::OnAttachmentEndCollision);
attachment->OnAttachmentBeginOverlap.AddDynamic(doAction, &UCDoAction::OnAttachmentBeginOverlap);
attachment->OnAttachmentEndOverlap.AddDynamic(doAction, &UCDoAction::OnAttachmentEndOverlap);
}
if (!!equipment)
{
equipment->OnEquipmentBeginEquip.AddDynamic(doAction, &UCDoAction::OnBeginEquip);
equipment->OnEquipmentUnequip.AddDynamic(doAction, &UCDoAction::OnUnequip);
}
}
UCSubAction* subAction = nullptr;
if (!!SubActionClass)
{
subAction = NewObject<UCSubAction>(this, SubActionClass);
subAction->BeginPlay(InOwner, attachment, doAction);
}
//웨폰 데이터에 있는 변수들 가져오기
*OutWeaponData = NewObject<UCWeaponData>();
(*OutWeaponData)->Attachment = attachment;
(*OutWeaponData)->Equipment = equipment;
(*OutWeaponData)->DoAction = doAction;
(*OutWeaponData)->SubAction = subAction;
}
- CWeaponComponent
- 웨폰 에셋이 있고, 웨폰 데이터의 내용도 저장 되었으니, 컴포넌트에서 이것을 웨폰 컴포넌트에서 제어한다.
- 결과 : 웨폰 컴포넌트는 한 객체에 하나씩 생기고, 실행될때 웨폰 에셋을 초기화할때 웨폰 데이터도 새로 생기니 공유가 풀리게 된다.
- 헤더 파일 - 웨폰 데이터 클래스 포인터 배열 변수 ‘데이터들’을 선언하고 배열 수를 ‘무기타입 ; 장착 안했음의 int32형’으로 선언한다.
//header
private:
UPROPERTY()
class UCWeaponData* Datas[(int32)EWeaponType::Max];
- CPP 파일 - BeginPlay 함수 확인 - 데이터 에셋 현재 순번에 들어가는 매개변수에 ‘데이터들 : 현재 순번’ 의 주소를 넣는다. - 각 클래스 반환하는 결과는 모드 ‘데이터들 ; 현재 순번’으로 교체한다.
//CPP
void UCWeaponComponent::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
for (int32 i = 0; i < (int32)EWeaponType::Max; i++)
{
if (!!DataAssets[i])
{
//if (i == 1)
//{
// CLog::Log((int64)&DataAssets[i]);
//}
DataAssets[i]->BeginPlay(OwnerCharacter, &Datas[i]);
}
}
}
//ex => 각 함수들에서 return을 이렇게 교체한다.
ACAttachment* UCWeaponComponent::GetAttachment()
{
CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!Datas[(int32)Type], nullptr);
return Datas[(int32)Type]->GetAttachment();
}
- CBTTaskNode_Equip
- 헤더 파일 - 웨폰 컴포넌트의 EWeaponType의 ‘타입’ 변수를 선언하고 EWeaponType의 Max(아무 무기도 선택 안함)으로 초기화한다. - 생성자 , 테스크 실행 , TickTask 함수 선언
//header
private:
UPROPERTY(EditAnywhere, Category = "Weapon")
EWeaponType Type = EWeaponType::Max; //무기 타입 저장
public:
UCBTTaskNode_Equip();
protected:
EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
- CPP 파일 - 생성자에 노드 이름 설정, Tick 노티파이 실행 설정 - ‘Task 실행’ 함수 정의 - ‘컨트롤러’ , ‘ai’ 를 받는 부분은 동일하다. - ‘ai’의 웨폰 컴포넌트를 지정하고 ‘컴포넌트 가져오기’ 함수를 호출한 결과를 ‘웨폰’ 포인트 변수를 선언하고 저장한다. - ‘웨폰’이 비어있다면 ‘노드 결과 : 실패’를 반환한다. - ‘현재 무기 타입’이 ‘무기 타입 : 장착 안함’과 같다면 ‘노드 결과 : 실패’를 반환한다. - ‘현재 무기 타입’이 ‘웨폰’의 ‘타입 가져오기’ 함수 호출 결과와 같다면 : ‘노드 결과 : 성공’을 반환한다. - ‘현재 무기 타입’을 받아 switch 선언 : ‘무기 타입 : 검’의 경우 : ‘무기’의 ‘검 모드 설정’ 함수를 호출하고 조건문을 나간다. - ‘노드 결과 : 진행중’을 반환한다. - TickTask 함수 정의 - ‘컨트롤러’ , ‘ai’, ‘무기’를 받는 부분은 동일하다. - ‘무기’의 ‘Equipment 가져오기’ 함수 호출의 ‘장착됨 변수 가져오기’ 함수 호출의 결과를 bool 포인터 변수 ‘장착됨 확인’ 변수를 선언하고 저장한 다음 ‘상수’로 설정한다. - ‘ai’의 상태 컴포넌트를 지정해서 ‘컴포넌트 가져오기’를 호출한 결과를 상태 컴포넌트 포인트 변수 ‘상태’를 선언하고 저장한다. - ‘장착됨 확인’ 포인터 변수가 true이고, ‘상태’의 ‘기본상태 모드 확인’ 함수 호출 결과가 true 라면 : ‘테스크 정상 종료’ 함수에 ‘오너 컴포 : 트리 , 노드 결과 : 성공’을 넣고 호출한다.
//CPP
UCBTTaskNode_Equip::UCBTTaskNode_Equip()
{
NodeName = "Equip";
bNotifyTick = true;
}
EBTNodeResult::Type UCBTTaskNode_Equip::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::ExecuteTask(OwnerComp, NodeMemory);
ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner());
ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn());
UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai);
CheckNullResult(weapon, EBTNodeResult::Failed);
CheckTrueResult(Type == EWeaponType::Max, EBTNodeResult::Failed);
//
if (Type == weapon->GetType())
{
return EBTNodeResult::Succeeded;
}
switch (Type)
{
case EWeaponType::Sword:
weapon->SetSwordMode();
break;
}
return EBTNodeResult::InProgress;
}
void UCBTTaskNode_Equip::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
ACAIController* controller = Cast<ACAIController>(OwnerComp.GetOwner());
ACEnemy_AI* ai = Cast<ACEnemy_AI>(controller->GetPawn());
UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(ai);
const bool* bEquipped = weapon->GetEquipment()->GetEquipped(); //장착 되었는지 확인하기위해 가져온다.
UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai);
if (*bEquipped && state->IsIdleMode())
{
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
}