Unreal Engine C++ The Ultimate Shooter Course (4)
카테고리: Unreal Engine
Chapter 10 Outline and Glow Effects
10-148 Outline Effect Theory
- When Unreal Engine renders the scene, it uses several buffers
- A buffer is just information pertaining to each pixel
- Now, the reason we’re talking about these buffers is because in order to create an outline effect
- Now, Unreal Engine has an additional buffer called custom depth, and you’re allowed to select specific objects to participate in the custom depth buffer
- Let’s take all the pixels that have no white on them and let’s call these pixels object pixels
- No white neighbors, call these interior pixels
- And here’s the result of subtracting away the interior pixels for each object(inverse object pixels) This is the basis for creating an outline effect
10-149 Post Process Materials
10-150 Custom Depth
10-151 Texel Position and Size
- Texels are containers for pixels
- Screen position is giving us the position on the screen and U, V coordinates for a given Texel
10-152 Show Interior Pixels
- If it’s negative after ceil and clamp, it’s going to be zero And if it’s positive after ceil and clamp, it’s going to be one
- Ceil에 대해 설명하면? 소수점을 무조건 올려 더 큰 정수로 만든 결과를 출력합니다 (참고)
- Clamp에 대해 설명하면? 값을 받아 최소치와 최대치로 정의된 특정 범위로 제한시킵니다 (참고)
10-153 Getting The Border
- At Multiply, the only thing that these two have in common that are both one is the border
10-154 Adding the Border to the Scene Color
- Lerp 노드에 대해 설명하면? If Alpha is 0.0, the first input is used If Alpha is 1.0, the second input is used (참고)
10-155 Hide Occluded Pixels
10-156 Change Outline Thickness
10-157 Color Tinting Effect
10-158 Multiple Colors with Custom Depth Stencil
10-159 Blend Materials with Material Functions
10-160 Fresnel Effect on Glow Material
10-161 Material Instances
10-162 Scrolling Lines Effect
10-163 Enable Custom Depth in C++
void AShooterCharacter::TraceForItems()
{
if (bShouldTraceForItems)
{
FHitResult ItemTraceResult;
FVector HitLocation;
TraceUnderCrosshairs(ItemTraceResult, HitLocation);
if (ItemTraceResult.bBlockingHit)
{
TraceHitItem = Cast<AItem>(ItemTraceResult.Actor);
if (TraceHitItem && TraceHitItem->GetPickupWidget())
{
TraceHitItem->GetPickupWidget()->SetVisibility(true);
// Enable Custom Depth
TraceHitItem->EnableCustomDepth();
}
if (TraceHitItemLastFrame)
{
if (TraceHitItem != TraceHitItemLastFrame)
{
TraceHitItemLastFrame->GetPickupWidget()->SetVisibility(false);
// Disable Custom Depth
TraceHitItemLastFrame->DisableCustomDepth();
}
}
TraceHitItemLastFrame = TraceHitItem;
}
}
else if (TraceHitItemLastFrame)
{
TraceHitItemLastFrame->GetPickupWidget()->SetVisibility(false);
// Disable Custom Depth
TraceHitItemLastFrame->DisableCustomDepth();
}
}
10-164 Dynamic Material Instances
- So we’re going to add this material index as a variable on the item class
- Now, the next thing we’re going to do if we want to be able to change materials properties at runtime is we’re going to use something called a dynamic material A dynamic material instance allows us to use a material instance and set properties on that material instance at runtime
- So why two different variables? The material instance is what we will select in our blueprint That way we’ll know which material instance to use And once we create a dynamic material instance, we’re going to use this material instance in that dynamic material instance
- The construction script is a script that runs under certain conditions It runs if we move our actor in the world or if we change a property on the actor This is run before the game starts Nothing in the construction script will be run during the game
- OnConstruction() is the C++ version of the construction script
void AItem::OnConstruction(const FTransform& Transform)
{
if (MaterialInstance)
{
DynamicMaterialInstance = UMaterialInstanceDynamic::Create(MaterialInstance, this);
// It's going to assign this instance for the material with the given material index
ItemMesh->SetMaterial(MaterialIndex, DynamicMaterialInstance);
}
}
10-165 Enable Glow Material in C++
10-166 Show Outline While Interping
void AItem::StartItemCurve(AShooterCharacter* Char)
{
//..
// 이코드가 필요한 이유는?
// ItemCurve가 진행되는 동안에는 CustomDepth가 살아있어야 하기 때문이다
bCanChangeCustomDepth = false;
}
10-167 Curve Vector for Material Parameters
void AItem::UpdatePulse()
{
if (ItemState != EItemState::EIS_Pickup) return;
const float ElapsedTime{ GetWorldTimerManager().GetTimerElapsed(PulseTimer) };
if (PulseCurve)
{
FVector CurveValue{ PulseCurve->GetVectorValue(ElapsedTime) };
DynamicMaterialInstance->SetScalarParameterValue(TEXT("GlowAmount"), CurveValue.X * GlowAmount);
DynamicMaterialInstance->SetScalarParameterValue(TEXT("FresnelExponent"), CurveValue.Y * FresnelExponent);
DynamicMaterialInstance->SetScalarParameterValue(TEXT("FresnelReflectFraction"), CurveValue.Z * FresnelReflectFraction);
}
}
10-168 Material Pulse When Interping
void AItem::UpdatePulse()
{
float ElapsedTime{};
FVector CurveValue{};
switch (ItemState)
{
case EItemState::EIS_Pickup:
if (PulseCurve)
{
ElapsedTime = GetWorldTimerManager().GetTimerElapsed(PulseTimer);
CurveValue = PulseCurve->GetVectorValue(ElapsedTime);
}
break;
case EItemState::EIS_EquipInterping:
if (InterpPulseCurve)
{
// ItemInterpTimer를 사용해도 되는 이유는?
// itemZCurve의 특정 구간과 MaterialPulseCurve의 특정 구간을 동기화 시켜
// 해당 구간을 반짝이게 하고 싶기 때문이다
ElapsedTime = GetWorldTimerManager().GetTimerElapsed(ItemInterpTimer);
CurveValue = InterpPulseCurve->GetVectorValue(ElapsedTime);
}
break;
}
if (DynamicMaterialInstance)
{
DynamicMaterialInstance->SetScalarParameterValue(TEXT("GlowAmount"), CurveValue.X * GlowAmount);
DynamicMaterialInstance->SetScalarParameterValue(TEXT("FresnelExponent"), CurveValue.Y * FresnelExponent);
DynamicMaterialInstance->SetScalarParameterValue(TEXT("FresnelReflectFraction"), CurveValue.Z * FresnelReflectFraction);
}
}
void AItem::StartItemCurve(AShooterCharacter* Char)
{
//..
// 이 코드의 목적은?
// 타이머 초기화
GetWorldTimerManager().ClearTimer(PulseTimer);
//..
}
10-169 Inventory Slot Widget
10-170 Inventory Bar Widget
10-171 Add Button Icons to the Inventory Bar
10-172 Add Inventory Bar to the ShooterHUDOverlay
10-173 Inventory Array in C++
class SHOOTER_API AShooterCharacter : public ACharacter
{
//..
// 헤더파일에서 유니폼 초기화가 가능하다
const int32 INVENTORY_CAPACITY{ 6 };
//..
}
10-174 Binding the Background Icon
10-175 Binding the Item Icon
10-176 Binding the Ammo Icon
10-177 Binding Weapon Ammo Text
10-178 Set Item State for Picked Up
void AShooterCharacter::GetPickupItem(AItem* Item)
{
//..
if (Weapon)
{
// 이 코드의 결과는?
// 슬롯 인덱스가 하나씩 밀려나간다
Weapon->SetSlotIndex(Inventory.Num());
if (Inventory.Num() < INVENTORY_CAPACITY)
{
Inventory.Add(Weapon);
Weapon->SetItemState(EItemState::EIS_PickedUp);
}
else // Inventory is full! Swap with EquippedWeapon
{
SwapWeapon(Weapon);
}
}
//..
}
10-179 Send Slot Index with a Delegate
10-180 Play Widget Animation
10-181 Exchange Inventory Items
void AShooterCharacter::SwapWeapon(AWeapon* WeaponToSwap)
{
//..
// 아래 코드에 대해 설명하면?
// 인벤토리의 아이템이 교체된다
if (Inventory.Num() - 1 >= EquippedWeapon->GetSlotIndex())
{
Inventory[EquippedWeapon->GetSlotIndex()] = WeaponToSwap;
WeaponToSwap->SetSlotIndex(EquippedWeapon->GetSlotIndex());
}
//..
}
void AShooterCharacter::ExchangeInventoryItems(int32 CurrentItemIndex, int32 NewItemIndex)
{
if ((CurrentItemIndex == NewItemIndex) || (NewItemIndex >= Inventory.Num()) || (CombatState != ECombatState::ECS_Unoccupied)) return;
auto OldEquippedWeapon = EquippedWeapon;
auto NewWeapon = Cast<AWeapon>(Inventory[NewItemIndex]);
EquipWeapon(NewWeapon);
// 아래 상태는 무슨 상태로 전환 되는 것인지 설명하면?
// 장착하고 있지는 않으나 인벤토리에 있는 상태
OldEquippedWeapon->SetItemState(EItemState::EIS_PickedUp);
NewWeapon->SetItemState(EItemState::EIS_Equipped);
}
10-182 Disable Trace While Interping
void AShooterCharacter::SelectButtonPressed()
{
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (TraceHitItem)
{
TraceHitItem->StartItemCurve(this);
// 아래 코드가 필요한 이유는?
// It's not going to allow us to hit the select button again and
// Start the item curve again when we just started the item curve
TraceHitItem = nullptr;
}
}
10-183 Prevent Swapping while Reloading
void AShooterCharacter::SelectButtonPressed()
{
// 아래 코드가 필요한 이유는?
// ECS_Unoccupied 상태가 아니면 아이템을 얻지 못하게 하기 위함
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (TraceHitItem)
{
TraceHitItem->StartItemCurve(this);
TraceHitItem = nullptr;
}
}
void AShooterCharacter::ExchangeInventoryItems(int32 CurrentItemIndex, int32 NewItemIndex)
{
// 아래 코드에서 가장 중요한 것을 설명하면?
// ECombatState::ECS_Unoccupied 상태가 아니면 아이템 교환을 못하게 한다
if ((CurrentItemIndex == NewItemIndex) || (NewItemIndex >= Inventory.Num()) || (CombatState != ECombatState::ECS_Unoccupied)) return;
auto OldEquippedWeapon = EquippedWeapon;
auto NewWeapon = Cast<AWeapon>(Inventory[NewItemIndex]);
EquipWeapon(NewWeapon);
OldEquippedWeapon->SetItemState(EItemState::EIS_PickedUp);
NewWeapon->SetItemState(EItemState::EIS_Equipped);
}
10-184 Equip Montage
10-185 Play Equip Sound while Swapping
void AItem::PlayEquipSound(bool bForcePlaySound)
{
if (Character)
{
// bForcePlaySound 매개변수가 필요한 이유는?
// 사운드를 강제적으로 내야할 상황에 사용하기 위함이다
if (bForcePlaySound)
{
if (EquipSound)
{
UGameplayStatics::PlaySound2D(this, EquipSound);
}
}
else if (Character->ShouldPlayEquipSound())
{
Character->StartEquipSoundTimer();
if (EquipSound)
{
UGameplayStatics::PlaySound2D(this, EquipSound);
}
}
}
}
10-186 Swap Pickup Text
10-187 Swap Animation Limitations
void AShooterCharacter::EquipWeapon(AWeapon* WeaponToEquip, bool bSwapping)
{
if (WeaponToEquip)
{
const USkeletalMeshSocket* HandSocket = GetMesh()->GetSocketByName(
FName("RightHandSocket"));
if (HandSocket)
{
// Attach the Weapon to the hand socket RightHandSocket
HandSocket->AttachActor(WeaponToEquip, GetMesh());
}
if (EquippedWeapon == nullptr)
{
// -1 == no EquippedWeapon yet. No need to reverse the icon animation
EquipItemDelegate.Broadcast(-1, WeaponToEquip->GetSlotIndex());
}
// bSwapping 변수가 필요한 이유를 설명하면?
// 무기를 교체하는 상황이 아니라 무기를 얻고있는 상황에서는 UI 애니메이션이 동작하지 않게 하기위해
else if(!bSwapping)
{
EquipItemDelegate.Broadcast(EquippedWeapon->GetSlotIndex(), WeaponToEquip->GetSlotIndex());
}
// Set EquippedWeapon to the newly spawned Weapon
EquippedWeapon = WeaponToEquip;
EquippedWeapon->SetItemState(EItemState::EIS_Equipped);
}
}
void AShooterCharacter::ExchangeInventoryItems(int32 CurrentItemIndex, int32 NewItemIndex)
{
// ECombatState::ECS_Equipping 상태를 추가한 이유를 설명하면?
// 무기를 교체중인 상황에서 다시 교체를 시도했을때 기다리지 않고 교체되게 하기 위함이다
const bool bCanExchangeItems =
(CurrentItemIndex != NewItemIndex) &&
(NewItemIndex < Inventory.Num()) &&
(CombatState == ECombatState::ECS_Unoccupied || CombatState == ECombatState::ECS_Equipping);
if (bCanExchangeItems)
{
auto OldEquippedWeapon = EquippedWeapon;
auto NewWeapon = Cast<AWeapon>(Inventory[NewItemIndex]);
EquipWeapon(NewWeapon);
OldEquippedWeapon->SetItemState(EItemState::EIS_PickedUp);
NewWeapon->SetItemState(EItemState::EIS_Equipped);
CombatState = ECombatState::ECS_Equipping;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && EquipMontage)
{
AnimInstance->Montage_Play(EquipMontage, 1.0f);
AnimInstance->Montage_JumpToSection(FName("Equip"));
}
NewWeapon->PlayEquipSound(true);
}
}
10-188 Create Icon Animation
10-189 Create Icon Highlight Delegate
// 아이콘 애니메이션 델리게이트 선언
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FHighlightIconDelegate, int32, SlotIndex, bool, bStartAnimation);
10-190 Functions to Broadcast Icon Highlight Delegate
void AShooterCharacter::HighlightInventorySlot()
{
// 아래 코드에 대해 설명하면?
// 델리게이트를 호출하는 코드이다
const int32 EmptySlot{ GetEmptyInventorySlot() };
HighlightIconDelegate.Broadcast(EmptySlot, true);
HighlightedSlot = EmptySlot;
}
10-191 Calling Highlight and UnHighlight Inventory Slot
void AItem::FinishInterping()
{
bInterping = false;
if (Character)
{
Character->IncrementInterpLocItemCount(InterpLocIndex, -1);
Character->GetPickupItem(this);
// Important
Character->UnHighlightInventorySlot();
}
//..
}
void AItem::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AShooterCharacter* ShooterCharacter = Cast<AShooterCharacter>(OtherActor);
if (ShooterCharacter)
{
ShooterCharacter->IncrementOverlappedItemCount(1);
// Important
ShooterCharacter->UnHighlightInventorySlot();
}
}
}
10-192 Assigning the Icon Delegate
10-193 Data Tables FTableRowBase
USTRUCT(BlueprintType)
struct FItemRarityTable : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor GlowColor;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor LightColor;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor DarkColor;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 NumberOfStars;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UTexture2D* IconBackground;
};
class SHOOTER_API AItem : public AActor
{
GENERATED_BODY()
//..
/** Item Rarity data table */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = DataTable, meta = (AllowPrivateAccess = "true"))
class UDataTable* ItemRarityDataTable;
//..
}
- Now, UDataTable is not the same thing as FTableRowBase
- OnConstruction() 함수에 대해 설명하면? It gets called When the item changes or when we move it in the world
10-194 Accessing Data Table Rows in C++
void AItem::OnConstruction(const FTransform& Transform)
{
if (MaterialInstance)
{
DynamicMaterialInstance = UMaterialInstanceDynamic::Create(MaterialInstance, this);
ItemMesh->SetMaterial(MaterialIndex, DynamicMaterialInstance);
}
EnableGlowMaterial();
// Load the data in the Item Rarity Data Table
// Path to the Item Rarity Data Table
FString RarityTablePath(TEXT("DataTable'/Game/_Game/DataTable/ItemRarityDataTable.ItemRarityDataTable'"));
UDataTable* RarityTableObject = Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), nullptr, *RarityTablePath));
if (RarityTableObject)
{
FItemRarityTable* RarityRow = nullptr;
switch (ItemRarity)
{
case EItemRarity::EIR_Damaged:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Damaged"), TEXT(""));
break;
case EItemRarity::EIR_Common:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Common"), TEXT(""));
break;
case EItemRarity::EIR_Uncommon:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Uncommon"), TEXT(""));
break;
case EItemRarity::EIR_Rare:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Rare"), TEXT(""));
break;
case EItemRarity::EIR_Legendary:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Legendary"), TEXT(""));
break;
}
if (RarityRow)
{
GlowColor = RarityRow->GlowColor;
LightColor = RarityRow->LightColor;
DarkColor = RarityRow->DarkColor;
NumberOfStars = RarityRow->NumberOfStars;
IconBackground = RarityRow->IconBackground;
}
}
}
- OnConstruction() 함수에 대해 설명하면? It gets called When the item changes or when we move it in the world
- StaticLoadObject()에 대해 설명하면? Find or load an object by string name with optional outer and filename specifications (참고)
10-195 Setting Widget Colors from Data Table Values
10-196 Setting Glow Color from Data Table Value
void AItem::OnConstruction(const FTransform& Transform)
{
// Load the data in the Item Rarity Data Table
// Path to the Item Rarity Data Table
FString RarityTablePath(TEXT("DataTable'/Game/_Game/DataTable/ItemRarityDataTable.ItemRarityDataTable'"));
UDataTable* RarityTableObject = Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), nullptr, *RarityTablePath));
if (RarityTableObject)
{
FItemRarityTable* RarityRow = nullptr;
switch (ItemRarity)
{
case EItemRarity::EIR_Damaged:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Damaged"), TEXT(""));
break;
case EItemRarity::EIR_Common:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Common"), TEXT(""));
break;
case EItemRarity::EIR_Uncommon:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Uncommon"), TEXT(""));
break;
case EItemRarity::EIR_Rare:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Rare"), TEXT(""));
break;
case EItemRarity::EIR_Legendary:
RarityRow = RarityTableObject->FindRow<FItemRarityTable>(FName("Legendary"), TEXT(""));
break;
}
if (RarityRow)
{
GlowColor = RarityRow->GlowColor;
LightColor = RarityRow->LightColor;
DarkColor = RarityRow->DarkColor;
NumberOfStars = RarityRow->NumberOfStars;
IconBackground = RarityRow->IconBackground;
}
}
// 이코드가 여기있는 이유는?
// 위에서 GlowColor를 불러오고 난뒤 Set을 해줘야 하기 때문이다
if (MaterialInstance)
{
DynamicMaterialInstance = UMaterialInstanceDynamic::Create(MaterialInstance, this);
DynamicMaterialInstance->SetVectorParameterValue(TEXT("FresnelColor"), GlowColor);
ItemMesh->SetMaterial(MaterialIndex, DynamicMaterialInstance);
EnableGlowMaterial();
}
}
10-197 Set Post Process Highlight Color from Data Table
struct FItemRarityTable : public FTableRowBase
{
GENERATED_BODY()
//..
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 CustomDepthStencil;
};
void AItem::OnConstruction(const FTransform& Transform)
{
//..
if (GetItemMesh())
{
GetItemMesh()->SetCustomDepthStencilValue(RarityRow->CustomDepthStencil);
}
//..
}
- What does StaticLoadObject do? Takes a UClass and a path and constructs an instance of the specified class
Chapter 11 Multiple Weapon Types
11-198 Weapon Data Table
11-199 Getting Data from Weapon Data Table
void AWeapon::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
const FString WeaponTablePath{ TEXT("DataTable'/Game/_Game/DataTable/WeaponDataTable.WeaponDataTable'") };
UDataTable* WeaponTableObject = Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), nullptr, *WeaponTablePath));
if (WeaponTableObject)
{
FWeaponDataTable* WeaponDataRow = nullptr;
switch (WeaponType)
{
case EWeaponType::EWT_SubmachineGun:
WeaponDataRow = WeaponTableObject->FindRow<FWeaponDataTable>(FName("SubmachineGun"), TEXT(""));
break;
case EWeaponType::EWT_AssaultRifle:
WeaponDataRow = WeaponTableObject->FindRow<FWeaponDataTable>(FName("AssaultRifle"), TEXT(""));
break;
}
if (WeaponDataRow)
{
AmmoType = WeaponDataRow->AmmoType;
Ammo = WeaponDataRow->WeaponAmmo;
MagazineCapacity = WeaponDataRow->MagazingCapacity;
SetPickupSound(WeaponDataRow->PickupSound);
SetEquipSound(WeaponDataRow->EquipSound);
GetItemMesh()->SetSkeletalMesh(WeaponDataRow->ItemMesh);
SetItemName(WeaponDataRow->ItemName);
SetIconItem(WeaponDataRow->InventoryIcon);
SetAmmoIcon(WeaponDataRow->AmmoIcon);
}
}
}
11-200 Assault Rifle Glow Material
11-201 Set Material Instance and Index with Data Table
struct FWeaponDataTable : public FTableRowBase
{
//..
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UMaterialInstance* MaterialInstance;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MaterialIndex;
}
void AWeapon::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
//..
SetMaterialInstance(WeaponDataRow->MaterialInstance);
// 아래 두 구문이 필요한 이유는?
// 무기마다 material 정보가 다르다 때문에 이전 material 정보를 지워 줘야한다
PreviousMaterialIndex = GetMaterialIndex();
GetItemMesh()->SetMaterial(PreviousMaterialIndex, nullptr);
SetMaterialIndex(WeaponDataRow->MaterialIndex);
// Item 클래스에서 아래 구문을 이미 실행하고 있는데 여기서 또 하는 이유는?
// Dynamic으로 Material를 교체해주기 위해
if (GetMaterialInstance())
{
SetDynamicMaterialInstance(UMaterialInstanceDynamic::Create(GetMaterialInstance(), this));
GetDynamicMaterialInstance()->SetVectorParameterValue(TEXT("FresnelColor"), GetGlowColor());
GetItemMesh()->SetMaterial(GetMaterialIndex(), GetDynamicMaterialInstance());
EnableGlowMaterial();
}
}
11-202 Adding Barrel Socket to AR
11-203 FABRIK IK
- What is the Fabric? Fabric is an inverse kinematics technique that allows us to move a particular bone on our skeleton
11-204 Switch On Weapon Type
11-205 Disable FABRIK when Reloading
11-206 AR Reload Animation Bone Name
11-207 Reload Montage Section in Data Table
11-208 AR AnimBP
11-209 Set AnimBP for Weapon from Data Table
11-210 Different Crosshairs Per Weapon
11-211 Drawing Crosshairs from Weapon Variables
11-212 More Weapon Properties in Data Table
11-213 Use Weapon Properties in FireWeapon
struct FWeaponDataTable : public FTableRowBase
{
//..
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float AutoFireRate;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UParticleSystem* MuzzleFlash;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
USoundCue* FireSound;
}
class SHOOTER_API AWeapon : public AItem
{
//..
/** The speed at which automatic fire happens */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = DataTable, meta = (AllowPrivateAccess = "true"))
float AutoFireRate;
/** Particle system spawned at the BarrelSocket */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = DataTable, meta = (AllowPrivateAccess = "true"))
UParticleSystem* MuzzleFlash;
/** Sound played when the weapon is fired */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = DataTable, meta = (AllowPrivateAccess = "true"))
USoundCue* FireSound;
//..
FORCEINLINE float GetAutoFireRate() const { return AutoFireRate; }
FORCEINLINE UParticleSystem* GetMuzzleFlash() const { return MuzzleFlash; }
FORCEINLINE USoundCue* GetFireSound() const { return FireSound; }
}
11-214 Adding Pistol Assets
11-215 Hide Bone By Name
void AWeapon::BeginPlay()
{
Super::BeginPlay();
// 아래 코드에서 EPhysBodyOp::PBO_None를 세팅해준 이유는?
// We don't care about physics for this
if (BoneToHide != FName(""))
{
GetItemMesh()->HideBoneByName(BoneToHide, EPhysBodyOp::PBO_None);
}
}
11-216 Pistol Icon
11-217 Pistol Data Table Properties
11-218 Barrel Socket on the Pistol
11-219 Pistol Reload Animation
11-220 Pistol FABRIK IK
11-221 Pistol Aiming Pose
11-222 Aiming Pose for Each Weapon
11-223 Pistol Slide Curve
11-224 Pistol Slide Timer
void AShooterCharacter::FireWeapon()
{
if (EquippedWeapon == nullptr) return;
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (WeaponHasAmmo())
{
PlayFireSound();
SendBullet();
PlayGunfireMontage();
EquippedWeapon->DecrementAmmo();
StartFireTimer();
// Important
if (EquippedWeapon->GetWeaponType() == EWeaponType::EWT_Pistol)
{
// Start moving slide timer
EquippedWeapon->StartSlideTimer();
}
}
}
11-225 Update Slide Displacement
void AWeapon::UpdateSlideDisplacement()
{
if (SlideDisplacementCurve && bMovingSlide)
{
const float ElapsedTime{ GetWorldTimerManager().GetTimerElapsed(SlideTimer) };
const float CurveValue{ SlideDisplacementCurve->GetFloatValue(ElapsedTime) };
SlideDisplacement = CurveValue * MaxSlideDisplacement;
}
}
11-226 Transform Pistol Slide Bone
11-227 Pistol Glow Material
11-228 Semi Automatic Fire
void AShooterCharacter::AutoFireReset()
{
CombatState = ECombatState::ECS_Unoccupied;
if (EquippedWeapon == nullptr) return;
if (WeaponHasAmmo())
{
// Important
if (bFireButtonPressed && EquippedWeapon->GetAutomatic())
{
FireWeapon();
}
}
else
{
ReloadWeapon();
}
}
11-229 Stop Aiming when Exchanging Weapons
void AShooterCharacter::ExchangeInventoryItems(int32 CurrentItemIndex, int32 NewItemIndex)
{
const bool bCanExchangeItems =
(CurrentItemIndex != NewItemIndex) &&
(NewItemIndex < Inventory.Num()) &&
(CombatState == ECombatState::ECS_Unoccupied || CombatState == ECombatState::ECS_Equipping);
if (bCanExchangeItems)
{
// Important
if (bAiming)
{
StopAiming();
}
//..
}
}
void AShooterCharacter::AimingButtonPressed()
{
// So now if the combat state is reloading or equipping, we will not aim
bAimingButtonPressed = true;
if (CombatState != ECombatState::ECS_Reloading && CombatState != ECombatState::ECS_Equipping)
{
Aim();
}
}
댓글남기기