Fracture Mode
Fracture Mode를 이용하면 메시를 파괴가능하게(물리적으로 충돌 발생시 조각내기) 변경할 수 있다.
스태틱 메시를 하나 선택하고, 좌측 상단의
Fracture를 클릭한다.
New를 누르고, 저장할 폴더를 선택 후
Create Geometry Collection을 클릭한다.
이제 우측에서 여러
Fracturing을 진행할 수 있다.
Uniform을 선택하면 위와 같은 창이 뜨는데,Min Voronoi Sites와Max Voronoi Sites로 랜덤하게 최소와 최대의 조각개수를 정해줄 수 있다.
Fracture을 누르면 메시에 선이 생겨 파괴시 선을 따라 파괴 가능하게 한다.
우측의Fracture Hierarchy에서 아무부분이나 선택후 다시Fracture하는 것도 가능하다.
우측의
Details패널에서Simulate Physics와Enable Gravity활성화를 확인하고 에셋을 공중에 띄우고 실행하면 떨어지고 파괴되는 것을 확인할 수 있다.
Details패널에서Show bone Colors체크를 비활성화하고 실행할 경우 정상적인 메쉬로 출력된다.
FieldSystem
이제 무기를 이용한 파괴가 가능하도록 바꿔야 한다.
먼저 적절하게 폴더를 생성하고FieldSystemActor를 생성한다.
Physics Field를 생성하기 위해 노드를 추가한다.
Enable Field는Physics Field를 활성화시키는 bool 타입 변수이므로 반드시 활성화 시켜주어야 한다.
Physics Type은Enum은 열거형 타입으로 다양한 타입의Physics Field상수를 가지고 있다.
우선 필요한 것은 외부의 힘에 의한 파괴이므로External Strain을 선택한다.
이제 어디에 어느정도의 힘을 가해야하는지와 같은 정보가 있어야 파괴가능하게 만들 수 있다.
중심에서 멀어질수록 힘이 점점 약해지는Radial Falloff를 사용할 것이다.
우측의Components패널에서Radial Falloff를 추가해 준다.
노드를 추가해주고,
Set Radial Falloff함수 노드를 호출해준다.
- Field Magnitude
해당Physics Field가 가하는 물리적인 힘의 크기(자기장과 같이 중심에서 바깥으로 힘이 가해진다는 느낌)- Min/Max Range
Field Magnitude에 최종적으로 곱을 통해 장의 크기를 정해주는 parameter인 것 같음.- Default Value
Field의 반경보다 멀리 있을 경우Field Magnitude의 기본값을 설정하- Sphere Radius
Field의 반지름- Center Position
장의 Center 위치- Falloff Type
장의 type. 선형적, 제곱, 로그 등 적용 가능
컴파일후 파괴 가능한 물건 옆에 배치하면 파괴되는 것을 확인할 수 있다.
좀더 디테일하게 표현하기 위해 부서진 파편이 땅에 떨어지는것 뿐만 아니라 날라가는 것까지 표현할 수 있다.
똑같이
Add Transient Field노드를 추가해주고
우측의
Components패널에서Radial Vector를 추가하고,Set Raidal Vector함수를 호출해준다.
Center Position을Get Actor Location노드를 통해 벡터값을 얻어오고,Field Magnitude값을 정해주고Field Node와 연결시킨다.
Field Magnitude값은 에셋의Damage Threshold탭에서 최대값 확인후 적절히 값을 할당한다.
마지막으로
Set Radial Vector함수가 파괴가능한 오브젝트에만 적용되도록 수정하기 위해 좌측의Components패널에서MetaData Filter를 추가해준다.
우측
Details패널의Field에서Object Type을Destruction으로 변경해주면 된다.
마지막으로 노드를 이어주면 끝.
결과가 기존에 파괴되던것 보다 더 약하게 날라갈 경우, 오브젝트 클릭후 우측Details패널에서Enable Clustering비활성화시키면 해결 가능. 다만 이때BP_FieldSystem에서Field Magnitude값을 적절히 변경시켜줘야함.
무기에 적용
Blueprint로 구현하는 것이 편리하므로 Weapon클래스에서 선언과 필요한 값들만 const 참조를 통해 받고, Blueprint로 구현한다.
- Weapon.h
// Weapon.h protected: ... UFUNCTION(BlueprintImplementableEvent) void CreateFields(const FVector& FieldLocation); ...BoxTrace에 의해 충돌 판별시 CreateFields를 호출하도록 한다.
- Weapon.cpp
// Weapon.cpp void AWeapon::OnBoxOverlap(...) { ... if(CollisionBoxHit.GetActor()) { ... CreateFields(CollisionBoxHit.ImpactPoint); } }
- BP_Weapon
먼저 cpp 클래스에서 생성한
Event Create Field노드를 추가해준다.
BP_FieldSystem과 동일하게 좌측의Components패널에서Field System,Radial Falloff,Radial Vector,Meta Data Filter를 추가해준다.
Meta Data Filter를 클릭하고 우측의Details패널에서Object Type을Destruction으로 변경한다.
Radial Falloff에 대해서BP_SystemField와 동일하게 구상해주되,Center Position은 c++ 클래스에서 ImpactPoint를 참조하므로Field Location과 연결시켜준다.
Radial Vector도 똑같이 구현해준다.
레벨에 위치한BP_FieldSystem을 삭제하고,Enable Clustering을 다시 활성화시켜준 뒤,Generate Overlap Event를 활성화해준다.
BoxTrace를 쉽게 하기 위해
Fracture Mode에서 생성한 파일을 열고
Collisions 쪽에서
Implicit Type을Capsule로 변경한다.
Hit Event가 발생하는지 확인하기 위해
DrawDebugSphere노드를 추가해주고 확인한다.
Enable Clustering을 활성화했으므로Field Magnitude값을 다시 적절히 조정해서 공격하면 물체가 파괴되는 것을 확인할 수 있다.
Actor Class로 파괴가능한 오브젝트 생성
필요한 것은 Actor이므로 Actor class를 생성한다.
- BreakableActor.h
// BreakableActor.h ... class UGeometryCollecitonComponent; ... private: UPROPERTY(VisibleAnywhere) UGeometryCollectionComponent* GeometryCollection; ...
- BreakableActor.cpp
// BreakableActor.cpp ... #include "GeometryCollection/GeometryCollectionComponent.h" void ABreakableActor::ABreakableActor() { // Tick 필요없으므로 false로 변경 PrivaryActorTic.bCanEverTick = false; GeometryCollection = CreateDefaultSubobject<UGeometryCollectionComponent>(TEXT("GeometryCollection")); SetRootCommponent(GeometryCollection); // Collision Preset GeometryCollection->SetGenerateOverlapEvents(true); }
- Study.Build.cs
// Study.Build.cs ... public class Study : ModuleRules { public Study(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "GeometryCollectionEngine" }); ... } }
BreakableActor기반 블루프린트 클래스를 생성한다.
우측의
Rest Collection에 사용할Geometry Collection을 넣어주면 된다.
카메라와의 collision을 없애기 위해 코드를 추가한다.
- Breakable.cpp
// Breakable.cpp ABreakable::ABreakable() { ... GeometryCollection->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); }Breakable도 Interface를 사용할 수 있다.
HitInterface의GetHit함수에UFUNCTION(BlueprintNativeEvent)매크로를 사용하여 코드에 구현한 기능과 블루프린트에서 추가한 기능 둘다 사용할 수 있도록 변경할수도 있다.
- BlueprintCallable
블루프린트에서 c++로 구현한 기능 호출만 가능- BlueprintImplementableEvent
코드상에서 선언만 하고 블루프린트에서 구현- BlueprintNativeEvent
c++에서 구현한 기능과 블루프린트에서 override 가능함
먼저HitInterface.h에 UFUNCTION 매크로를 추가해준다.- HitInterface.h
// HitInterface.h ... public: UFUNCTION(BlueprintNativeEvent) void GetHit(const FVector& ImpactPoint);다른 부분에서 override할 수 있도록 코드를 수정해준다.
- Enemy.h
// Enemy.h // Enemy.cpp 에서도 void AEnemy::GetHit_Implementation(...)로 수정 ... public: ... virtual void GetHit_Implementation(const FVector& ImpactPoint) override; ... }
- Weapon.cpp
// Weapon.h ... void AWeapon::OnBoxBeginOverlap(...) { ... if(CollisionBoxHit.GetAtor()) { ... if(HitInterface) { HitInterface->Execute_GetHit(CollisionHitBox.GetActor(), CollisionBoxHit.ImpactPoint); } ... } } ...Breakable.h에서도 interface를 사용하도록 수정
- Breakable.h
// Breakable.h ... #include "Interfaces/HitInterface.h" ... UCLASS() class STUDY_API ABreakable : public AActor, public IHitInterface { ... public: virtual void GetHit_Implementation(const FVector& ImpacePoint) override; ... }
BP_Breakable에서Event Get Hit노드를 생성할 수 있고, 우클릭후Add Call to Parent Function을 이용하면 c++ 버전도 호출 가능하다.
사운드 추가
필요한 사운드 에셋을
import하고Meta Sound를 생성한다.
BP_Breakable에서Play Sound at Location노드를 추가해주고 방금 생성한SFX_PotBreak를 넣어주면 파괴시 사운드가 재생된다.
일반적으로 게임에서 파괴된 오브젝트는 일정시간이 지난 뒤 사라진다.
Set Life Span노드를 추가해 구현해준다.
파괴 가능한 오브젝트를 여러개 배치하고 하나만 파괴했을 때, 연쇄작용으로 근처의 오브젝트들 또한 파괴될 수 있다. 하지만 GetHit에 의한 파괴만 사라지고 연쇄작용으로 인해 파괴된 오브젝트는 사라지지 않는다.
이를 해결하기 위해 우측의
Details패널에서On Chaos Break Event에 있는 +를 눌러 Event노드를 추가한다.
위에 있는
Set Life Span노드를 연결시켜준 뒤
우측
Details패널에서Notify Breaks를 활성화시켜주고 실행하면 연쇄적으로 파괴된 오브젝트들도 같이 사라지는 것을 확인할 수 있다.
파괴시 아이템 스폰
항아리 파괴시 아이템이 스폰되는 것을 구현한다.
먼저 필요한 에셋들을 다운받아import한다
보물 에셋은 Epic Games의 무료마켓에서 받고, 사운드의 경우 Soundimage 여기에서 적당한 것을 다운한다.
사용할
Meta Sound를 미리 생성해준다.
Item.cpp에서 파생된
Treasure클래스를 생성한다.
- Treasure.h
// Treasure.h ... protected: virtual void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override; ... private: UPROPERTY(EditAnywhere, Category = "Sound") USoundBase* PickupSound; ...
- Treasure.cpp
// Treausre.cpp ... #include "Characters/WraithCharacter.h" ... void ATreasure::OnSphereBeginOverlap(...) { // 부모버전의 OnSphereBeginOverlap을 사용하지 않을 것이므로 Super사용x AWraithCharacter* WraithCharacter = Cast<AWraithCharacter>(OtherActor); if(WraithCharacter) { if(PickupSound) { UGameplayStatics::PlaySoundAtLocation(this, PickupSound, GetActorLocation()); } // Treasure pickup시 파괴 Destroy(); } }
Treausre.cpp 기반 블루프린트를 생성한다.
Satic Mesh를 할당해주고
생성해둔
SFX_Treasure를 넣어준다.
우선 정상적으로 작동하는지 확인하기 위해 월드에
BP_Treasure를 배치하고 실행시켜본다.Blueprint를 이용한 구현
정상적으로 작동된다면 다시 돌아가서
Spawn Actor from Class노드를 추가해주고, spawn 대상을 방금 만든BP_Treausre,Collision Handling Override를Always Spawn, Ignore Collisions로 설정한다.
- Always Spawn, Ignore Collisions
액터를 생성하고, 충돌을 항상 무시한다. 생성된 액터는 충돌을 감지하지 않고 그자리에 생성된다.- Try to Adjust Location, But Always Spawn
충돌이 발생하면 충돌을 피하기 위해 액터의 위치를 조정하려고 시도하지만, 불가능한 경우에도 액터를 생성한다.- Try to Adjust Location, Don't Spawn if Still Colliding
충돌이 발생하면 충돌을 피하기 위해 액터의 위치를 조정하려고 시도하고, 불가능한 경우 액터를 생성하지 않는다.- Do Not Spawn
액터를 생성하지 않는다.
BP_Treasure의 mesh가 충돌하지 않게 해야하므로 Collision 관련 설정을 변경한다.
추가로 영상과 다르게 무한루프 오류가 발생할 수도 있다. 무언가를 파괴한 상태에서 또다시 파괴하려고 시도하는 경우 무한루프에 빠진다는것 같다.
이를 해결하기 위해 bool type 변수를 추가해주고 기본값을 false로 설정해준다.
GetHit event발생시 bBroken값을 참조하여 이미 파괴된 상태인 경우 return, 그렇지 않은 경우 bBroken값을 true로 변경한 뒤 spawn되도록 변경한다.
실행하면 정상적으로 작동하는 것을 확인할 수 있다.
BP_Treasure의 위치를 조금 조정해주면 좀더 깔끔하게 작동되는 것을 확인할 수 있다.
Make Transform노드를 이용하는 방법도 있다.c++를 이용한 구현
BP_Treasure에서Static Mesh를 설정하였으므로 c++에서는 이에 대한 정보가 없다.
c++에 기반한 spawn이 아닌 블루프린트에 기반한 spawn이 필요하다.
SpawnActor의 경우 템플릿 함수이고<>에 따라 어떤 c++ 클래스가 spawn될지 정해진다.
SpawnActor의 parameter에UClass*타입의 parameter가 있는데 이는<>와 동일한 역할을 한다. 이를 통해 블루프린트 클래스를 이용할 수 있다.
먼저UClass*타입의 변수를 하나 생성한다.
- Breakable.h
// Breakable.h private: // 블루프린트에서 사용할 클래스 UPROPERTY(EditAnywhere) UClass* TreasureClass; // 무한루프 방지용 bool bBroken = false; ...
- Breakable.cpp
// Breakable.cpp ... #include"Items/Treasure.h" ... void ABreakableActor::GetHit_Implementation(const FVector& ImpactPoint) { if(bBroken) return; bBroken = true; UWorld* World = GetWorld() if(World && TreasureClass) { FVector Location = GetActorLocation(); Location.Z -= 40.f; World->spawnActor<ATreasure>(TreasureClass, Location, GetActorRotation()); } }
BP_Breakabel에서Treasure Class를 선택하고 실행하면 정상적으로 동작되는 것을 확인할 수 있다.
<추가>
Trasure Class에 BP_Treasrue를 찾기엔 너무 많을 경우TSubclassOf<>사용시<>내부에 있는 것들만 표시되게 할 수 있다.
- Breakable.h
// Breakable.h private: ... // 전방선언 해도 되고 <>안에 class 사용해도 가능 UPROPETY(EditAnywhere) TSubclassOf<class ATreasure> TreasureClass; ...
컴파일 후 확인해보면 Treasure 관련한 것들만 나타나는 것을 확인할 수 있다.
이제 파괴시 파변들에 충돌이 발생하는 불편함을 해결해야 한다.
단순히Static Mesh가 캐릭터와의 충돌을 무시하게 하면, 파괴전에도 캐릭터와의 충돌을 무시하게 되므로Capsule Component를 이용해 파괴전엔 충돌가능하도록 해야한다.
- Breakable.h
// Breakable.h ... class UCapsuleComponent; ... private: ... // 충돌을 위한 capsule component UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UCapsuleComponent* Capsule;
- Breakable.cpp
// Breakable.cpp ... #include "Components/CapsuleComponent.h" ... ABreakable::ABreakable() { ... Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule")); Capsule->SetupAttachment(GetRootComponent()); Capsule->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); Capsule->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block); }
캡슐의 크기를 적당히 조절해준다.
마지막으로 파괴 이벤트 발생 이후 Capsule의 Collision에서 pawn을 무시하도록 하면 된다.
간혹 카메라가 파편때문에 돌아가는 경우 카메라도 동일하게 설정해주면 된다.
다양한 아이템 spawn
항아리 파괴시, 여러 아이템들이 나오도록 하고, 가치에 따라 골드량을 표시한다.
먼저 아이템별 골드 가치를 저장하기 위한 변수를 하나 선언한다.
- Treasure.h
// Treasure.h ... private: ... // Treasure 종류에 따른 골드가치 UPROPERTY(EditAnywhere, Category = "Treasure Properties") int32 Gold; ...
BP_Treausre에서 Gold값을 지정해주고
실제 게임처럼 보이도록, yaw 축 기준으로 회전하도록 만든다.
BP_Treasure우클릭후Child Blueprint Class를 생성해준다.
우측을 보면
Parent class가BP_Treasure인 것을 확인할 수 있다.
최상위 부모 블루프린트 클래스임을 알아보기 쉽게
BP_Treasure를BP_BaseTreasure로 변경하고, 필요한 만큼 자식클래스를 생성한다.
TArray를 사용하여 랜덤한 Treasure를 파괴시 얻을 수 있도록 코드를 작성한다.
- Breakable.h
// Breakable.h private: ... UPROPERTY(EditAnywhere, Category = "Breakable Properties") TArray<TSubclassOf<class ATreasure>> TreasureClasses; // 삭제 // TSubclassOf<class ATreasure> TreasureClass; ...
- Breakable.cpp
// Breakable.cpp void ABreakable::GetHit_Implementation(const FVector& ImpactPoint) { UWorld* World = GetWorld(); // TreasureClass => TreasureClasses.Num() > 0 으로 변경 if(World && TreasureClasses.Num() > 0) { FVector Location = GetActorLocation(); Location.Z -= 40.f; const int32 Selection = FMath::RandRange(0, TreasureClasees.Num() - 1); World->SpawnActor<ATreasure>(TreasureClasses[Selection], Location, GetActorRotation()); } }
컴파일 후
BP_Breakable에서 스폰할 블루프린트 클래스들을 넣어준다.
실행시 랜덤한 아이템들이 스폰되는것을 확인할 수 있다.
다양한 파괴가능 오브젝트 생성
다양한
BP_Treasure를 생성한것과 같이 다양한BP_Breakable을 생성하는 것도 가능하다.
동일하게Fracture mode에서 파괴가능하도록 설정하고,BP_Treasrue의 자식 블루프린트 클래스를 생성한 뒤, 메쉬와 Geometry collection을 할당해주면 된다.
Fracture mode를 통해 파괴시 파편이 나뉘도록 설정해주고
Geometry Collection도 생성해주고
기본
BP_Breakable->BP_BaseBreakable로 변경후 자식 블루프린트 클래스인BP_PotMedium생성
BP_PotMedium에서Geometry Collection할당
미디엄 사이즈의 항아리이므로, 큰 아이템들은 스폰되지 않도록 제외시킨다.
Niagara Systems
우클릭후
Niagara System을 클릭하면 생성할 수 있다.
New System from selected emitter(s)를 선택한다.
- New System from selected emitter(s)
사용 가능한 이미터 목록이 표시되고, 프로젝트에 있는 기존 이미터와 템플릿 이미터가 모두 포함되어 있음. 신규 시스템에 포함할 이미터를 선택하고 +를 눌러 추가가능. 기존 이미터를 선택하면 시스템은 이 이미터롭부터 상속되나, 템플릿 이미터를 선택하면 시스템은 상속받지 않음.- New system from a template or behavior example
여러 이펙트 시스템이 있는 템플릿 목록에서 선택 가능하며 아트 책임자나 크리에이티브 디렉터가 조정할 수 있음. 해당 옵션은 niagara에서 FX시스템이 어떻게 제작되는지에 대한 샘플을 제공.- Coopy existing system
기존 시스템 목록이 표시되고, 선택후 Finish를 선택하여 사용 가능- Create empty system
빈 프로젝트 생성
필요한emitter를 선택하고 +를 누른다. 모두 선택했다면Finish를 누른다.
- 시스템
왼쪽 노드는 시스템 노드를 의미.Niagara System은 이펙트 빌드에 필요한 모든 요소가 담긴 컨테이너이다. 시스템에서 다양한 빌딩 블록을 쌓아 전체적인 이펙트를 만들 수 있음. 시스템 단계의 행동을 수정한 뒤, 이펙트에 속하는 모든 요소에 적용할 수 있음.- 이미터
Niagara System에서 파티클이 생성되는 노드. 파티클의 생성 방식, 시간 경과에 따라 파티클에서 발생하는 현상, 파티클의 외관 및 행동을 제어. 이미터는 스택으로 구성되는데, 스택은 여러 그룹이 있고, 그룹 내에 개별 동작을 수행하는 모듈을 추가할 수 있음.
상세한 건 Niagara System API 참조
Spawn Rate를 선택하고 적절하게 조절한다.
Particle Spawn을 선택하고 필요에 따라 적절하게 값들을 조절한다.
Shape Location을 선택하고 값을 적절하게 조절한다.
Add Velocity를 선택하고 입자들이 날라다니지 않도록 속도를 조절하고Cone Angle을 늘려서 더 넓은 원뿔 모양이 되도록 한다.
Particle Update는 파티클 시뮬레이션이 계속되는 동안 각 파티클에 적용되는 것들인데 여기에 있는Gravity Force체크를 해제하면 게임에서 아이템이 젠되었을 때 보이는 이펙트를 만들 수 있다.
Drag의 값을 설정하면 파티클 입자가 공기저항에 의해 감속되는 정도를 설정 가능하다.
Scale Color는 색 조정이 가능하다.
그외에도
Particle Update옆의+를 통해 필요한 것들을 추가할 수 있다.Vortex Velocity를 추가하고, 파티클에 소용돌이를 추가함과 동시에 속도를 증감시켜 파티클이 소용돌이처럼 회전하도록 만들 수도 있다.
월드에 생성한
Niagara System을 드래그에서 배치시키면 인게임에서 어떻게 적용되는지 확인할 수 있다.
이제 생성한Niagara System을 여러곳에 적용시킬 수 있다.Blueprint에서 적용방법
BP_Item파일을 열고, 우측의Components패널에서Add를 누른 뒤Niagara Particle System Component를 검색하여 추가한다.
우측의
Details패널에서Niagara System Asset탭에 생성한Niagara System을 적용시키고 컴파일하면 끝.c++에서 적용방법
Niagara Component를 사용하기 위해서는 Build 파일에 나이아가라 모듈을 포함시켜야 한다.
- Study.Build.cs
// Study.Build.cs ... PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "GeometryCollectionEngine", "Niagara"}); ...
- Item.h
// Item.h ... class UNiagaraCVomponent; ... protected: // 무기에 장착한 나이아가라 이펙트 // 상속된 클래스에서도 사용가능해야함 UPROPERTY(EditAnywhere) UNiagaraComponent* EmbersEffect; ...
- Item.cpp
// Item.cpp ... #include "NiagaraComponent.h" ... AItem::AItem() { ... EmbersEffect = CreateDefaultSubobject<UNiagaraComponent>(TEXT("EmbersEffect")); EmbersEffect->SetupAttachment(GetRootComponent()); }
컴파일 후 실행시
Components패널에Embers Effect가 추가된 것을 확인할 수 있다.
클릭 후 우측의
Details패널에서 나이아가라 시스템 에셋을 넣고 실행하면 정상적으로 동작하는 것을 확인할 수 있다.
BP_BaseTreasure에 적용시 모든 보물들에게 Niagara System이 적용된 것을 확인할 수 있다.
무기에 Niagara System을 적용시키면 무기를 주워도 이펙트가 계속 발생되는 문제점이 생겨 이를 해결해야 한다.
- Weapon.cpp
// Weapon.cpp ... #include "NiagaraComponent.h" ... void AWeapon::EquipWeapon() { ... if(EmbersEffect) { EmbersEffect->Deactivate(); } }
컴파일 후 실행하면 더이상 장착시 이펙트 발생이 되지 않는 것을 확인할 수 있다.