Unreal Engine C++ The Ultimate Shooter Course (3)
카테고리: Unreal Engine
Chapter 7 ReloadingPermalink
07-89 Retargeting Animations in Unreal Engine 5Permalink
- 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 AnimationsPermalink
- 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 UnrealPermalink
- 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 AmmoPermalink
- 언리얼에서 열거형에 대해 설명하면? 일반적인 enum이 아닌 enum class로 만들어야 한다 그리고 UENUM은 uint8만을 지원한다 (참고)
// 언리얼에서 지원하는 Map 클래스
TMap<EAmmoType, int32> AmmoMap;
07-93 Ammo Count WidgetPermalink
07-94 Draw Ammo Count To ScreenPermalink
- 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++Permalink
bool AShooterCharacter::WeaponHasAmmo()
{
if (EquippedWeapon == nullptr) return false;
return EquippedWeapon->GetAmmo() > 0;
}
07-96 Bind Weapon AmmoPermalink
07-97 Fixing Barrel Socket LocationPermalink
- 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 LecturePermalink
- So we’ll see you in the next video when we start to restructure this code
07-99 Improving Weapon Fire CodePermalink
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 MontagePermalink
- 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 LecturePermalink
- Remember, if we’re not in the unoccupied state, we cannot fire the weapon
07-102 The Weapon TypePermalink
UCLASS()
class SHOOTER_API AWeapon : public AItem
{
//..
FORCEINLINE EWeaponType GetWeaponType() const { return WeaponType; }
//..
}
07-103 Reload ContinuedPermalink
07-104 Update AmmoMapPermalink
// 표현식( 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 AmmoPermalink
07-106 Bind Weapon NamePermalink
07-107 Move Clip LecturePermalink
- 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 ClipPermalink
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 AnimBPPermalink
- Get owning actor is a node that returns an actor That is the actor that owns this animation blueprint
07-110 Moving the ClipPermalink
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 SoundsPermalink
07-112 Pickup SoundsPermalink
//..
// 사운드 재생 함수
UGameplayStatics::PlaySound2D(this, Item->GetEquipSound());
//..
Chapter 8 Advanced MovementPermalink
08-113 Rotate Root BonePermalink
- 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 AnimationsPermalink
- 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 CurvesPermalink
08-116 Turn In Place Using Curve ValuesPermalink
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 OffsetPermalink
08-118 Aiming Aim OffsetPermalink
08-119 LeanPermalink
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 BlendspacePermalink
08-121 Crouching SetupPermalink
08-122 Crouching AnimationsPermalink
08-123 Crouching AnimBPPermalink
08-124 Crouching Turn AnimationsPermalink
08-125 Retargeting Anims with Different SkeletonsPermalink
08-126 Crouch Turn In Place AnimBPPermalink
- 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 WeightPermalink
void UShooterAnimInstance::TurnInPlace()
{
if (bTurningInPlace)
{
if (bReloading)
{
RecoilWeight = 1.f;
}
else
{
RecoilWeight = 0.f;
}
}
}
08-128 Crouch Walking BlendspacePermalink
08-129 Crouch WalkingPermalink
08-130 Crouch Movement Speed and JumpPermalink
void AShooterCharacter::CrouchButtonPressed()
{
if (!GetCharacterMovement()->IsFalling())
{
bCrouching = !bCrouching;
}
if (bCrouching)
{
GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
}
else
{
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
}
}
08-131 Interp Capsule Half HeightPermalink
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 ParametersPermalink
08-133 Aim WalkingPermalink
08-134 Reconciling Aiming and ReloadingPermalink
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 PickupsPermalink
09-135 Item Interping SlidePermalink
09-136 Ammo ClassPermalink
09-137 Overriding SetItemPropertiesPermalink
- 왜 AAmmo 클래스에서 AItem 클래스의 SetItemProperties() 함수가 정상동작 하지 않는가? AAmmo 클래스는 AmmoMesh를 사용하기 때문이다
09-138 PickupAmmo FunctionPermalink
09-139 Ammo WidgetPermalink
- Overlay에 대해 설명하면? This will allow us to stack things on top of each other
09-140 Ammo Widget ContinuedPermalink
09-141 Bind Ammo CountPermalink
09-142 Bind Ammo IconPermalink
09-143 Pickup Ammo on OverlapPermalink
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 ComponentsPermalink
09-145 Setup Interp LocationsPermalink
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 LocationsPermalink
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 SoundsPermalink
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);
}
댓글남기기