무기 시스템 - 활
줌인 줌아웃 속도 설정
- 커브를 사용해서 빨라졌다 느려졌다를 설정한다.
- 시야각을 부드럽게 90에서 45값으로 설정되도록 한다.
- Curve_Aiming
- 커브 베이스 : 지정된 범위에서 계산한 점들이 보간하여 곡선을 정의한다.
- CurveFloat : 값을 하나만 보간해서 사용한다.
- CurveLinearColor : 값을 네개 보간해서 사용한다.
- CurveVector : 값을 세 개 보간해서 사용한다.
- 상단에서 Key(시간) : 축의 좌표, Value(키 값) : 해당 축에서의 값 을 설정할 수 있다.
- 상단의 가로 세로 스내핑 토글을 활성화하면 설정한 근처의 값으로 설정되도록 한다.
- 키 추가 방법 : 원하는 곳 좌클릭 후 Enter
- Key : 0.0 / Value : 90 ⇒ 시간이 0초일때, 90도
- Key : 20 / Value : 45 ⇒ 시간이 20초일때, 45도
- 즉, 0초에서 20초가 지나는 사이에 각도가 90도에서 45도로 떨어지는 곡선을 만들 수 있다.
- 축의 핀 버튼을 클릭하면 안끈 축만 보고 설정할 수 있다.
- 키 편하게 선택 : 드래그로 선택이 가능하다.
- 상단 탐색 버튼 우측에 있는 ‘딱 맞게 줌 설정’ 버튼을 누르면 모든 키를 볼 수 있는 화면 크기로 설정된다.다.
- 스내핑 토글 우측 ‘큐빅 보간 - 자동 탄젠트’ 클릭 : 선택된 키값들로 탄젠트 곡선을 생성하는 버튼이다.
- 콘텐츠 - 우클릭 - 기타 - 커브 - CurveVector 선택
- 키 생성 - Key : 0.0 / Value : 90 - 키 생성 - Key : 20 / Value : 45 - 키값들 선택 - ‘자동 탄젠트’ 버튼 클릭
- 커브 베이스 : 지정된 범위에서 계산한 점들이 보간하여 곡선을 정의한다.
- CSubAction
- DoAction이나 SubAction의 Tick 함수는 WeaponComponent에서 실행시킨다.
- 헤더 파일 - Tick 함수를 가상화 시켜 선언한다.
//header virtual void Tick(float InDeltaTime) {} //웨폰 컴포넌트에서 콜한다. - CWeaponComponent
- CPP 파일 - Tick 함수 확인 - ‘서브 엑션 가져오기’ 함수 호출의 결과가 null이 아니라면 : ‘서브 엑션 가져오기’ 함수 호출의 ‘TIck’ 함수에 ‘델타 타임’을 넣고 호출한다.
//CPP void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!!GetDoAction()) { GetDoAction()->Tick(DeltaTime); //DoAction의 틱 이벤트 실행 } if (!!GetSubAction()) { GetSubAction()->Tick(DeltaTime); //SubAction의 틱 이벤트 실행 } } - CSubAction_Bow
- 커브는 타임라인 변수가 필요한데, 일반 변수이기에 전방선언이 불가능하여 헤더파일에 헤더를 추가해줘야한다.
- 타임 라인 : 시간에 따라 흘러가는 객체를 제어하는 클래스다.
- 다이나믹 델리게이트는 반드시 ‘변수명’까지 전부 맞춰야한다.
- 헤더 파일 - 커브를 저장할 커브 벡터 클래스의 ‘커브’ 변수 선언 - 타임라인 구조체 변수들을 사용할 ‘타임 라인’ 변수 선언 - 델리게이트에 연결할 ‘조준 활성’ 함수에 ‘결과’ 변수를 받도록 선언한다. - Tick 함수 재정의 - ‘조준 속도’ 변수 선언하고 200.0f로 초기화한다.
//header private: UPROPERTY(EditAnywhere, Category = "Aiming") class UCurveVector* Curve; //커브 저장 UPROPERTY(EditAnywhere, Category = "Aiming") float AimingSpeed = 200.0f; //조준 속도 => 현재 20초 / 200.0f : 0.1초 void Tick(float InDeltaTime) override; private: UFUNCTION() void OnAiming(FVector Output); //다이나믹 델리게이트에 연결할 함수 private: FTimeline Timeline; //헤더에 있는 타임라인 자료형을 변수로 선언- CPP 파일 - 생성자 함수 확인 - ‘커브’ 에셋 가져와서 저장 - ‘BeginPlay’ 함수 확인 - 타임라인 벡터 활성 델리게이션 구조체 자료형의 ‘타임라인’ 변수 선언 - ‘타임라인’의 ‘함수 연결’ 함수에 ‘연결할 함수가 있는 클래스 : 현재 클래스, 함수명 : OnAiming’을 넣고 호출한다. - ‘타임라인’의 ‘보간 벡터 추가’ 함수에 ‘커브벡터, 타임라인 벡터 델리게이션’을 넣어 호출한다. - ‘타임 라인’의 ‘실행 비율 설정’ 함수에 ‘조준 속도’를 넣어 호출한다. - 델리게이션에 연결할 ‘조준 활성’ 함수 정의 - ‘카메라’의 ‘시야각 범위’를 ‘매개변수 벡터값’의 ‘X’ 값으로 설정한다. - Tick 함수 정의 - 부모 함수 호출 - ‘타임 라인’의 ‘틱 타임 라인’ 함수에 ‘매개변수 델타타임’을 넣고 호출한다. - ‘누름’ 함수 확인 - ‘타임라인’의 ‘시작 부터 실행’ 함수를 호출한다.
//CPP FOnTimelineVector timeline; //타임라인 델리게이션 timeline.BindUFunction(this, "OnAiming"); Timeline.AddInterpVector(Curve, timeline); Timeline.SetPlayRate(AimingSpeed); //타임라인 속도 설정 : 조준 속도 void UCSubAction_Bow::Tick(float InDeltaTime) { Super::Tick(InDeltaTime); Timeline.TickTimeline(InDeltaTime); //타임라인의 시간을 가게 한다. } void UCSubAction_Bow::OnAiming(FVector Output) { } - 테스트 결과 : 서브엑션 버튼을 누르면 20초 동안 X의 값이 90에서 45로 떨어지는것을 확인할 수 있다.
활 당겼을때 꺾이게 만들기
- 블렌드 스페이스를 이용하여 기본 상태와 휘어진 메쉬 1프레임을 이용한다.
- 무기의 중심이 ‘루트’로 되어있으니 움직여야할 본을 순차적으로 키로 만들어서 수정해야한다.
- 본의 키를 생성하면 메쉬를 수정할 수 있도록 키가 생성된다.
- 위치, 회전, 스케일 을 수정할 수 있도록 된다.
- 수정은 한번에 눌러서 해도 되지만 헷갈리면 한 요소씩 눌러서 수정할 수 있다.
- 내부 UI는 ‘커브’와 동일하다.
- 애니메이션 블루프린트는 ‘스켈레톤’을 기준으로 생성된다.
- 즉, 무기 스켈레톤과 캐릭터 스켈레톤은 다른 애니메이션을 사용하니 ‘애님 인스턴스’도 무기 스켈레톤을 사용하는것으로 따로 생성해야한다.
- 휘는 애니메이션 생성
- 콘텐츠 - 활 기본 애니메이션 복사 - 타임에서 우클릭 - 복사본의 1프레임만 남긴다. - 원하는 본 클릭 - 상단 ‘키’ 버튼 클릭 - 수정
- BS_Aiming
- 생성
- 0 ~ 1로 놓아도 좋다.
- 가로축 : Aiming - 최솟값 : 0 - 최대값 : 100 - 그리드 분할 : 1 - 0일때 ‘Bow_Idle’ - 100일때 ‘Bow_Pulled’
- Curve_Aiming
- Y값 사용 : 활 애니메이션의 들어갈 Aiming 가로축 값 0 ~ 100까지의 값에 사용한다.
- 키 생성 - Key : 0.0 / Value : 0.0 - 키 생성 - Key : 20 / Value : 100 - ‘자동 탄젠트’ 클릭
- CSubAction_Bow
- 커브값은 외부(CAnimInstance)에서 사용할때 ‘매 프레임마다’ 접근해서 가져올 수 있고, ‘포인터’를 이용하는 등 여러가지 방법이 있다.
- 지금은 ‘포인터 변수’를 생성해서 그 안에 ‘어던 주소값’을 받고, 그곳에 ‘커브’의 ‘Y값 : Aiming’을 넣을것이다.
- 헤더 파일 - ‘벤딩 : 당기다’ 포인터 변수 선언
- CPP 파일 - ‘조준 활성’ 함수 확인 - ‘벤딩’ 변수가 비어있지 않다면 : 포인터 변수 ‘벤딩’에 매개변수 ‘결과값’의 Y를 저장한다.
- 커브값은 외부(CAnimInstance)에서 사용할때 ‘매 프레임마다’ 접근해서 가져올 수 있고, ‘포인터’를 이용하는 등 여러가지 방법이 있다.
- CAnimInstance_Bow
- 애니메이션에 들어갈 Aiming 가로축 값은 SubAction_Bow의 ‘타임 라인 커브’를 이용하면 된다.
- 줌 할때 당겨야하니까
- Curve_Aiming의 Y값을 이용한다. / X 값은 줌의 시야각에 사용했다.
- 헤더 파일 - 블루프린트 읽기 전용으로 ‘벤딩’ 변수 선언 - 벤딩 값을 가져올 수 있도록 포인터 함수 ‘벤딩 가져오기’ 함수를 선언하고 ‘벤딩’의 ‘주소’를 반환하도록 정의한다. - ‘애니메이션 실행’ 함수와 ‘애니메이션 업데이트’ 함수를 재정의 선언한다.
//header protected: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation") float Bending; //서브 엑션 보우에 있는 벤딩값을 가져와 저장할 변수 public: FORCEINLINE float* GetBending() { return &Bending; } public: void NativeBeginPlay() override; void NativeUpdateAnimation(float DeltaSeconds) override;- CPP 파일 - ‘애니메이션 실행’ 함수와 ‘애니메이션 업데이트’ 함수를 부모 호출만 한다.
//CPP void UCAnimInstance_Bow::NativeBeginPlay() { Super::NativeBeginPlay(); } void UCAnimInstance_Bow::NativeUpdateAnimation(float DeltaSeconds) { Super::NativeUpdateAnimation(DeltaSeconds); } - 애니메이션에 들어갈 Aiming 가로축 값은 SubAction_Bow의 ‘타임 라인 커브’를 이용하면 된다.
- ABP_Bow
- 생성
- 블렌드 스페이스 배치 - Aiming 값 핀을 끌고 ‘Bending’ 변수 검색 - 최종 애니메이션 노드에 연결
- CAttachment_Bow
- Attachment의 ‘스켈레탈 메쉬’에 애니메이션이 들어오는 것이다.
- 벤딩의 값은 애님 인스턴스가 객체화 된 곳에 저장되어있으니 그것을 빼와서 사용한다.
- 이 클래스에서 ‘애님 인스턴스’를 생성하고, 생성한 객체를 스켈레탈 메쉬에 할당한다.
- 해당 애님 인스턴스를 가져올 수 있는데 부모의 클래스로 되어있기에 ‘상속하여 생성한’ 애님 인스턴스 클래스로 ‘캐스팅’ 해줘야한다.
- 헤더 파일 - 포인터 자료형으로 ‘애님 인스턴스 _ 벤딩 가져오기’ 함수 선언
//header public: float* GetAnimInstance_Bending(); //애님 인스턴스 벤딩 가져오기- CPP 파일 - 생성자 함수 확인 - 생성한 <CAnimInstance_Bow> 클래스를 ‘애님 인스턴스’ 변수로 선언한다. - <CAnimInstance_Bow>클래스로 ‘클래스 가져오기’ 함수를 호출하고 ‘애님 인스턴스 주소, 해당 클래스로 파생한 애니메이션 블루프린트 파일 레퍼런스 주소’를 넣고 호출한다. - ‘스켈레탈 메쉬’의 ‘애님 인스턴스 클래스 설정’ 함수에 ‘애님 인스턴스’를 넣고 호출한다. - ‘애님 인스턴스 _ 벤딩 가져오기’ 함수 정의 - ‘스켈레탈 메쉬’의 ‘애님 인스턴스 가져오기’ 함수 호출하고 부모 애님 인스턴스 클래스이기 때문에 할당된 애님 인스턴스 클래스로 ‘캐스팅’ 해야한다. ⇒ 해당 결과를 생성한 애님 인스턴스 클래스 자료형의 ‘활’ 변수를 선언하고 저장한다. - ‘활’이 비어있지 않다면 : ‘활’의 ‘벤딩 가져오기’ 함수를 호출한다.
//CPP //Anim_Bow 관련 설정 TSubclassOf<UCAnimInstance_Bow> animInstance; CHelpers::GetClass<UCAnimInstance_Bow>(&animInstance, "AnimBlueprint'/Game/Weapons/Bow/ABP_Bow.ABP_Bow_C'"); SkeletalMesh->SetAnimInstanceClass(animInstance); float* ACAttachment_Bow::GetAnimInstance_Bending() { //스켈레탈의 애님 인스턴스 가져오기 UCAnimInstance_Bow* bow = Cast<UCAnimInstance_Bow>(SkeletalMesh->GetAnimInstance()); if (!!bow) { return bow->GetBending(); } return nullptr; } - CSubAction_Bow
- CPP 파일 - BeginPlay 함수 확인 - 매개변수로 들어온 Attachment를 파생한 Attachment_Bow가 필요하기 때문에 해당 클래스로 ‘캐스팅’을 하고 ‘Attachment’를 넣은 결과를 해당 클래스 자료형의 ‘활’ 변수를 선언하고 저장한다. - ‘활’이 비어있지 않다면 : ‘활’의 ‘애님 인스턴스 _ 벤딩 가져오기’ 함수 호출 결과를 ‘벤딩’에 저장한다.
//CPP ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment); if (!!bow) { Bending = bow->GetAnimInstance_Bending(); }
화살 손에서 분리
- Arrow에 함수를 하나 놓고, 발사 하라는 입력이 들어오면 ‘발사’ 함수를 호출할 것이다.
- projectile 날아가는 속도 계산 : 초기 속도 * 속도 벡터(날아가는 방향)
- 활 모션은 UpperBody 본 슬롯을 사용할 수 없다.
- 상하 조절은 AO_Bow에서 하지만, 활 모션 뒤에 UpperBody가 굽혀진 허리를 다시 조정하기 때문에 정면 모션으로 되돌아간다.
- 해결책 : AO_Bow 전에 발사 모션을 넣어 쏘는 각도가 제대로 들어가게 한다.
- CArrow
- 헤더 파일 - ‘발사’ 함수를 선언하고 ‘전방’방향 주소 변수를 넣고, 값이 복사되지 않도록 ‘상수’로 선언한다.
//header //발사 시키기 함수 void Shoot(const FVector& InForward);- CPP 파일 - ‘발사’ 함수 정의 - ‘발사체의 ‘속도’ 변수에 매개변수 ‘전방 방향’ * ‘발사체’의 ‘초기 속도’ 계산을 저장한다. - ‘발사체’의 ‘활성 설정’을 true로 설정한다.
//CPP void ACArrow::Shoot(const FVector& InForward) { //전방 벡터 * 초기 속도 Projectile->Velocity = InForward * Projectile->InitialSpeed; Projectile->SetActive(true); //날아가기 활성 } - CDoAction_Bow
- 손에 붙은 화살을 분리시켜서 날아가게 만든다.
- 칼 같은 무기는 붙은 곳에서의 ‘상대 간격’을 유지하며 분리시키지만, 활은 그렇게 하면 안된다.
- 화살의 상대간격은 (0, 0, 0)부터 오른손의 화살까지 벌어진 살짝의 간격이기 때문에, 상대 간격으로 분리시키면 (0, 0, 0)지점에서 살짝 떨어진 지점으로 지동하게 된다.
- 즉, 떨어진 위치가 아니라 0지점(자닥에서 살짝 떨어진 지점)으로 순간이동된다.
- modity 수정 체크 : true 안하면 손에서 안떨어진다.
- 헤더 파일 - ‘엑션’ 함수, ‘엑션 시작’ 함수, ‘엑션 종료’ 함수 재정의 선언 - 어딘가에 붙은 화살을 찾기 위해 화살 클래스 포인터의 ‘붙은 화살 가져오기’ 함수를 선언한다.
//header void DoAction() override; void Begin_DoAction() override; void End_DoAction() override; class ACArrow* GetAttachedArrow(); //손에 붙은 화살을 찾아 반환할 함수- CPP 파일 - ‘엑션’ 함수 정의 - ‘상태’의 ‘기본 상태 확인’ 함수 호출이 false인지 확인한다. - 부모 함수 호출 - ‘엑션 데이터’의 ‘0’번 순서의 ‘엑션 실행’에 ‘오너 캐릭터’를 넣고 호출한다. - ‘엑션 종료’ 함수 정의 - ‘붙은 화살 가져오기’ 함수 정의 - ‘화살들’에 있는 요소들을 하나씩 꺼내 ‘화살’에 저장하고 반복시킨다 : ‘화살’의 ‘붙은 부모 엑터 가져오기’ 함수를 호출한 결과가 null이 아니라면 : 붙은 ‘화살’을 반환하고, 없다면 null을 반환한다. - ‘엑션 시작’ 함수 정의 - 화살 포인터 변수 ‘화살’을 선언하고 ‘붙은 화살 가져오기’ 함수를 호출한 결과를 저장한다. - ‘화살’의 ‘엑터로부터 분리’ 함수에 ‘위치 분리 규칙’의 ‘분리 규칙 : 월드, 모디파이 수정 : true’를 넣고 호출한다.
//CPP void UCDoAction_Bow::DoAction() { //조준한 상태에서만 쏘게 한다. CheckFalse(State->IsIdleMode()); //CheckFalse(State->IsSubActionMode()); Super::DoAction(); DoActionDatas[0].DoAction(OwnerCharacter); } void UCDoAction_Bow::Begin_DoAction() { Super::Begin_DoAction(); ACArrow* arrow = GetAttachedArrow(); //손에 붙은 화살 찾기 arrow->DetachFromActor(FDetachmentTransformRules(EDetachmentRule::KeepWorld, true)); } ACArrow* UCDoAction_Bow::GetAttachedArrow() { for (ACArrow* arrow : Arrows) { if (!!arrow->GetAttachParentActor()) { return arrow; } } return nullptr; } - DA_Bow
- 공격을 하기 위해서 공격 데이터를 넣어야 한다.
- 활 공격 몽타주 생성
- 본 슬롯 설정
- 노티파일 설정 : ‘공격 시작’ , ‘공격 종료’
- 엑션 데이터에 몽타주 할당
- 캐릭터 스켈레톤
- 활 전용 ‘슬롯’ 생성
- ABP_Character
- 활 레이어 확인 - AO_Bow 이전 연결 핀 해제 - 해제한 핀 끌고 ‘Save Pose’ 검색 후 이름 변경 - 해당 포즈 검색 후 복사해서 2개로 만들기 - ‘본 별로 레이어로 블렌딩’ 검색 후 ‘Base Pose’에 ‘활 포즈’ 노드 1개 연결 - ‘defaultSlot’ 검색 후 남은 ‘활 포즈’ 노드와 연결, 슬롯을 ‘활 슬롯’으로 변경 - 본 별로 레이어로 블렌딩 노드의 ‘BlendPoses’와 슬롯 노드 연결 - 본 별로 레이어로 블렌딩 노드 클릭 - 레이어 설정, 인덱스, 분기 필터 배열 추가, 인덱스, 본 이름 : spine_01, 뎁스 : 1, ‘메시 스페이스 회전’ 체크 - 설정이 끝나면 AO_BOw와 연결한다.
인터폴레이션
- 이징(Easing) : 0 ~ 1까지 인할지 아웃할지 결정하는 함수
- 수식으로 정해져있다. ⇒ 구글에 ‘이징’을 검색해서 나온 함수 시트에서 원하는 가속의 형태 함수를 찾을 수 있다.
- 커브 : 어느 구간에서 어느 구간까지 가는데 어떤 모양과 속도로 가는지 정할 수 있게하는 클래스다.
- 빨라졌다가, 원래 속도로 갔다가, 다시 빨라지도록 하기 위해서 사용했다.
C#과 C++의 레퍼런스 타입 차이점
- C#과 C++은 Value와 Ref 타입의 변수 타입이 있다.
- C++에서 const & 변수 선언이 가능하지만, C#에서는 불가능하다.