무기 시스템 - 활 (붙이기만 하고 잠시 대기함)
- 활은 Attachment 블루프린트가 아니라 다른 방법으로 붙인다.
- 여러가지 기능들이 들어가야하기 때문에 C에서 스폰부터 기능까지 작업한다.
활 밑준비 - 스폰시키고 홀스터에 부착시키기
- DA_Bow
- BP_Player
- WeaponComponent에 DA_Bow 할당
- CAttachment_Bow
- 생성
- 좌표값에서 Min값과 Max 값을 사용할떄는 FVector2D로 선언한다.
- PoseableMesh->SetSkeletalMesh(SkeletalMesh->SkeletalMesh) 설명 : 스켈레탈 메쉬 컴포넌트에 설정한 디테일 - 스켈레탈 메쉬 를 의미하는 것이다.
- 헤더 파일 - ‘피치 범위 뷰’ 변수를 선언하고 ‘(-40, 30)’으로 초기화한다. - 활 스켈레탈을 가져와서 저장할 ‘스켈레탈 메쉬’ 변수를 선언한다. - 포즈에이블 컴포넌트 클래스의 ‘포즈에이블 메쉬’ 변수를 선언한다. - 생성자 선언 - BeginPlay 재정의 선언 - Tick 재정의 선언
//header
private:
UPROPERTY(EditDefaultsOnly, Category = "View")
FVector2D ViewPitchRange = FVector2D(-40, 30); //
private:
UPROPERTY(VisibleAnywhere)
class USkeletalMeshComponent* SkeletalMesh;
UPROPERTY(VisibleAnywhere)
class UPoseableMeshComponent* PoseableMesh;
public:
ACAttachment_Bow(); //생성자
- CPP 파일 - 생성자에 ‘스켈레탈 메쉬’와 ‘포즈에이블 메쉬’ 컴포넌트 생성 - 스켈레탈 메쉬를 가져와서 저장할 ‘메쉬’ 변수 선언 - 스켈레탈 메쉬 에셋을 가져와 ‘메쉬’에 저장한다. - ‘스켈레탈 메쉬’의 ‘스켈레탈 메쉬 설정’ 함수를 ‘메쉬’를 넣어 호출한다. - BeginPlay 정의 - 부모 함수 호출 - 정의한 ‘AttachTo’ 함수에 소켓 이름을 넣고 호출한다. - ‘스켈레탈 메쉬’의 ‘보여지기 설정’을 false로 넣어 호출한다. - ‘포즈 에이블 메쉬’의 ‘스켈레탈 메쉬 설정’ 함수에 ‘스켈레탈 메쉬의 스켈레탈 메쉬 디테일 옵션’을 넣어 호출한다. - ‘포즈 에이블 메쉬’의 ‘스켈레탈 컴포넌트의 포즈 복사’ 함수에 ‘스켈레탈 메쉬’를 넣어 호출한다.
//CPP
ACAttachment_Bow::ACAttachment_Bow()
{
PrimaryActorTick.bCanEverTick = true;
CHelpers::CreateComponent<USkeletalMeshComponent>(this, &SkeletalMesh, "SkeletalMesh", Root);
CHelpers::CreateComponent<UPoseableMeshComponent>(this, &PoseableMesh, "PoseableMesh", Root);
USkeletalMesh* mesh;
CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Characters/Weapons/ElvenBow/SK_ElvenBow.SK_ElvenBow'");
SkeletalMesh->SetSkeletalMesh(mesh);
}
void ACAttachment_Bow::BeginPlay()
{
Super::BeginPlay();
AttachTo("Holster_Bow");
SkeletalMesh->SetVisibility(false);
PoseableMesh->SetSkeletalMesh(SkeletalMesh->SkeletalMesh); //스켈레탈 메쉬 변수에 저장한 mesh를 의미하는거다.
PoseableMesh->CopyPoseFromSkeletalComponent(SkeletalMesh);
}
잔상 효과 (GhostTrail)
- C로 클래스를 만들어 넣고싶은 곳에 붙이고 사용이 끝나면 필요없을때 삭제하는 방식으로 제작한다.
- 일반적인 잔상을 ‘Trail’이라고 부르고, 캐릭터에 대한 잔상을 ‘GhostTrail’ 이라고 부른다.
- 캐릭터에도 잔상 효과를 주고 무기에도 잔상효과를 주면 더 그럴싸하다.
- 아니면, 무기를 휘두를때만 잔상 효과가 나와도 된다.
- BindLambda(=)
- [=] : 외부값을 대입해서 쓰는 캡쳐절이다.
- 함수를 간단하게 쓰기 위해 사용한다.
잔상 효과 밑준비
- 잔상 : 반사와 굴절이 생겨야 나온다.
- Fresnel 방정식 : 반사와 굴절이 합쳐진 방정식이다. ⇒ 잔상 효과를 만들때 사용한다.
- 유니티는 특별한 쉐이더를 선택하지 않는이상 색상 값의 범위는 ‘0 ~ 1’이지만, 언리얼의 색상 범위는 1을 넘어갈 수 있다.
- 색상값이 1을 넘어가면 ‘번지듯이 밝아지는 효과’가 나타난다.
- 즉, 번지듯이 밝아지는 효과를 위해 ‘이미시브 컬러’에 1 이상의 값을 넣고, 기본 색상을 위해 ‘베이스 컬러’에 0 ~ 1까지의 값을 넣는다.
- Exponnet : 밀도, 값이 커질수록 밀도가 커진다.
- M_GhostTrail
- 메테리얼 생성 - 인스턴스 메테리얼 생성
- 결과 : 외곽선만 색이 입혀지고 내부는 굴절이 일어나서 빛이 투과되게 된다.
- 베이스 컬러 : 색
- 이미시브 컬러 : 외곽선 바깥의 밝아지는 색
- 메테리얼 그래프 - 베이스 컬러가 될 ParameterVector 검색 후 이름 변경(Color) - 굴절과 반사 수식 계산에 필요한 ‘Fresnel’ 검색 - 넓이를 조절하기 위해 ‘ScalarParameter’ 검색 후 이름 변경(Exponent) - 잔상의 넓이를 조절하기 위해 Fresnel 결과와 Exponent 값을 ‘곱한다’ - 넓이가 결정된 잔상에 색을 입해기 위해 곱한 값을 ‘베이스 컬러’와 곱한다. - 잔상을 임의로 보정하기 위해 결과에 핀을 끌어 ‘Multiplyer’를 검색하고 B값에 20을 준다. - 나온 결과를 ‘베이스 컬러’ 핀에 연결한다. - Fresnel 노드와 Exponent 값을 곱한 노드의 핀을 ‘오퍼시티’ 핀에 연결한다. - 베이스 컬러에 연결된 결과 노드를 또 ‘이미시브 컬러’ 핀에 연결한다.
- M_GhostTrail_Inst
- Exponent와 Color의 체크박스에 체크를 안하면 색이 나타나지 않는다.
- 컬러 변경
잔상효과 출력
- 게임 플레이가 종료될때 생성한 타이머가 사라지지 않았으면 게임이 터진다.
- CGhostTrail
- 생성
- 헤더 파일 - ‘시작 딜레이’ 변수 선언 - ‘잔상 캡쳐 간격’ 변수 선언 - ‘잔상 색상’ 변수 선언하고 흰색으로 초기화한다. - ‘밀도 넓이’ 변수 선언 - ‘스케일’ 변수 선언 - ‘스케일 보정’ 변수 선언 - 포즈에이블 메쉬 컴포넌트 클래스의 ‘메쉬’ 변수 선언 - ‘종료 사유’를 받는 ‘플레이 종료’ 함수를 재정의한다. - ‘오너 캐릭터’ 와 ‘메테리얼’ 변수 선언 - 타이머 핸들링 자료형의 ‘타이머 핸들’ 변수를 선언한다.
//header
private:
UPROPERTY(EditDefaultsOnly, Category = "Capture")
float StartDelay = 0; //잔상이 몇초 뒤에 시작되나
UPROPERTY(EditDefaultsOnly, Category = "Capture")
float Interval = 0.25; //잔상 캡쳐 간격 => 짧으면 짧을수록 자연스럽다.
UPROPERTY(EditDefaultsOnly, Category = "Capture")
FLinearColor Color = FLinearColor(1, 1, 1, 1); //잔상색깔
UPROPERTY(EditDefaultsOnly, Category = "Capture")
float Exponent = 1; //밀도 넓이 조절 값
UPROPERTY(EditDefaultsOnly, Category = "Capture")
FVector Scale = FVector::OneVector; //잔상 크기를 캐릭터 대비 몇 비율로 할거냐
UPROPERTY(EditDefaultsOnly, Category = "Capture")
FVector ScaleAmount = FVector::ZeroVector; //스케일에 더 보정할 값
private:
UPROPERTY(VisibleAnywhere)
class UPoseableMeshComponent* Mesh;
public:
ACGhostTrail();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EEndPlayReason) override; //게임이 종료되거나, 컴포넌트가 삭제될때 호출되는 함수다.
private:
class ACharacter* OwnerCharacter;
class UMaterialInstanceDynamic* Material;
FTimerHandle TimerHandle;
- 타이머 핸들 : 타이머의 식별자다.
- 타이머 델리게이트 : 타이머가 동작할때 실행할 이벤트 내용
- CPP 파일 - 캡쳐 포즈를 사용할 컴포넌트인 포즈에이블 컴포넌트 쟈료형의 ‘메쉬’ 컴포넌트 생성 - BeginPlay 정의 - ‘오너 캐릭터’를 형변환해서 저장한다. - 메테리얼 인스턴스를 동적 생성하여 저장하기 위해 메테리얼 인스턴스 컨스턴스 클래스로 ‘임시 메테리얼’ 변수 선언 - 메테리얼 인스턴스 컨스턴스 클래스로 ‘메테리얼 동적생성’하여 ‘메테리얼’ 변수에 저장한다. - ‘메테리얼’ 변수에 ‘임시 메테리얼’ 변수에 저장된 메테리얼을 생성하여 저장한다. - ‘메테리얼’의 ‘벡터 파라미터 값 설정’ 함수에 ‘베이스 컬러 이름, 컬러 변수’를 넣어 색상을 설정한다. - ‘메테리얼’의 ‘스케일 파라미터 값 설정’ 함수에 ‘스칼라 파라미터 이름, 밀도 넓이 변수’ 를 넣어 얼마나 번지는지 설정한다. - ‘메쉬’의 ‘보이기 설정’ 함수를 fasle로 넣어 호출한다. - ‘메쉬’의 ‘스켈레탈 메쉬 설정’ 함수에 ‘오너캐릭터의 메쉬 가져오기 함수를 호출하여 그 안의 스켈레탈 메쉬’를 넣어 호출한다. - ‘메쉬’의 ‘스켈레탈 컴포넌트의 포즈 복사’ 함수에 ‘오너캐릭터의 메쉬 가져오기 함수 호출’을 넣어 호출한다. - ‘메쉬’의 ‘3D 스케일 설정’ 함수에 ‘크기’ 변수를 넣어 호출한다. - 오너캐릭터 스켈레탈 메쉬의 메테리얼 갯수만큼 반복한다. : ‘메쉬’의 ‘메테리얼 설정’ 함수에 ‘현재 순번, 메테리얼’을 넣고 호출한다. - 타이머 델리게이트 구조체의 ‘타이머 델리게이트’ 변수 선언 - ‘타이머 델리게이트’의 ‘람다 연결’ 함수에 ‘외부 값 캡쳐해서 대입’하여 호출한다. : ‘메쉬’의 ‘보여지기 확인’ 함수가 false라면 : ‘메쉬’의 ‘보여지기 토글’ 함수를 호출한다. , 현재 포즈를 캡쳐하기 위해 ‘메쉬’의 ‘스켈레탈 컴포넌트의 포즈 복사’ 함수에 ‘오너캐릭터의 메쉬 가져오기 함수 호출’을 넣어 호출한다. - ‘월드 가져오기’ 함수 호출의 ‘타이머 매니터 가져오기’ 함수의 ‘타이머 설정’ 함수에 ‘타이머 핸들, 타이머 델리게이트, 잔상 캡쳐 간격, 반복 여부, 시작 딜레이 값’을 넣어 호출한다. - EndPlay 정의 - 부모 호출 - ‘월드 가져오기’ 함수 호출의 ‘타이머 메니저 가져오기’ 함수 호출의 ‘타이머 초기화’에 ‘타이머 핸들’을 식별자로 넣어 호출한다.
//CPP
ACGhostTrail::ACGhostTrail()
{
CHelpers::CreateComponent<UPoseableMeshComponent>(this, &Mesh, "Mesh");
}
void ACGhostTrail::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
UMaterialInstanceConstant* material;
CHelpers::GetAssetDynamic<UMaterialInstanceConstant>(&material, "MaterialInstanceConstant'/Game/Materials/M_GhostTrail_Inst.M_GhostTrail_Inst'");
Material = UMaterialInstanceDynamic::Create(material, this);
Material->SetVectorParameterValue("Color", Color);
Material->SetScalarParameterValue("Exponent", Exponent);
Mesh->SetVisibility(false);
Mesh->SetSkeletalMesh(OwnerCharacter->GetMesh()->SkeletalMesh);
Mesh->CopyPoseFromSkeletalComponent(OwnerCharacter->GetMesh());
Mesh->SetRelativeScale3D(Scale);
//OwnerCharacter->GetMesh()->SkeletalMesh->GetMaterials().Num();
for (int32 i = 0; i < OwnerCharacter->GetMesh()->SkeletalMesh->GetMaterials().Num(); i++)
{
Mesh->SetMaterial(i, Material);
}
FTimerDelegate timerDelegate;
timerDelegate.BindLambda([=]()
{
if (Mesh->IsVisible() == false)
{
Mesh->ToggleVisibility();
}
Mesh->CopyPoseFromSkeletalComponent(OwnerCharacter->GetMesh()); //포즈 저장
});
GetWorld()->GetTimerManager().SetTimer(TimerHandle, timerDelegate, Interval, true, StartDelay);
}
void ACGhostTrail::EndPlay(const EEndPlayReason::Type EEndPlayReason)
{
Super::EndPlay(EEndPlayReason);
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
}
- CWeaponStructures
- 액션때 잔상효과를 출력할 것이니 DoActionData에 GhostTrail 파생 블루프린트를 넣을 변수를 넣는다.
- 헤더 파일 - GhostTrail로 클래스 제한을 걸어 ‘고스트 트레일 클래스’ 변수를 선언한다. - ‘잔상 파괴’ 함수 선언
//header
UPROPERTY(EditAnywhere)
TSubclassOf<class ACGhostTrail> GhostTrailClass;
- 캡슐의 절반만큼 빼는 이유 : 캡쳐 메쉬가 그려질때 발을 중심으로 그려져야 하기 때문이다.
- CPP 파일 - DoAction 함수 확인 - ‘고스트 트레일 클래스’ 변수가 비어있지 않다면 : ‘오너 캐릭터’의 ‘엑터 위치 가져오기’ 함수를 호출해 ‘위치’ 변수를 선언하고 저장한다. , ‘위치’의 Z 값에 ‘오너 캐릭터’의 ‘캡슐 컴포넌트 가져오기’ 함수 호출의 ‘캡슐 절반 높이 크기 가져오기’ 함수를 호출한 결과값을 뺀다. , 엑터 스폰 파라미터 구조체의 ‘파라미터들’ 변수 선언 , ‘파라미터들’의 ‘오너’에 매개변수로 들어온 ‘오너 캐릭터’를 저장한다. - ‘파라미터들’의 ‘스폰 충돌 핸들링 재정의’에 ‘스폰 충돌 핸들링 방법 : 항상 스폰’으로 저장한다. - 트랜스폼 구조체의 ‘트랜스폼’ 변수 선언 - ‘스랜스폼’의 ‘이동 설정’ 함수에 ‘위치’를 넣어 호출한다. - ‘오너 캐릭터’의 ‘월드 가져오기’ 함수 호출의 ‘엑터 스폰’을 ‘GhostTrail’ 클래스로 지정하고 ‘고스트 트레일 클래스, 트랜스폼, 파라미터들’ 변수를 넣고 호출한 결과를 ‘고스트 트레일’ 변수에 저장한다. - ‘잔상 파괴’ 함수 정의 - ‘고스트 트레일’일 null이 아니라면 : ‘고스트 트레일’의 ‘파괴’ 함수를 호출한다.
//CPP
if (!!GhostTrailClass)
{
FVector location = InOwner->GetActorLocation();
location.Z -= InOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
FActorSpawnParameters params;
params.Owner = InOwner;
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
FTransform transform;
transform.SetTranslation(location);
GhostTrail = InOwner->GetWorld()->SpawnActor<ACGhostTrail>(GhostTrailClass, transform, params);
}
void FDoActionData::Destroy_GhostTrail()
{
if (!!GhostTrail)
{
GhostTrail->Destroy();
}
}
- CDoAction_Combo
- CPP 파일 - Begin_DoAction과 End_DoAction에 추가 - ‘DoActionDatas’의 ‘콤보 수’의 ‘고스트 트레일 파괴’ 함수를 호출한다.
//CPP
DoActionDatas[Index].Destroy_GhostTrail();
- BP_GhostTrail_Hammer / Sword
- GhostTrail 파생 블루프린트 파일 생성
- 시작 딜레이, 캡쳐 간격과 잔상 색상을 설정한다.
- DA_Hammer
- GhostTrail Class에 해당 무기의 잔상클래스를 할당한다.
잔상 위치 보정
- 캐릭터의 메쉬 컴포넌트를 가져올때 메쉬의 위치화 회전을 가져오기 때문에 메쉬에 지정된 회전 -90도 같이 포함되어있다.
- 해결 방법 : 캡쳐된 메쉬를 소환할때 회전을 준다.
- 캡쳐 메쉬의 소환위치를 발에 맞춰놓았는데 해당 메쉬의 위치를 캐릭터의 메쉬 위치로 놓으니 메쉬의 중심점인 배 위치에 캡쳐메쉬의 발이 맞춰져서 공중에 띄워지는 것이다.
- ‘엑터 위치 설정’ 함수에 ‘오너캐릭터의 엑터 위치 가져오기 함수 호출’을 넣어 호출한다. , ‘엑터 회전 설정’ 함수에 ‘오너캐릭터의 엑터 회전 가져오기 함수 호출’을 넣어 호출한다.
//CPP
SetActorLocation(OwnerCharacter->GetActorLocation() + FVector(0, 0, -90));
SetActorRotation(OwnerCharacter->GetActorRotation() + FRotator(0, -90, 0));
언리얼 문서 빠르게 가는방법
- 에디터 - 상단에 도움말 버튼 - C++ API 레퍼런스 클릭
UPoseableMeshComponent
- UObject
- UActorCOmponent : 컴포넌트에 붙을 수 있는 ‘기능’만을 제공하는 컴포넌트다.
- USceneComponent : 어딘가 또는 어느 컴포넌트에 붙일 수 있는 기능을 가진 컴포넌트다.
- 상대적 간격을 가지고 있다. ⇒ 컴포넌트 창에서 보여지는 부모와 자식의 구분, 다른 상속을 받는 컴포넌트와 구분 등을 확인할 수 있다.
- UPriemitiveComponent : 그릴 수 있는 최소한의 기능을 가지고 있다.
- UShapeCompoennt : 충돌체의 모양을 정의하는 기능을 가지고 있다.
- UMeshComponent :그물망 구조(버텍스, 트라이앵글을 연결할 형태)를 그릴 수 있는 최소한의 기능을 가지고 있다.
- UStaticMeshComponent : 메쉬 자체가 고정적이어서 움직이지 않고 변형되지 않는다.
- USkinnedMeshComponent : 변형이 되는 메쉬를 가지고 있다.
- 변형이 된것들을 자연스럽게 메꾸는 기능을 가지고 있다.
- 두 큐브를 붙여놓고 팔을 굽히는것처럼 구부릴때 큐브 사이가 벌어지고 좁혀지는것을 보정해주는 기능이 있다.
- USkeletalMeshComponent
- UDistructableMeshComponent : 파괴되고 찢어지는 메쉬의 기능을 가지고 있다.
- UPoseableMeshComponent : 애니메이션을 캡쳐해서 변형하여 쓸 수 있게하고, 폰을 임의로 변형할 수 있게하는 컴포넌트다.
- 잔상효과를 내기위해 스켈레탈 메쉬를 복사하면 연산량이 크다.
- 해결방법 : 애니메이션의 한 ‘프레임’을 캡쳐해서 그 크기를 늘려 사용한다.
- 그후, 캡쳐한 부분의 메테리얼을 바꿔 잔상처럼 보이게 설정하는 것이다.
메쉬를 이루는 요소 설명
- 버텍스 : 삼각형(트라이앵글)을 그릴 수있는 최소한의 ‘정점’을 의미한다.
- 트라이앵글 ; 버텍스를 3개 연결한 삼각형을 의미한다.
- 버택스를 연결해 트라이앵글을 만들때 각 정점을 ‘번호’로 연결한다.
- 그 번호를 ‘인덱스 버퍼(버택스 버퍼)’라고 한다.
- 어떤식으로 트라이앵글을 연결해 갈지의 정보를 ‘프리미티브 토폴로지’ 라고 한다.
- 즉, 이러한 그릴 수 있는 최소한의 정보를 ‘UPriemitiveComponent’가 가지고 있다.
Goast Trail