Unreal Engine C++ The Ultimate Shooter Course (3)

Date:     Updated:

카테고리:

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

rig

match

base

reload

retarget

morphing

  • 위의 이미지에 나타난 과정을 거치는 이유는? 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

adjust

key

07-92 Ammo

  • 언리얼에서 열거형에 대해 설명하면? 일반적인 enum이 아닌 enum class로 만들어야 한다 그리고 UENUM은 uint8만을 지원한다 (참고)
// 언리얼에서 지원하는 Map 클래스
TMap<EAmmoType, int32> AmmoMap;

07-93 Ammo Count Widget

widget

custom

ammo

07-94 Draw Ammo Count To Screen

pc

  • We’re going to draw this HUD to the screen using our player controller class and we’re going to do this from C++

over

  • ShooterHUDOverlay를 만드는 이유는? We want to position AmmoCountBP where we want it

bp

cho

final

mode

  • 위의 과정은 무엇을 위함인지 설명하면? 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

select

set

ammo

code

07-97 Fixing Barrel Socket Location

add

  • 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

montage

drag

new

  • 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

conclude

node

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

class

hfile

delete

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);

//..

notify

07-105 Bind Carried Ammo

bind

end

07-106 Bind Weapon Name

item

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

notify

next

track

bone

hand

final

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

bp

create

ref

graph

  • Get owning actor is a node that returns an actor That is the actor that owns this animation blueprint

07-110 Moving the Clip

inside

clip

remember

nice

selbp

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

cue

sound

select

end

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

expose

conclude

  • 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

copy

remove

upper

rule

rrule

blend

loop

  • 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

curve

graph

meta

turn

reuse

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

offset

ao

down

end

connect

pitch

reload

clear

08-118 Aiming Aim Offset

one

aim

add

many

air

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

space

input

next

08-121 Crouching Setup

crouch

08-122 Crouching Animations

dup

end

upper

08-123 Crouching AnimBP

start

insert

rule

check

rulee

final

08-124 Crouching Turn Animations

look

down

noskin

first

next

end

08-125 Retargeting Anims with Different Skeletons

match

view

too

use

apply

re

temp

08-126 Crouch Turn In Place AnimBP

match

spin

upper

node

ani

curve

curvegood

again

speed

  • 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

weight

void UShooterAnimInstance::TurnInPlace()
{
	if (bTurningInPlace)
	{
		if (bReloading)
		{
			RecoilWeight = 1.f;
		}
		else
		{
			RecoilWeight = 0.f;
		}
	}
}

08-128 Crouch Walking Blendspace

crouch

blend

insert

08-129 Crouch Walking

bs

rule

nrule

time

pick

08-130 Crouch Movement Speed and Jump

void AShooterCharacter::CrouchButtonPressed()
{
	if (!GetCharacterMovement()->IsFalling())
	{
		bCrouching = !bCrouching;
	}
	if (bCrouching)
	{
		GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
	}
	else
	{
		GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
	}
}

remove

08-131 Interp Capsule Half Height

pause

low

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

mouse

rotate

08-133 Aim Walking

space

blend

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

weapon

09-136 Ammo Class

item

ammo

select

sound

09-137 Overriding SetItemProperties

  • 왜 AAmmo 클래스에서 AItem 클래스의 SetItemProperties() 함수가 정상동작 하지 않는가? AAmmo 클래스는 AmmoMesh를 사용하기 때문이다

09-138 PickupAmmo Function

real

09-139 Ammo Widget

  • Overlay에 대해 설명하면? This will allow us to stack things on top of each other

hud

align

color

cen

pos

09-140 Ammo Widget Continued

click

main

09-141 Bind Ammo Count

ammo

get

count

09-142 Bind Ammo Icon

texture

ar

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

static

sm

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

default

type

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);
}


맨 위로 이동하기

댓글남기기