Unreal Engine C++ The Ultimate Shooter Course (3)
카테고리: Unreal Engine
Chapter 7 Reloading
07-89 Retargeting Animations in Unreal Engine 5
- I cover the new way of retargeting animations in Unreal Engine 5 in a new YouTube video
- The link is in the resources for Lecture 88: Retargeting Animations
07-90 Retargeting Animations
- What does that mean, retarget? the process of retargeting and animation is taking that animation and assigning it to a different skeleton
- In order to retarget this animation to the Belka skeleton, we’re going to use something called a rig
- 위의 이미지에 나타난 과정을 거치는 이유는? To fix morphing problem
07-91 Edit Animations In Unreal
- we’re going to make a duplicate of this animation because we’re going to have a different version of this reload animation for each weapon
07-92 Ammo
- 언리얼에서 열거형에 대해 설명하면? 일반적인 enum이 아닌 enum class로 만들어야 한다 그리고 UENUM은 uint8만을 지원한다 (참고)
// 언리얼에서 지원하는 Map 클래스
TMap<EAmmoType, int32> AmmoMap;
07-93 Ammo Count Widget
07-94 Draw Ammo Count To Screen
- We’re going to draw this HUD to the screen using our player controller class and we’re going to do this from C++
- ShooterHUDOverlay를 만드는 이유는? We want to position AmmoCountBP where we want it
- 위의 과정은 무엇을 위함인지 설명하면? This right here will override what we have set in the project settings for every level
07-95 Weapon Ammo in C++
bool AShooterCharacter::WeaponHasAmmo()
{
if (EquippedWeapon == nullptr) return false;
return EquippedWeapon->GetAmmo() > 0;
}
07-96 Bind Weapon Ammo
07-97 Fixing Barrel Socket Location
- So we don’t actually want the mesh of our character We want the mesh of our equipped weapon
07-98 Improving Weapon Fire Code Lecture
- So we’ll see you in the next video when we start to restructure this code
07-99 Improving Weapon Fire Code
void AShooterCharacter::FireButtonPressed()
{
bFireButtonPressed = true;
FireWeapon();
}
void AShooterCharacter::FireWeapon()
{
if (EquippedWeapon == nullptr) return;
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (WeaponHasAmmo())
{
PlayFireSound();
SendBullet();
PlayGunfireMontage();
EquippedWeapon->DecrementAmmo();
StartFireTimer();
}
}
void AShooterCharacter::StartFireTimer()
{
CombatState = ECombatState::ECS_FireTimerInProgress;
GetWorldTimerManager().SetTimer(
AutoFireTimer,
this,
&AShooterCharacter::AutoFireReset,
AutomaticFireRate);
}
void AShooterCharacter::AutoFireReset()
{
CombatState = ECombatState::ECS_Unoccupied;
if (WeaponHasAmmo())
{
if (bFireButtonPressed)
{
FireWeapon();
}
}
else
{
// Reload Weapon
}
}
07-100 Reload Montage
- The reason we’re saying SMG is because this animation is specific to the SMG weapon and we’re going to have different sections here with the different animations for the different weapons
07-101 Reload Lecture
- Remember, if we’re not in the unoccupied state, we cannot fire the weapon
07-102 The Weapon Type
UCLASS()
class SHOOTER_API AWeapon : public AItem
{
//..
FORCEINLINE EWeaponType GetWeaponType() const { return WeaponType; }
//..
}
07-103 Reload Continued
07-104 Update AmmoMap
// 표현식( Ammo + Amount <= MagazineCapacity )이 true가 아니면 에러메시지를 출력한다
void AWeapon::ReloadAmmo(int32 Amount)
{
checkf(Ammo + Amount <= MagazineCapacity, TEXT("Attempted to reload with more than magazine capacity!"));
Ammo += Amount;
}
//..
// 언리얼 Map에서 Add는 Replace를 의미한다
AmmoMap.Add(AmmoType, CarriedAmmo);
//..
07-105 Bind Carried Ammo
07-106 Bind Weapon Name
07-107 Move Clip Lecture
- The scene component can be used as a reference point for its transform
- So what we’re going to do with this scene component is attach it to our hand
- Then we can simply use that scene component for its transform its location, rotation and scale
07-108 Grab and Release Clip
void AShooterCharacter::GrabClip()
{
//..
//------------------------------------------------------------------------------------------
// EAttachmentRule::KeepRelative에 대해 설명하면?
// Keeps current relative transform as the relative transform to the new parent
// https://docs.unrealengine.com/4.27/en-US/API/Runtime/Engine/Engine/EAttachmentRule/ 참고
// -----------------------------------------------------------------------------------------
FAttachmentTransformRules AttachmentRules(EAttachmentRule::KeepRelative, true);
HandSceneComponent->AttachToComponent(GetMesh(), AttachmentRules, FName(TEXT("Hand_L")));
HandSceneComponent->SetWorldTransform(ClipTransform);
EquippedWeapon->SetMovingClip(true);
//..
}
07-109 Weapon AnimBP
- Get owning actor is a node that returns an actor That is the actor that owns this animation blueprint
07-110 Moving the Clip
AShooterCharacter::AShooterCharacter() :
{
//..
// Now this is a scene component and really we don't even need to attach it to anything
// Because Grap clip is going to handle the attachment
// We're going to attach it to our handbone
HandSceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("HandSceneComp"));
//..
}
07-111 Clip Sounds
07-112 Pickup Sounds
//..
// 사운드 재생 함수
UGameplayStatics::PlaySound2D(this, Item->GetEquipSound());
//..
Chapter 8 Advanced Movement
08-113 Rotate Root Bone
- The root is the parent of the skeleton hierarchy So if we rotate the root, the body will follow
void UShooterAnimInstance::TurnInPlace()
{
if (ShooterCharacter == nullptr) return;
if (Speed > 0)
{
// Don't want to turn in place; Character is moving
}
else
{
CharacterYawLastFrame = CharacterYaw;
CharacterYaw = ShooterCharacter->GetActorRotation().Yaw;
const float YawDelta{ CharacterYaw - CharacterYawLastFrame };
RootYawOffset -= YawDelta;
// If we use a different number for each message for the key,
// Then we can print multiple messages on the screen at the same time
if (GEngine) GEngine->AddOnScreenDebugMessage(
1,
-1,
FColor::Blue,
FString::Printf(TEXT("CharacterYaw: %f"), CharacterYaw));
if (GEngine) GEngine->AddOnScreenDebugMessage(
2,
-1,
FColor::Red,
FString::Printf(TEXT("RootYawOffset: %f"), RootYawOffset));
}
}
- And so we’re going to use Yaw Offset to rotate our bone back toward the forward direction
- So this is how we use our route, yaw offset to rotate the bone back once we’ve rotated our camera for each frame
08-114 Turn In Place Animations
- We actually haven’t programmed the ability for the bone to rotate back towards our direction of movement All we’re doing is playing the animation
08-115 Animation Curves
08-116 Turn In Place Using Curve Values
void UShooterAnimInstance::TurnInPlace()
{
if (ShooterCharacter == nullptr) return;
if (Speed > 0)
{
// Don't want to turn in place; Character is moving
RootYawOffset = 0.f;
CharacterYaw = ShooterCharacter->GetActorRotation().Yaw;
CharacterYawLastFrame = CharacterYaw;
RotationCurveLastFrame = 0.f;
RotationCurve = 0.f;
}
else
{
CharacterYawLastFrame = CharacterYaw;
CharacterYaw = ShooterCharacter->GetActorRotation().Yaw;
const float YawDelta{ CharacterYaw - CharacterYawLastFrame };
// Root Yaw Offset, updated and clamped to [-180, 180]
RootYawOffset = UKismetMathLibrary::NormalizeAxis(RootYawOffset - YawDelta);
// if turning than? the value is 1.0
// if not than? the value is 0.0
const float Turning{ GetCurveValue(TEXT("Turning")) };
if (Turning > 0)
{
RotationCurveLastFrame = RotationCurve;
RotationCurve = GetCurveValue(TEXT("Rotation"));
const float DeltaRotation{ RotationCurve - RotationCurveLastFrame };
// if RootYawOffset > 0 than? Turning Left
// if RootYawOffset < 0 than? Turning Right
RootYawOffset > 0 ? RootYawOffset -= DeltaRotation : RootYawOffset += DeltaRotation;
const float ABSRootYawOffset{ FMath::Abs(RootYawOffset) };
if (ABSRootYawOffset > 90.f)
{
const float YawExcess{ ABSRootYawOffset - 90.f };
RootYawOffset > 0 ? RootYawOffset -= YawExcess : RootYawOffset += YawExcess;
}
}
if (GEngine) GEngine->AddOnScreenDebugMessage(1, -1, FColor::Cyan, FString::Printf(TEXT("RootYawOffset: %f"), RootYawOffset));
}
}
08-117 Hip Aim Offset
08-118 Aiming Aim Offset
08-119 Lean
void UShooterAnimInstance::Lean(float DeltaTime)
{
//..
// Explain below code
// This gives us a measure of how quickly we're turning
// And this is going to handle any sin(math) changes
const FRotator Delta{ UKismetMathLibrary::NormalizedDeltaRotator(CharacterRotation, CharacterRotationLastFrame) };
const float Target{ Delta.Yaw / DeltaTime };
//..
}
08-120 Lean Blendspace
08-121 Crouching Setup
08-122 Crouching Animations
08-123 Crouching AnimBP
08-124 Crouching Turn Animations
08-125 Retargeting Anims with Different Skeletons
08-126 Crouch Turn In Place AnimBP
- layered blend per bone에 대해 설명하면? A value of 0.0 means the Additive pose is not added to the Base input pose at all, while a value of 1.0 means the Additive pose is added fully to the Base input pose (참고)
08-127 Crouch Recoil Weight
void UShooterAnimInstance::TurnInPlace()
{
if (bTurningInPlace)
{
if (bReloading)
{
RecoilWeight = 1.f;
}
else
{
RecoilWeight = 0.f;
}
}
}
08-128 Crouch Walking Blendspace
08-129 Crouch Walking
08-130 Crouch Movement Speed and Jump
void AShooterCharacter::CrouchButtonPressed()
{
if (!GetCharacterMovement()->IsFalling())
{
bCrouching = !bCrouching;
}
if (bCrouching)
{
GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
}
else
{
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
}
}
08-131 Interp Capsule Half Height
void AShooterCharacter::InterpCapsuleHalfHeight(float DeltaTime)
{
// 아래 코드가 필요한 이유는?
// 캡슐 크기가 작아지는 만큼 메시를 들어올리기 위해
const float DeltaCapsuleHalfHeight{ InterpHalfHeight - GetCapsuleComponent()->GetScaledCapsuleHalfHeight() };
const FVector MeshOffset{ 0.f, 0.f, -DeltaCapsuleHalfHeight };
GetMesh()->AddLocalOffset(MeshOffset);
}
void AShooterCharacter::CrouchButtonPressed()
{
// 아래 코드는 무엇을 하는 코드인가?
// 상태에 따라 마찰계수를 조절하는 코드
if (!GetCharacterMovement()->IsFalling())
{
bCrouching = !bCrouching;
}
if (bCrouching)
{
GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
GetCharacterMovement()->GroundFriction = CrouchingGroundFriction;
}
else
{
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
GetCharacterMovement()->GroundFriction = BaseGroundFriction;
}
}
08-132 Tweaking Parameters
08-133 Aim Walking
08-134 Reconciling Aiming and Reloading
void AShooterCharacter::ReloadWeapon()
{
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (EquippedWeapon == nullptr) return;
// Do we have ammo of the correct type?
if (CarryingAmmo() && !EquippedWeapon->ClipIsFull())
{
// 이 코드가 여기 있어야 하는 이유는?
// We really only want to stop aiming if we're actually going to reload
if (bAiming)
{
StopAiming();
}
CombatState = ECombatState::ECS_Reloading;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && ReloadMontage)
{
AnimInstance->Montage_Play(ReloadMontage);
AnimInstance->Montage_JumpToSection(
EquippedWeapon->GetReloadMontageSection());
}
}
}
Chapter 9 Ammo Pickups
09-135 Item Interping Slide
09-136 Ammo Class
09-137 Overriding SetItemProperties
- 왜 AAmmo 클래스에서 AItem 클래스의 SetItemProperties() 함수가 정상동작 하지 않는가? AAmmo 클래스는 AmmoMesh를 사용하기 때문이다
09-138 PickupAmmo Function
09-139 Ammo Widget
- Overlay에 대해 설명하면? This will allow us to stack things on top of each other
09-140 Ammo Widget Continued
09-141 Bind Ammo Count
09-142 Bind Ammo Icon
09-143 Pickup Ammo on Overlap
void AAmmo::AmmoSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// 이 코드는 무엇을 위한 코드인가?
// 아이템 콜라이더에 충돌하면 자동으로 아이템이 습득되게 하는 코드
if (OtherActor)
{
auto OverlappedCharacter = Cast<AShooterCharacter>(OtherActor);
if (OverlappedCharacter)
{
StartItemCurve(OverlappedCharacter);
AmmoCollisionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
}
}
09-144 Interpolation Scene Components
09-145 Setup Interp Locations
void AShooterCharacter::InitializeInterpLocations()
{
FInterpLocation WeaponLocation{ WeaponInterpComp, 0 };
InterpLocations.Add(WeaponLocation);
FInterpLocation InterpLoc1{ InterpComp1, 0 };
InterpLocations.Add(InterpLoc1);
FInterpLocation InterpLoc2{ InterpComp2, 0 };
InterpLocations.Add(InterpLoc2);
FInterpLocation InterpLoc3{ InterpComp3, 0 };
InterpLocations.Add(InterpLoc3);
FInterpLocation InterpLoc4{ InterpComp4, 0 };
InterpLocations.Add(InterpLoc4);
FInterpLocation InterpLoc5{ InterpComp5, 0 };
InterpLocations.Add(InterpLoc5);
FInterpLocation InterpLoc6{ InterpComp6, 0 };
InterpLocations.Add(InterpLoc6);
}
09-146 Interp to Multiple Locations
FVector AItem::GetInterpLocation()
{
if (Character == nullptr) return FVector(0.f);
switch (ItemType)
{
case EItemType::EIT_Ammo:
return Character->GetInterpLocation(InterpLocIndex).SceneComponent->GetComponentLocation();
break;
case EItemType::EIT_Weapon:
return Character->GetInterpLocation(0).SceneComponent->GetComponentLocation();
break;
}
return FVector();
}
09-147 Limit Pickup and Equip Sounds
void AItem::PlayPickupSound()
{
if (Character)
{
if (Character->ShouldPlayPickupSound())
{
Character->StartPickupSoundTimer();
if (PickupSound)
{
UGameplayStatics::PlaySound2D(this, PickupSound);
}
}
}
}
void AShooterCharacter::StartPickupSoundTimer()
{
bShouldPlayPickupSound = false;
GetWorldTimerManager().SetTimer(
PickupSoundTimer,
this,
&AShooterCharacter::ResetPickupSoundTimer,
PickupSoundResetTime);
}
댓글남기기