무기 시스템 - 활 (이전것에 이어서 시작)
활 장착
- 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개밖에 없다고 판단되어 방향계산이 잘 되지 않는것이다. ⇒ ‘짐벌락 문제’
- 해결법 : 이동 방향과 카메라의 회전값의 평균을 구한다.
- 캐릭터 속도의 회전값 구하기
- 캐릭터 카메라 회전값 구하기
- 두 회전값 평균내서 저장
- 이전 회전값부터 두 회전값 평균까지의 회전에 ‘보간’을 줘서 부드럽게 회전시킨다. ⇒ 이 값의 ‘요’ 값을 ‘방향’으로 삼는다.
- 두 회전값의 평균을 내면서 ‘수직방어’를 하고, 보간까지 하면 애니메이션의 프레임이 끊어지는 문제를 해결하게 된다.
- 이유 : 속도는 캐릭터의 속도를 받지만, 회전값은 카메라의 회전값을 받기 때문에 방향이 일치하지 않게된다.
- 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에서 똑같은 의미로 동작한다.)
- 레벨을 이동했는가
- 에디터를 종료했는가
- 게임을 종료했는가
- 전체 프로그램을 종료했는가