무기시스템 - 활
- 날아가는 화살도 ‘배열’로 관리해야한다.
- 배열에 화살 하나를 넣었다.
- 발사했으면 화살 하나를 더 생성해서 빈 손에 쥐어야한다.
- 화살이 어딘가에 부딪히면 없앤다.
- 화살은 몇개가 생성될지 모르니 TArray를 사용한다.
- 중간의 값이 삭제되면 뒤에있는 값을이 앞으로 땡겨와진다.
- 가변형 배열을 반복문으로 삭제하는것은 삭제하고 땡기면 안된다.
- 땡겨진 빈 공간 다음것을 가리키게 되기 때문이다.
- 해결법 : 순서를 반대로 샌다.
- 손에 붙어있는 화살만 찾아서 장착 해제때 제거한다.
- 이미 날아간것은 삭제하면 안된다.
화살 생성 후 바로 날아가지 못하도록 설정
- CArrow
- 발사체 생성 직후 바로 날아가지 않게 UProjectileMovementComponent를 비활성화 시켜야한다.
- CPP 파일 - ‘발사체’의 ‘활성화 설정’ 함수를 false로 설정하고 호출한다.
//CPP void ACArrow::BeginPlay() { Super::BeginPlay(); //시작하면 투사체를 비활성화한다. Projectile->SetActive(false); }
화살 장착 해제시 사라지도록 설정
- 생성한 화살을 저장한 배열에서, ‘손에 붙어있는 화살’만을 찾아 삭제한다.
- 배열의 번호로 해당 요소를 탐색할 것이니 Num() 함수 호출에서 -1을 해줘야한다.
- CDoAction_Bow
- 헤더 파일 - ‘장착 해제 활성’ 함수 재정의 - 생성한 화살을 저장할 ‘화살들’ 가변 배열 변수 선언
//header void OnUnequip() override; private: TArray<class ACArrow*> Arrows; //생성된 화살을 저장하는 배열- CPP 파일 - ‘화살 생성’ 함수 확인 - 화살을 메쉬에 부착한 이후 확인 - ‘화살들’의 ‘추가’에 ‘화살’을 넣고 호출하고, 생성한 화살을 배치한다. - ‘장착 해제 활성’ 함수 정의 - 부모 호출 - ‘화살들’의 ‘요소 갯수 반환’ 함수 호출의 -1 부터 하나씩 줄어들며 반복한다. : ‘화살들’의 현재 순번을 ‘화살’ 변수를 선언하고 저장한다. , ‘화살’이 비어있지 않다면 : ‘화살’의 ‘붙은 부모 엑터 가져오기’ 함수 호출이 비어있지 않다면 : ‘화살들’의 ‘제거’ 함수에 ‘화살’을 넣고 호출한다. , ‘화살’의 ‘파괴’ 함수를 호출한다.
//CPP //붙이기 설정 FAttachmentTransformRules rule = FAttachmentTransformRules(EAttachmentRule::KeepRelative, true); //붙는 규칙 arrow->AttachToComponent(OwnerCharacter->GetMesh(), rule, "Hand_Bow_Right_Arrow"); Arrows.Add(arrow); //생성된 화살을 배열에 저장 //배치 설정 (엑터, 확정된 배치 위치) UGameplayStatics::FinishSpawningActor(arrow, transform); void UCDoAction_Bow::OnUnequip() { Super::OnUnequip(); //활 시위의 좌표를 다시 원래의 위치로 되돌린다. PoseableMesh->SetBoneLocationByName("bow_string_mid", OriginLocation, EBoneSpaces::ComponentSpace); for (int32 i = Arrows.Num() - 1; i >= 0; i--) { ACArrow* arrow = Arrows[i]; //해당 순번에 화살이 있다면 if (!!arrow) { //GetAttachParentActor : 엑터가 어딘가에 붙어있다면 Null이 아니다. //즉, 날아간 화살은 삭제가 안되고, 손에 붙어있는 화살만 삭제가 된다. if (!!arrow->GetAttachParentActor()) { Arrows.Remove(arrow); //리스트의 화살 삭제 arrow->Destroy(); //화살 객체 삭제 } } } }
활 시위를 손에 붙이기
- 장착할때 붙이고, 쏘거나 장착해제할때 손에서 땐다.
- DoAction에서 설정한다.
- 활의 캡쳐된 메쉬는 Attachment에 있어서 매개변수로 받아 저장했다.
- 장착되었는지의 여부확인은 Equipment에 있어서 Equipment를 매개변수로 받아 저장할 것이다.
- 메쉬를 수정하기 전에는 반드시 PoseableMesh를 이용해서 ‘포즈를 캡처’해야한다.
- 캡처하지 않으면 메쉬 수정이 불가능하다.
- 지금은 월드 공간의 값을 사용한다. ⇒ 최초로 활을 붙일때의 컴포넌트 스페이스 좌표다.
- 수정할때는 월드 공간 좌표이고, 사용할때는 컴포넌트 공간 좌표를 이용한다.
- ‘소켓 위치 가져오기’ 함수는 월드 좌표를 반환한다.
- 캐릭터 스켈레톤
- 오른손에 활 시위를 붙일 소켓 생성
- CDoAction_Bow
- CPP 파일 - Tick 함수 확인 - ‘포즈에이블’의 ‘스켈레탈 컴포넌트의 포즈 캡처’ 함수에 ‘스켈레탈’을 넣고 호출한다. - ‘오너 캐릭터’의 ‘메쉬 가져오기’ 함수 호출의 ‘소켓 위치 가져오기’ 함수에 ‘소켓 이름’을 넣고 호출한 결과를 ‘손 위치’ 변수를 선언하고 저장한다. - ‘포즈 에이블 메쉬’의 ‘이름에 의한 본 위치 설정’ 함수에 ‘본 이름, 설정할 위치 : 오른손 소켓 위치, 본 공간 타입 : 소켓 좌표가 월드 좌표이니 월드 공간으로 설정’을 넣고 호출한다.
//CPP void UCDoAction_Bow::Tick(float InDeltaTime) { Super::Tick(InDeltaTime); //스켈레탈 메쉬 포즈 캡쳐 //활의 포즈를 캡쳐한 다음 캡쳐한 포즈의 활 시위를 바꾸기 위해 사용한다. PoseableMesh->CopyPoseFromSkeletalComponent(SkeletalMesh); //활 시위를 붙일 소켓의 위치를 가져온다. FVector handLocation = OwnerCharacter->GetMesh()->GetSocketLocation("Hand_Bow_Right"); //활 시위를 정해진 소켓의 위치에 붙인다. //EBoneSpaces::WorldSpace : 소켓의 위치가 월드공간에서의 좌표라는 뜻이다. //애니메이션을 수정할 때만 WorldSpace로 설정한다. PoseableMesh->SetBoneLocationByName("bow_string_mid", handLocation, EBoneSpaces::WorldSpace); }
장착 해제시 활 시위 되돌리기 설정
- Bone : 캐릭터 디자이너가 설정한 원래의 위치
- Socket : 개발자가 캐릭터의 본에 임의로 생성한 공간이다.
- 메쉬를 수정할때만 ‘월드 공간’으로 설정하고 원본으로 되돌릴때는 ‘컴포넌트 공간’으로 설정하는것이 좋다.
- CDoAction_Bow
- 헤더 파일 - 활 시위 첫 좌표를 저장할 ‘원본 위치’ 변수 선언
//header private: FVector OriginLocation; //활 시위의 장착 전 좌표를 저장하는 변수- CPP 파일 - BeginPlay 확인 - ‘포즈 에이블 메쉬’의 ‘이름에 의한 본 위치 가져오기’ 함수에 ‘본 이름, 본 공간 타입’을 넣고 호출한 결과를 ‘원본 위치’ 변수에 저장한다. - ‘장착 해제 활성’ 함수 확인 - ‘포즈 에이블 메쉬’의 ‘이름에 의한 본 위치 설정’ 함수에 ‘본 이름, 설정할 위치 : 원본 위치, 본 공간 타입 : 원본 좌표가 컴포넌트 좌표이니 컴포넌트 공간으로 설정’을 넣고 호출한다.
//CPP //EBoneSpaces::ComponentSpace : 본의 첫 상대적 위치를 반환한다. OriginLocation = PoseableMesh->GetBoneLocationByName("bow_string_mid", EBoneSpaces::ComponentSpace); //활 시위의 좌표를 다시 원래의 위치로 되돌린다. PoseableMesh->SetBoneLocationByName("bow_string_mid", OriginLocation, EBoneSpaces::ComponentSpace);
화살이 장착 완료될 때만 활 시위가 붙도록 설정하기
- 장착이 되었는지는 Equipment에 있기 때문에 Equipment를 매개변수로 받아 설정한다.
- End_Equip이 발생했을때 ‘장착됨’ 변수를 설정하고 이것을 DoAction으로 가져와서 사용하는것이다.
- DoAction에 EndEquip을 둬서 true, false로 놓아도 되지만 다른 무기들도 장착 확인을 할 수 있도록 이렇게 하는 것이다.
- Equipment를 소유해야만 해당 클래스의 상황에 맞는 값을 얻을 수 있다.
- 하지만, 같은 레벨의 클래스는 서로 소유하지 못하도록 해야한다.
- 해결 방안 : ‘포인터 자료형’ 함수를 선언하고 해당 값의 ‘주소’를 반환한다.
- 문제점 ; 주소를 넘기면 해당 주소의 값이 외부에서 변경될 위험이 있다.
- 해결 방안 : 외부에서 사용하도록 설정한 함수를 ‘상수’로 설정한다.
- CEquipment
- DoAction에서 사용해야하는 변수가 있지만 Equipment를 소유하지 못하도록 ‘매개 변수’로 넘겨서 사용할 수 있게한다.
- 헤더 파일 - ‘장착 됨’ 변수 선언 - 외부에서 사용할 수 있도록 ‘포인터 자료형’으로 ‘장착됨 가져오기’ 함수를 선언하고 ‘장착됨’ 변수의 ‘주소’를 반환하도록 정의한다. ⇒ 값이 변경되지 않도록 ‘상수’로 설정한다.
//header public: //소유하지 못하도록 '포인터'를 넘겨서 '주소'를 주지만, 수정은 하지 못하도록 '상수'로 설정한다. FORCEINLINE const bool* GetEquipped() { return &bEquipped; } private: bool bEquipped; //장착 되었는지 확인- CPP 파일 - ‘장착 종료’ 함수 확인 - ‘장착 됨’ 변수를 true로 설정한다. - ‘장착 해제’ 함수 확인 - ‘장착 됨’ 변수를 false로 설정한다.
//CPP void UCEquipment::End_Equip_Implementation() { bEquipped = true; //장착 되었다. if (Data.bCanMove == false) Movement->Move(); State->SetIdleMode(); } void UCEquipment::Unequip_Implementation() { bEquipped = false; //장착 해제 되었다. if (Data.bUseControlRotation) Movement->DisableControlRotation(); if (OnEquipmentUnequip.IsBound()) OnEquipmentUnequip.Broadcast(); } - DoAction , DoAction_Bow , DoAction_Warp , DoAction_Combo
- BeginPlay 함수에서 Equipment를 매개변수로 받도록 헤더파일과 CPP 파일에 설정한다.
- DoAction_Bow는 실제로 Equipment의 값을 사용해야 하니 헤더파일을 include 해야한다.
- CWeaponAsset
- CPP 파일 - DoActionClass가 비었는지 확인하는 코드 - DoAction의 BeginPlay를 호출하는 부분에 Equipment를 매개변수에 추가해야한다.
- CDoAction_Bow
- 헤더 파일 - Equipment에서 가져올 ‘장착됨’ 의 값을 저장할 포인터 자료형 ‘장착됨’ 변수를 ‘상수’로 설정하고 선언한다.
//header private: const bool* bEquipped; //Equipment에 있는 bEquipped다.- CPP 파일 - 매개변수로 들어온 ‘Equipment’의 ‘장착 됨 가져오기’ 함수를 호출한 결과를 ‘장착 됨’ 변수에 저장한다. - Tick 함수 확인 - ‘장착 됨’ 변수의 ‘포인터 값’을 넣어 False인지 확인한다. ⇒ false라면 무기가 장착되지 않은 상태이니 활 시위의 위치 수정도 하지 않는다.
//CPP bEquipped = InEquipment->GetEquipped(); //Equipment를 소유하지않고 값만 가져온다. CheckFalse(*bEquipped); //장착이 되었는지 확인한다.
조준 밑준비 - 나머지 준비는 다음에 이어서 시작
- 애니메이션 가져오기
- ‘기준 애니메이션’에 다른 동작들 한 프레임씩 섞어서 새로운 동작을 만든다.
- ‘기준 애니메이션’의 프레임과 섞는 동작의 프레임은 같게 잘랐다. (1 프레임으로)
- 애니메이션 설정
- 밑의 Additive 이론에 적어놨다.
- 기준 애니메이션을 제외하고 섞을 애니메이션 전체 선택 - 우클릭 - 에셋 액션 - ‘프로퍼티 매트릭스를 통한 대량 편집’ 클릭 - 디스플레이 창 - AdditiveSettings - ‘애디티브 애님 타입’ : Mesh Space - ‘베이스 포즈 타입’ : Selected animation frame - ‘레퍼런스 프레임’ : 0 - 베이스 포즈 애니메이션(기준 애니메이션) ; 정조준 애니메이션
- AO_Bow
- 상하 좌우 모두 움직이는 조준일때는 ‘에임 오프셋’ , 상하만 움직이는 조준일때는 ‘에임 오프셋 1D’로 생성한다.
- 블렌드 스페이스와 거의 비슷하지만 한가지 차이점이 존재한다.
- 에디티브 세팅의 ‘베이스 애니메이션’ 지정을 안하면 애니메이션을 섞을 수 없다.
- 서로 다른 애니메이션이라고 판단되기 때문이다.
- 콘텐츠 브라우저 - 우클릭 - 애니메이션 - ‘에임 오브셋 1D’ 클릭
- 에셋 디테일 - 가로축 이름 : Pitch(y회전) - 최소 축 값 ; -90 - 최대 축 값 : 90 - 그리드 분할 : 4
- 애니메이션 축값에 따라 배치
- 상하 좌우 모두 움직이는 조준일때는 ‘에임 오프셋’ , 상하만 움직이는 조준일때는 ‘에임 오프셋 1D’로 생성한다.
- ABP_Player
- 간단한 포즈 : ‘스테이트 머신 / 복잡한 포즈 : 레이어 생성 후 정의
- 포즈 : 스테이트 머신에서 나온 결과를 저장하고 사용한다.
- 레이어 : 애니메이션 자체를 사용한다.
- 이벤트 그래프를 생성하여 포즈를 만들어도 되지만 ‘애니메이션 레이어’를 사용하는것이 편하다.
- 애니메이션 레이어 사용 이유 ; ‘최종 애니메이션 노드’를 하나만 놓고 사용하기 위해서다.
- 애니메이션 레이어 생성 - 이름 지정 - ‘BS_Bow’ 끌어 놓기 - ‘Direction’과 ‘Speed’ 검색해서 연결
- 애님 그래스 - ‘생성한 레이어 이름’ 검색 : 레이어 이름의 ‘링크된 애님 레이어’ 라고 나온다. - 블렌드 포즈의 Bow 핀에 연결
- 간단한 포즈 : ‘스테이트 머신 / 복잡한 포즈 : 레이어 생성 후 정의
가변형 배열 요소 삭제시 생기는 문제
- 가변형 배열 : 최대 요소의 수가 정해지지 않은 배열
- C : stlVector
- C++ : TArray<>
- C# : List
- 배열의 중간값이 삭제된다면 : 뒤에 있는 값들이 빈 공간을 채우기 위해 당겨진다.
- 문제점 : 삭제된 공간 다음 값을 읽을때 이미 다음 값이 빈공간을 채우기 위해 한칸씩 앞으로 값이 이동했으므로 실제로 읽는 값은 삭제된 요소 다음 다음 값이 된다.
- 반복문 내에서 발생하는 문제다.
- 해결 방법 ; 역순으로 탐색한다.
- 0 ~ 5의 요소중 3을 없앤다고 했을때, 3을 없애도 다음 탐색값은 2이고, 3의 공간을 이미 탐색한 4의 요소가 채우게 되니 오류가 없어진다.
- 문제점 : 삭제된 공간 다음 값을 읽을때 이미 다음 값이 빈공간을 채우기 위해 한칸씩 앞으로 값이 이동했으므로 실제로 읽는 값은 삭제된 요소 다음 다음 값이 된다.
- 배열에 저장한 화살의 제거, 무기 시스템 - 피스트 에 사용한 문법이다.
애니메이션 Additive 이해
- 애니메이션을 2개 이상 섞는것을 ‘Additive’라고 부른다.
- 애니메이션은 컴포넌트에서 일어난다.
- 액터는 컴포넌트를 붙여서 애니메이션을 보여주는것이다.
- UObject
- UActorComponent
- USceneComponent
- UPreimitiveComponent
- UMeshComponent
- UStaticMeshComponent
- USkinnedMeshComponent
- USkeletalMesh
- UPoseableMeshComponent
- UMeshComponent
- UPreimitiveComponent
- USceneComponent
- UActorComponent
- 스켈레탈에 애니메이션이 들어가는 것이다.
- 포즈 에이블은 애니메이션이 들어간 스켈레탈 메쉬를 ‘캡쳐’해서 복사하여 사용하는 것이다.
- 스킨드부터 애니메이션이 들어갈 수 있다.
- SkinnedMesh의 3가지 공간
- 애니메이션을 수정할 수 있느냐를 나눈다.
- 즉, 포즈가 들어갈 수 있느냐의 여부다.
- 애니메이션 블루프린트에서는 ‘스킨드 메쉬’에 있는 애니메이션을 플레이 시키는 것이다.
- Local Space : 애니메이션 수정 불가능, 플레이만 가능
- Mesh Space : 애니메이션 수정 가능, 단, 언리얼 에디터 옵션으로만 가능하다.
- 본 별로 레이어로 블렌딩에서 ‘메쉬 스페이스 회전’ 체크에서 사용된다.
- 체크를 해제하고 공격하면 허리 회전이 안들어가게 되고, 팔만 깔짝이게 된다.
- 본을 회전시킨다. 즉, 본을 수정한다. 인데, 체크를 해제하면 수정이 불가능하게 되는 것이다.
- 움직이면서 때릴때 사용하는것은 UpperBody다.
- Component : 프로그래밍에 의해서만 수정이 가능하다.
- 활 시위를 프로그래밍으로 애니메이션 수정을 한다.
- 애니메이션을 수정할 수 있느냐를 나눈다.
Additive 관련 이론
- 애니메이션 프로퍼티 대량 편집 창
- 디스플레이 - AdditiveSettings
- 애디티브 애님 타입 : 에디티브 애니메이션 타입을 설정한다.
- Mesh Space : 언리얼 에디터에서만 애니메이션 본 수정이 가능하도록 설정한다.
- 베이스 포즈 타입 : 기준이 되는 애니메이션을 섞을 타입을 설정하는 옵션이다.
- Selected animation frame : 애니메이션의 프레임으로 섞는 옵션이다.
- 기준 애니메이션과 특정 애니메이션의 ‘레퍼런스’에 지정된 프레임의 동작과 섞을 것이기 때문에 ‘프레임’ 타입으로 설정하는 것이다.
- 즉, 레퍼런스 : 원본 동작, 프레임 : 원본 동작 중 특정 프레임의 동작
- Selected animation frame : 애니메이션의 프레임으로 섞는 옵션이다.
- 레퍼런스 프레임 : 기준 애니메이션과 섞을 원본 애니메이션의 특정 프레임 순번
- 기본적으로 ‘0’을 놓는다.
- 베이스 포즈 애니메이션 : 섞을 애니메이션들의 ‘기준’이 되는 애니메이션을 설정하는 옵션이다.
- 애디티브 애님 타입 : 에디티브 애니메이션 타입을 설정한다.
- 디스플레이 - AdditiveSettings
- 설정이 끝나면 애니메이션 항목의 ‘에임 오프셋’ 에셋을 생성해서 넣는다.
발사체 조준 이해
- 조준에 필요한 것 : 동작
- 원래 조준 동작은 사방 팔방 움직이는 하나의 애니메이션이다.
- 그것을 최종 위치에 도달한 ‘한 프레임’ 만을 잘라서 다른 자른 동작과 연결하여 블렌딩하는 것이다.
- 조준할때 동작 3개를 섞어야한다.
- 아래, 정방향, 위
- 원래는 상하좌우, 대각선 모든 방향을 사용해야한다.
- 아니면 조준한 상태의 한 프레임씩 사용한다.
- 최종적으로, ‘기준 애니메이션’에 다른 동작들 한 프레임씩 섞어서 새로운 동작이 생기는 것이다.