공부/Unreal

24.08.16

월러비 2024. 8. 20. 13:06

무기 시스템 - 활 (이전것에 이어서 시작)

활 장착

  • C++ 클래스 파생 블루프린트 파일을 생성하는 파일은 직접 설정하는 변수를 ‘DefaultOnly’ 메크로로 설정해서 작업한다.
  • Attachment에서도 ‘장착중인지’ 명령을 받을 수 있도록 해야한다.
    • ‘델리게이트’ 사용
  • 메크로를 사용했더라도 자식에서 다시 작성하지 않아도 된다.
    • 메크로도 상속되기 때문이다.
  • 캐릭터 스켈레톤
    • 손에 활을 붙일 소켓 추가
  • 활 애니메이션
    • 활 장착 애니메이션 몽타주 생성
    • 정의한 ‘장착’ 노티파이 스테이트 추가
  • DA_Bow
    • 장착 데이터 확인 - 활 장착 몽타주 할당
  • ABP_Character
    • 활 블렌드 스페이스 스테이트 추가 - 포즈 저장 - (WeaponType) 블렌드 노드 확인 - Bow 추가 - ‘활 포즈’ 검색 후 연결
  • CAttachment
    • 헤더 파일 - 델리게이트 ‘장착 시작 활성’ 과 ‘장착 해제 확성’ 함수를 ‘가상화’한다.
      • Attacchment를 상속한 Attachment_Bow에서 사용할 수 있게 하기 위해서이다.
  • CAttachment_Bow
    • 헤더 파일 - 델리게이트 ‘장착 시작 활성’ 과 ‘장착 해제 확성’ 함수 재정의한다.
    //header
    public:
    	void OnBeginEquip_Implementation() override;
    	void OnUnequip_Implementation() override;
    
    • CPP 파일 - 델리게이트 ‘장착 시작 활성’ 함수 정의 - 정의한 ‘붙이기’ 함수에 활을 붙일 ‘손의 소켓 이름’을 넣고 호출한다. - 델리게이트 ‘장착 해제 활성’ 함수 정의 - 정의한 ‘붙이기’ 함수에 활을 붙일 ‘홀스터 소켓 이름’을 넣고 호출한다.
    //CPP
    void ACAttachment_Bow::OnBeginEquip_Implementation()
    {
    	Super::OnBeginEquip_Implementation();
    
    	AttachTo("Hand_Bow_Left"); //활 왼손에 장착
    
    }
    
    void ACAttachment_Bow::OnUnequip_Implementation()
    {
    	Super::OnUnequip_Implementation();
    
    	AttachTo("Holster_Bow");
    }
    
  • CWeaponComponent
    • 헤더 파일 - 활 모드 함수 선언
    • CPP 파일 - 다른 모드 함수 복붙해서 ‘활’로 변경한다.
  • CPlayer
    • CPP 파일 - ‘인풋 컴포넌트’ 함수 확인 - ‘액션 연결’ 함수에 ‘액션 이름, 액션 상태, 액션에 연결할 함수가 있는 클래스, 액션에 연결할 함수 명’을 넣고 호출한다.
    //CPP
    PlayerInputComponent->BindAction("Bow", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetBowMode);
    

캐릭터 시점이 수직일때 전달되는 값의 이상으로 애니메이션에 생기는 문제 해결

  • 캐릭터 시점이 아래로 수직 ⇒ 애니메이션의 모션과 방향이 맞지 않고, 프레임이 뚝뚝 끊긴다.
    • 이유 : 속도는 캐릭터의 속도를 받지만, 회전값은 카메라의 회전값을 받기 때문에 방향이 일치하지 않게된다.
      • X축이 회전하면서 Z축과 겹치니 축이 2개밖에 없다고 판단되어 방향계산이 잘 되지 않는것이다. ⇒ ‘짐벌락 문제’
    • 해결법 : 이동 방향과 카메라의 회전값의 평균을 구한다.
      1. 캐릭터 속도의 회전값 구하기
      2. 캐릭터 카메라 회전값 구하기
      3. 두 회전값 평균내서 저장
      4. 이전 회전값부터 두 회전값 평균까지의 회전에 ‘보간’을 줘서 부드럽게 회전시킨다. ⇒ 이 값의 ‘요’ 값을 ‘방향’으로 삼는다.
    • 두 회전값의 평균을 내면서 ‘수직방어’를 하고, 보간까지 하면 애니메이션의 프레임이 끊어지는 문제를 해결하게 된다.
  • CAnimInstance
    • 헤더 파일 - ‘이전 회전값’ 변수 선언
    //header
    private:
    	FRotator PrevRotation; //보간을 위해 이전 회전값 저장
    
    • CPP 파일 - ‘오너 캐릭터’의 ‘속도 가져오기’ 함수 호출의 ‘ToOrientationRotator’ 함수를 호출하여 ‘회전자’ 변수를 선언하고 저장한다. - ‘오너 캐릭터’의 ‘카메라 회전값 가져오기’ 함수를 ‘회전자 2’ 변수를 선언하고 저장한다. - ‘키즈멧 메스’의 ‘회전값 평균 일반화’ 함수에 ‘회전자, 회전자2’를 넣고 ‘델타’ 변수를 선언하고 저장한다. - ‘키즈멧 메스’의 ‘선형 보간’에 ‘이번 회전값, 두 회전값 평균(델타), 델타초(매개변수), 25(보간 속도)’를 넣고 호출한 결과를 ‘이전 회전값’에 저장한다. - ‘이전 회전값’의 ‘요’ 회전값을 ‘방향’ 변수에 저장한다.
    //CPP
    //방향 구하기 버전 2 => 속도는 캐릭터가 맞지만, 시야는 카메라이기 때문에 카메라의 방향을 구해야한다.
    FRotator rotator = OwnerCharacter->GetVelocity().ToOrientationRotator();
    FRotator rotator2 = OwnerCharacter->GetControlRotation();
    FRotator delta = UKismetMathLibrary::NormalizedDeltaRotator(rotator, rotator2); //두 방향의 평균
    
    //더 부드러운 회전을 위해 보간 => 짐벌락 방지를 위해 사용한다.
    //축이 겹치더라도 방어가 된다.
    PrevRotation = UKismetMathLibrary::RInterpTo(PrevRotation, delta, DeltaSeconds, 25);
    Direction = PrevRotation.Yaw;
    

화살 소환 - 밑준비

  • 화살은 캡슐 콜라이더 밑에 스테틱 메쉬를 놓는다.
    • 화살의 중심은 화살 깃 끝부분이고, 화살의 충돌지점은 화살촉이다.
    • 메쉬의 위치를 움직여 촉부분에 충돌체를 놓는다.
  • 캡슐 콜리전은 구와 원통이 합쳐진 형태로, 캡슐 반경을 먼저 변경후 캡슐 절반 높이를 반경과 같은 길이로 설정하면 ‘구’형태가 된다.
  • 발사체 움직임 컴포넌트
    • 초기 속도 : 발사체의 기본 속도
    • 발사체 중력 스케일 : 발사체가 받는 중력의 크기
    • 최대 속도 : 가속도를 받게끔 구현했을때 최대속도 이상의 속도를 받지 않게하는 값이다.
    • 발사체 바운스 : 발사체의 충돌때 튕기는 기능을 사용할때 설정하는 옵션들이 모여져있다.
      • 바운스 필요 : 튕기는 기능 필요시 체크하는 옵션이다.
      • 바운스 각도 마찰에 영향 : 튕기는 기능을 사용할때 마찰값을 사용할것인지의 여부를 결정하는 옵션이다.
      • 탄력성 : 튕길때의 힘을 설정하는 옵션이다.
      • 마찰 : 충돌했을때 힘이 줄어드는 값을 설정하는 옵션이다.
  • DoAction_Bow에서 화살 관련 기능들을 정의한다.
    • 생성 및 부착
    • SpawnActorDiffered를 이용해서 생성만 해놓고, 충돌하면 안되는 객체들을 값으로 설정한 다음 사용할 것이다.
      • 생성하자마자 발사되어 충돌을 일으키거나, 장착하자마자 플레이어와 충돌하려는 문제를 방지하기 위해서 사용하는 것이다.
  • 액터 클래스를 상속받는 CArrow 클래스 생성

화살 소환

  • 화살은 DoAction에서 관리한다.
    • DoAction을 파생한 활 전용 클래스를 만든다.
    • 활을 장착하면 화살을 등장시킨다.
  • 파생 클래스를 만둘기 위해서 UCLASS 매크로 선언 안에 ‘Blueprintable’를 선언해야지 파일을 생성할 수 있다.
  • CArrow
    • 헤더 파일 - 캡슐 컴포넌트 변수 선언 - 발사체 무브먼트 컴포넌트 변수 선언 - 엑터 클래스의 ‘무시들’ 배열 변수 선언 - ‘무시할 엑터 추가’ 함수에 ‘엑터’를 넣고 선언 후 ‘무시들’ 배열에 ‘매개변수 엑터’를 넣은 ‘추가’ 함수를 호출하는 것으로 ‘정의’한다.
    //header
    public:
    	UPROPERTY(VisibleAnywhere)
    		class UCapsuleComponent* Capsule; //화살촉에 넣을 콜라이더
    
    	UPROPERTY(VisibleAnywhere)
    		class UProjectileMovementComponent* Projectile; //발사시키는 컴포넌트
    		
    public:
    	FORCEINLINE void AddIgnoreActor(AActor* InActor) { Ignores.Add(InActor); }
    
    TArray<AActor*> Ignores; //충돌 무시 모음
    
    • CPP 파일 - ‘캡슐’ 변수에 저장할 ‘컴포넌트 생성’ 함수 호출 - ‘발사체’ 변수에 저장할 ‘컴포넌트 생성’ 함수 호출 - ‘발사체’의 ‘발사체 중력 크기’ 변수를 0.0f로 설정한다. - ‘캡슐’의 ‘콜리전 프리셋 설정’ 함수에 ‘막힌 오브젝트 전체’ 옵션을 넣어 호출한다. - ‘캡슐’의 ‘바디 인스턴스’의 ‘시뮬레이션 중 히트 이벤트 발생’ 변수를 true로 설정한다.
    //CPP
    ACArrow::ACArrow()
    {
    	CHelpers::CreateComponent<UCapsuleComponent>(this, &Capsule, "Capsule"); //얘를 루트로 놓을거다.
    	CHelpers::CreateActorComponent<UProjectileMovementComponent>(this, &Projectile, "Projectile");
    
    	Projectile->ProjectileGravityScale = 0.0f; //중력 설정
    
    	//BodyInstance : 충돌체 설정에 관한 대부분의 기능들이 모여져있는 '피직스 바디'다.
    	Capsule->BodyInstance.bNotifyRigidBodyCollision = true; //시뮬레이션 중 히트 설정
    	Capsule->SetCollisionProfileName("BlockAll"); //충돌체 콜리전 프리셋 설정
    
    }
    
  • CDoAction_Bow
    • 생성
    • 원래 컴포넌트 생성이나, 화살 생성같은 동작을 실행하기 전에 월드가 삭제되는지(게임이 종료되는지) 확인해야 한다.
    • 헤더 파일 - DoAction의 BeginPlay 재정의 선언 - Tick 재정의 선언 - 스켈레탈 메쉬와 포저블 메쉬 컴포넌트 변수 선언 - 화살 클래스를 받아올 수 있도록 ‘클래스 제한’으로 ‘ArrowClass’를 받을 ‘화살 클래스’ 변수 선언 - 화살 생성관련 코드를 정의할 ‘화살 생성’ 함수 선언
    //header
    private:
    	UPROPERTY(EditDefaultsOnly, Category = "Arrow")
    		TSubclassOf<class ACArrow> ArrowClass; //화살 클래스 저장
    
    private:
    	void CreateArrow(); //화살 생성
    
    void BeginPlay
    (
    	class ACharacter* InOwner,
    	class ACAttachment* InAttachment,
    	const TArray<FDoActionData>& InDoActionDatas,
    	const TArray<FHitData>& InHitDatas
    ) override;
    
    void Tick(float InDeltaTime) override;
    
    public:
    	void OnBeginEquip() override;
    
    private:
    	class USkeletalMeshComponent* SkeletalMesh;
    	class UPoseableMeshComponent* PoseableMesh;
    	
    
    • CPP 파일 - BeginPlay 함수 정의 - 부모 BeginPlay 호출 - 스켈레탈 메쉬와 포저블 메쉬의 ‘컴포넌트 가져오기’ 함수 호출에 ‘매개변수 Attachment’를 넣어 저장한다. - Tick 함수 정의 - 부모 Tick 호출 - ‘화살 생성’ 함수 정의 - ‘월드’의 ‘게임이 종료되는지 확인’ 변수가 true 라면 : 함수를 실행하지 않는다. - ‘화살 클래스’가 null이라면 : 실행하지 않는다. - 화살을 생성할 위치를 저장할 ‘위치’ 변수 선언 - ‘월드’의 ‘엑터 생성 후 대기’ 함수를 ‘화살 클래스’로 지정하고 ‘지정한 클래스 변수, 생성 위치, 오너 : null, 데미지 입힌 캐릭터 폰 : null, 스폰 직후 충돌 처리 방법 : 항상 스폰’을 넣고 호출한 결과를 화살 클래스 포인터 변수 ‘화살’을 선언하고 저장한다. - ‘화살’의 ‘무시 엑터 추가’ 함수에 ‘오너 캐릭터’를 넣고 호출한다. - ‘붙는 규칙’ 구조체에 ‘붙는 규칙 : KeepRelative, true’를 넣고 붙는 규칙 클래스의 ‘규칙’ 변수를 선언하고 저장한다. - ‘화살’의 정의한 ‘컴포넌트에 붙이기’ 함수에 ‘오너캐릭터의 메쉬 가져오기 함수 호출, 붙는 규칙, 붙일 소켓 이름’을 넣고 호출한다. - 게임 플레이 스테틱 클래스의 ‘엑터 스폰 종료’ 함수에 ‘생성할 변수, 생성 위치’를 넣고 호출한다. ⇒ 이것을 호출해야 deferred 함수의 실행이 종료된다. - ‘장착 시작 활성’ 함수 재정의 - 부모 함수 호출 - ‘화살 생성’ 함수 호출
    //CPP
    void UCDoAction_Bow::CreateArrow()
    {
    	//bIsTearingDown : 게임이 종료중인지 확인
    	CheckTrue(World->bIsTearingDown);
    	CheckNull(ArrowClass);
    
    	FTransform transform;
    	ACArrow* arrow = World->SpawnActorDeferred<ACArrow>(ArrowClass, transform, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
    	CheckNull(arrow); //여기까지는 생성만 하고, 배치는 안했다.
    
    	arrow->AddIgnoreActor(OwnerCharacter); //오너캐릭터 충돌 제외
    
    	//붙이기 설정
    	FAttachmentTransformRules rule = FAttachmentTransformRules(EAttachmentRule::KeepRelative, true); //붙는 규칙
    	arrow->AttachToComponent(OwnerCharacter->GetMesh(), rule, "Hand_Bow_Right_Arrow"); 
    
    	//배치 설정 (엑터, 확정된 배치 위치)
    	UGameplayStatics::FinishSpawningActor(arrow, transform);
    
    }
    
    void UCDoAction_Bow::OnBeginEquip()
    {
    	Super::OnBeginEquip();
    
    	CreateArrow();
    
    }
    
  • CDoAction
    • 헤더 파일 - ‘장착 시작 활성’과 ‘장착 해제 활성’ 함수 가상화 선언 후 정의
    //header
    public:
    	UFUNCTION()
    		virtual void OnBeginEquip() {}
    
    	UFUNCTION()
    		virtual void OnUnequip() {}
    
  • CWeaponAsset
    • CPP 파일 - BeginPlay 함수 확인 - DoAction 클래스 없는지 확인하는 코드 확인
    //CPP
    if (!!DoActionClass)
    	{
    		DoAction = NewObject<UCDoAction>(this, DoActionClass);
    		DoAction->BeginPlay(InOwner, Attachment, 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);
    		}
    	}
    
  • BP_CDoAction_Bow
    • 생성
    • Arrow 변수에 CArrow 클래스 할당
  • DA_Bow
    • DoAction Class 변수에 DoAction_Bow 할당

스켈레톤 메쉬에 붙인 메테리얼 인스턴스가 적용되지 않는 문제

  • 스켈레톤 메쉬에 메테리얼을 붙일때는 옵션을 하나 체크해줘야한다.
  • 메테리얼 원본 파일 - 디테일 - 사용 - ‘스켈레탈 메시와 사용됨’ 체크 - ‘Equipment’가 비어있지 않다면 : ‘Equipment’의 ‘Equipment 장착 실행 활성’ 함수에 ‘함수 연결’ 함수에 ‘실행할 함수 클래스, 연결할 함수’를 넣고 호출한다. , ‘Equipment’의 ‘Equipment 장착 해제 활성’ 함수에 ‘함수 연결’ 함수에 ‘실행할 함수 클래스, 연결할 함수’를 넣고 호출한다.

AActor LifeCycle

  • Play In Editor : 에디터의 플레이 버튼을 눌렀을 때 활성
  • LoadMap : 맵이 바뀌어서 Load 할때 활성
  • AddToWorld : 엑터 배치같이 월드에 엑터를 임의로 끌어서 배치했을때 활성
  • BeginPlay : 플레이 버튼을 누르거나, 맵이 로드되거나, 월드에 엑터가 배치 된 후 게임이 실행될때 활성
  • 사각형 3개가 합쳐진 형태의 노드 : 임의로 재정의할 수 있는 함수를 의미한다.
  • SpawnActor : 호출하면 화면에 바로 등장한다.
  • SpawnActorDefferd : FinishSpawningActor가 호출되지 않으면 생성만 하고 배치하지 않고 대기한다.
    • 사용 이유 : 생성하고 어떤 값을 설정하고 배치하려 할때 사용한다.
      • 생성하고 게임을 실행할때 어떤 값을 설정하려할때 사용한다.
      • 생성자는 ‘에디터 관련’이고, 디퍼드는 ‘게임 관련’ 설정 방식이다.
      • 처음에 객체를 생성하고, 배치가 되고, BeginPlay가 되는데 이때 배치를 지연시키고, 값을 세팅시켜놓고, BeginPlay를 사용하기 위해 사용한다.
    • ignores로 충돌 배제 같은거
  • The application is running and the Actor is Ticking : 게임이 종료되는 조건
    • EndPlay Reason의 조건들이다.
    • 엑터가 파괴되었을때 (= 엑터의 생명주기가 끝났을때 → EndPlay Reason에서 똑같은 의미로 동작한다.)
    • 레벨을 이동했는가
    • 에디터를 종료했는가
    • 게임을 종료했는가
    • 전체 프로그램을 종료했는가

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

24.08.20  (1) 2024.08.23
24.08.19  (0) 2024.08.20
24.08.13  (0) 2024.08.16
24.08.12  (0) 2024.08.16
24.08.09  (0) 2024.08.10