공부/Unreal

24.08.29

월러비 2024. 8. 30. 15:48

AI

순찰 경로가 있다면 가져오기

  • CEnemy_AI
    • 이 안에서는 패트롤 패스를 받는 역할만 하고, 패스의 운영은 다른곳에서 진행한다.
    • 헤더 파일 - 순찰 경로를 받아서 저장할 패트롤 패스 클래스의 포인터 변수 ‘순찰 경로’ 선언 - 패트롤 패스 클래스 포인터 함수 ‘패트롤 패스 가져오기’를 선언하고 ‘순찰 경로’ 변수를 반환하는것으로 정의한다.
    //CPP
    //패트롤 패스 받는 역할만 한다. (사용은 다른곳에서 사용)
    //배치된 AI 마다 순찰 경로를 다르게 줄 수도 있으니 EditAnywhere로 설정한다.
    UPROPERTY(EditAnywhere, Category = "Patrol")
    	class ACPatrolPath* PatrolPath; 
    
    FORCEINLINE class ACPatrolPath* GetPatrolPath() { return PatrolPath; }
    
  • CPatrolPath
    • 이동할 순찰 포인트를 가져오거나, 다음 이동 포인트를 업데이트한다.
    • 헤더 파일 - ‘이동할 포인트 가져오기’ 함수 선언
    //header
    public:
    	FVector GetMoveTo(); //이동할 순찰 포인트 가져오기
    
    • CPP 파일 - ‘이동할 포인트 가져오기’ 함수 정의 - ‘곡선’의 ‘곡선 포인트 위치 가져오기’ 함수에 ‘현재 포인트의 순번, 가져올 공간 유형 : 월드’를 넣고 호출한다.
    //CPP
    FVector ACPatrolPath::GetMoveTo()
    {
    	//직선 포인트 위치 가져오기 (여러 포인트 위치(곡선 등)이 있지만 여기서는 직선 포인트만 가져온다.
    	return Spline->GetLocationAtSplinePoint(Index, ESplineCoordinateSpace::World);
    }
    
  • CBTTaskNode_Patrol
    • 순찰 경로가 있을때 Enemy_AI에서 순찰 경로를 받고, 이동 경로를 설정한다.
    • CPP 파일 - 테스크 실행 함수 확인 - 순찰 구역 없을 때 코드 바로 위 확인 - ‘ai’의 ‘순찰 경로 가져오기’ 함수 호출이 null이 아니라면 : ‘ai’의 ‘순찰 경로 가져오기’ 함수 호출의 ‘이동할 포인트 가져오기’ 함수 호출 - ‘비헤이비어’의 ‘패트롤 위치 설정’ 함수에 ‘포인트로 이동’ 변수를 넣고 호출한다.
    //CPP
    //이동 지점 구하기 (순찰 구역 있을 때)
    if (!!ai->GetPatrolPath())
    {
    	//이동 포인트를 가져와서 저장
    	FVector moveToPoint = ai->GetPatrolPath()->GetMoveTo(); 
    	behavior->SetPatrolLocation(moveToPoint); //트리의 블랙보드에 가려는 위치 세팅
    
    	if (bDebugMode)
    	{
    		DrawDebugSphere(ai->GetWorld(), moveToPoint, 10, 10, FColor::Green, false, 5);
    	}
    
    	return EBTNodeResult::InProgress; //끝은 Tick에서 낸다.
    }
    
  • 배치된 Enemy_AI 복사해서 순찰 경로 근처에 놓고 해당 순찰 경로를 디테일에서 설정한다.
  • 결과 : 설정된 순찰 경로로 이동을 시작한다.

순찰 루프이동 및 역방향 이동 설정

  • 정방향 설명
    • 순찰 경로 1 포인트 갯수 : 4
      • 배열 순번 : 0 1 2 3
      • count -1 : 3까지 포인트 순서로 들어간다.
    • 4가 된다면? : 조건을 건너뛰고 총 갯수 -2를 하게 된다.
      • count -2 : 현재 배열 순서 바지막 번호 + 1이니까, 결론적으로는 마지막 순번 하나 이전을 다음 이동 포인트로 잡게 된다.
    • 루프가 켜져있다면? : 마지막 순번보다 큰 수이니 조건을 건너뛰었고, 루프 확인 됐으니 최초 순번인 0번을 가리키게 된다.
    • 역방향이 켜져 있다면? : 총갯수 ~ 0까지 순번이 줄어들면서 이동하게 되고, 0보다 작으면 다음 이동 포인트를 1번부터 시작한다.
      • 역방향 루프라면? : 총 갯수 -1을 하면 최종 배열의 순번이니 그것을 이동 포인트로 잡게 된다.
  • CPatrolPath
    • 헤더 파일 - ‘순찰 순번 업데이트’ 함수 선언
    • CPP 파일 - ‘순찰 순번 업데이트’ 함수 정의 - ‘역방향 확인’ 변수가 true 라면 : ‘포인트 순번’이 0보다 크다면 : ‘포인트 순번’을 하나 줄이고, 반환한다. - ‘곡선’의 ‘루프 확인’ 함수 호출 결과가 true 라면 : ‘포인트 순번’을 ‘카운트’ -1로 설정하고, 반환한다. - 역방향이고, 루프가 아니고, 0이 되었다면 ‘포인트 순번’을 1로 설정한다. - ‘역방향 확인’ 변수를 false로 설정한다. - ‘곡선’의 ‘곡선 포인트들 갯수 가져오기’ 함수를 호출한 결과를 ‘카운트’ 변수를 선언하고 저장한다. - ‘포인트 순번’이 ‘카운트’ - 1보다 작다면 : ‘포인트 순번’을 하나 상승시키고, 반환한다. - ‘곡선’의 ‘루프 확인’ 함수 호출 결과가 true 라면 : ‘포인트 순번’을 0번으로 설정하고, 반환한다. - 총 갯수만큼 순번이 상승했고, 루프도 아니라면 ‘포인트 순번’을 ‘카운트’ -2로 설정한다. - ‘역방향 확인’ 변수를 true로 설정한다.
    //CPP
    void ACPatrolPath::UpdateIndex()
    {
    	int32 count = Spline->GetNumberOfSplinePoints();
    	
    	//역방향 설정
    	if (bReverse)
    	{
    		if (Index > 0)
    		{
    			Index--;
    
    			return;
    		}
    
    		if (Spline->IsClosedLoop())
    		{
    			Index = count - 1;
    
    			return;
    		}
    
    		//역방향이고, 루프 아니고, 0에 도달했다면
    		Index = 1;
    		bReverse = false;
    	}
    	
    	//정방향 설정
    	if (Index < count - 1)
    	{
    		Index++;
    
    		return;
    	}
    
    	if (Spline->IsClosedLoop())
    	{
    		Index = 0;
    
    		return;
    	}
    
    	Index = count - 2; //count가 1 늘어났기에 -2를해야 이전 순서를 다음 목표로 향하게 된다.
    	bReverse = true;
    
    }
    
  • CBTTaskNode_Patrol
    • CPP 파일 - TickTask 함수 확인 - 도착했을때 코드 확인 - ‘ai’의 ‘순찰 경로 가져오기’ 함수 호출이 true 라면 : ‘ai’의 ‘순찰 경로 가져오기’ 함수 호출의 ‘순찰 포인트 업데이트’ 함수 호출한다.
    //CPP
    case EPathFollowingRequestResult::AlreadyAtGoal:
    {
    	if (ai->GetPatrolPath())
    	{
    		//순찰 경로를 계속 확인해야한다.
    		ai->GetPatrolPath()->UpdateIndex();
    	}
    
    	FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
    
    	break;
    }
    

장착 모드

  • 장착 하려고하는 무기타입과, 현재 장착 되어있는 무기 타입이 같다면? : 성공 리턴시키고 액션으로 넘어가야한다.
    • 타입이 달라서 무기를 장착하면 성공 리턴을 해야하나? : 아니다.
      • 장착 완료를 시켜야한다.
  • 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);
    	}
    }
    

공격 실행

  • 공격 시작 전 정지한다.
  • CDoAction
    • 헤더 파일 - ‘엑션 중 확인’ 변수 선언 - ‘엑션 중 가져오기’ 함수를 선언하고 ‘엑션 중 확인’ 변수를 반환하는것으로 정의한다.
    • CPP 파일 - ‘공격 실행 종료’ 함수 확인 - ‘기본 상태 모드 설정’ 함수 호출 전에 ‘엑션 중 확인’ 변수 false로 설정한다.
  • CBTTaskNode_Action
    • 헤더 파일 - 생성자, 테스크 실행, TickTask 함수 선언
    • CPP 파일 - ‘테스크 실행’ 함수 정의 - ‘컨트롤러’ , ‘ai’, ‘무기’ 를 받는 부분은 동일하다. - ‘무기’가 비어있다면 ‘노드 결과 ; 실패’를 반환한다. - ‘무기’의 ‘무기 장착 안함 모드 확인’ 함수 호출 결과가 true 라면 ‘노드 결과 : 실패’를 반환한다. - ‘컨트롤러’의 ‘움직임 정지’ 함수 호출 - ‘무기’의 ‘공격 실행’ 함수 호출 - ‘노드 결과 : 진행중’을 반환한다. - TickTask 함수 정의 - ‘컨트롤러’ , ‘ai’, ‘무기’ 를 받는 부분은 동일하다. - ‘ai’의 상태 컴포넌트를 지정해서 ‘컴포넌트 가져오기’ 함수를 호출한 결과를 상태 컴포넌트 포인트 변수 ‘상태’를 선언하고 저장한다. - ‘체크 확인’ 변수를 선언하고 true로 초기화한다. - ‘상태’의 ‘기본 상태 모드 확인’ 함수 호출 결과를 ‘체크 확인’ 변수에 AND 연산으로 저장한다. - ‘무기’의 ‘DoAction 가져오기’ 함수 호출의 ‘엑션 중 가져오기’ 함수 호출의 결과가 false와 같은지 확인한 결과를 ‘체크 확인’ 변수에 AND 연산으로 저장한다. - ‘체크 확인’ 변수가 true 라면 : ‘정상 종료’ 함수에 ‘오너 컴포 : 트리, 노드 결과 : 성공’을 넣고 호출한다.
    //CPP
    EBTNodeResult::Type UCBTTaskNode_Action::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(weapon->IsUnarmedMode(), EBTNodeResult::Failed);
    
    	controller->StopMovement(); //공격 전에 움직임 정지
    	weapon->DoAction(); //선택된 무기의 공격 모션 실행
    
    	return EBTNodeResult::InProgress;
    
    }
    
    void UCBTTaskNode_Action::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);
    	UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(ai);
    
    	bool bCheck = true;
    	bCheck &= (state->IsIdleMode());
    	bCheck &= (weapon->GetDoAction()->GetInAction() == false);
    
    	if (bCheck)
    	{
    		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
    	}
    
    }
    

AI Enemy 무기 장착 시킬때 에러 사항

  • 적이 무기 장착을 시작하면 플레이어의 무기 장착 동작이 실행되고, 적은 무기가 사라져있다.
  • 이유 : 현재 데이터 에셋 클래스는 모두가 같이 쓰고 있기 때문에 무기의 데이터가 하나의 무기로 취급되어진다.
  • 해결 방법 : 각각 무기 데이터가 생성되도록 해야한다.
    • 현재 : WeaponAsset에서 처음부터 할당을 하고 가져오도록 한다.
    • 바꾼 후 : WeaponData 클래스를 만들어서, 에셋이 첫 시작때 각 클래스들을 초기화시키고 웨폰 데이터에서 가져오도록 한다.

'공부 > Unreal' 카테고리의 다른 글

24.08.30  (0) 2024.08.31
24.08.28  (0) 2024.08.30
24.08.27  (6) 2024.08.28
24.08.26  (1) 2024.08.27
24.08.23  (0) 2024.08.24