캐릭터 스켈레탈 메시에 소켓 추가하기


그런데 하다보니 카메라에 달게 되어서 소켓이 상관 없어졌다.
샌드박스 무기 클래스 만들기
액터 WeaponBase파일과 이를 상속받은 SandboxWeaponBase, 또 SandboxWeaponBase를 상속받은 BP_SandboxWeaponBase_Shotgun을 만든다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WeaponBase.generated.h"
UCLASS()
class ADVANCEDHOMEWORK1_API AWeaponBase : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AWeaponBase();
UFUNCTION(BlueprintCallable)
virtual void Fire();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float MaxAmmo;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float CurrentAmmo;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon Stats")
int32 PelletCount; // 한 발당 총알 개수
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon Stats")
float SpreadAngle; // 탄이 퍼지는 각도
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon Stats")
float Range; // 사거리
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon Stats|Recoil")
float VerticalRecoil; // 위로 튀는 정도
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon Stats|Recoil")
float RecoilDuration; // 반동이 일어나는 시간
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "WeaponBase.h"
// Sets default values
AWeaponBase::AWeaponBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
void AWeaponBase::Fire()
{
}
// Called when the game starts or when spawned
void AWeaponBase::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AWeaponBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "WeaponBase.h"
#include "SandboxWeaponBase.generated.h"
/**
*
*/
UCLASS()
class ADVANCEDHOMEWORK1_API ASandboxWeaponBase : public AWeaponBase
{
GENERATED_BODY()
public:
virtual void Fire() override;
UFUNCTION(BlueprintImplementableEvent)
void SandboxFire();
UFUNCTION(BlueprintCallable)
void LinetraceOneShot(FVector Direction);
// 사운드
UFUNCTION(BlueprintCallable)
void PlaySound(USoundBase* Sound);
UFUNCTION(BlueprintCallable)
void Reload();
UFUNCTION(BlueprintCallable)
void UpdateAmmo();
UFUNCTION(BlueprintCallable)
void SetAiming(bool bNewAiming);
protected:
UPROPERTY(BlueprintReadOnly, Category = "Combat")
bool bIsAiming = false;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "SandboxWeaponBase.h"
#include "GameFramework/Character.h" // ACharacter 클래스 정의
#include "Camera/CameraComponent.h"
#include "Kismet/GameplayStatics.h"
void ASandboxWeaponBase::Fire()
{
SandboxFire();
}
void ASandboxWeaponBase::LinetraceOneShot(FVector Direction)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("LineTrace")));
AActor* MyOwner = GetOwner();
if (MyOwner)
{
ACharacter* MyCharacter = Cast<ACharacter>(MyOwner);
if (MyCharacter)
{
// 캐릭터의 GetMesh()를 호출
USkeletalMeshComponent* CharacterMesh = MyCharacter->GetMesh();
FVector SocketLocation = CharacterMesh->GetSocketLocation(FName("weapon_r_muzzle"));
UE_LOG(LogTemp, Warning, TEXT("Socket Location: %s"), *SocketLocation.ToString());
// 소켓 위치 가져오기
FHitResult Hit(ForceInit); // ForceInit으로 강제 초기화 가능
UCameraComponent* Camera = MyCharacter->FindComponentByClass<UCameraComponent>();
if (!Camera) return;
// 2. 조준점은 카메라의 위치에서 시작합니다.
FVector Start = Camera->GetComponentLocation();
// 3. 방향은 카메라가 바라보는 방향(ForwardVector)으로 고정!
FVector End = Start + (Camera->GetForwardVector() * Range);
UKismetSystemLibrary::LineTraceSingle(
this,
Start,
End,
UEngineTypes::ConvertToTraceType(ECC_Visibility),
false,
{ this, GetOwner() },
EDrawDebugTrace::ForDuration,
Hit,
true,
FLinearColor::Red,
FLinearColor::Green,
5.f
);
}
}
}
void ASandboxWeaponBase::PlaySound(USoundBase* Sound)
{
UGameplayStatics::PlaySoundAtLocation(this, Sound, GetActorLocation());
}
void ASandboxWeaponBase::Reload()
{
CurrentAmmo = MaxAmmo;
}
void ASandboxWeaponBase::UpdateAmmo()
{
CurrentAmmo -= PelletCount;
}
void ASandboxWeaponBase::SetAiming(bool bNewAiming)
{
bIsAiming = bNewAiming;
// 조준 중일 때의 로직 (예: 탄퍼짐 감소)
if (bIsAiming) {
// CurrentSpread *= 0.5f;
}
}


캐릭터 BP 클래스 만들기
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SandboxWeaponBase.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/Character.h"
#include "SandboxPlayerCharacter.generated.h"
UCLASS()
class ADVANCEDHOMEWORK1_API ASandboxPlayerCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ASandboxPlayerCharacter();
// 무기 발사 함수
UFUNCTION(BlueprintCallable)
void StartFire();
UFUNCTION(BlueprintCallable)
void OnStartAiming();
UFUNCTION(BlueprintCallable)
void OnStopAiming();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// 에디터에서 무기 클래스를 선택하기 위한 변수
UPROPERTY(EditAnywhere, Category = "Combat")
TSubclassOf<ASandboxWeaponBase> WeaponClass;
UPROPERTY()
ASandboxWeaponBase* CurrentWeapon;
// C++에서 카메라와 스프링암을 제어할 수 있도록 선언
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* FollowCamera;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
USpringArmComponent* SpringArm;
float TargetFOV = 90.0f;
float TargetArmLength = 300.0f;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "SandboxPlayerCharacter.h"
// Sets default values
ASandboxPlayerCharacter::ASandboxPlayerCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
WeaponClass = nullptr;
// 1. 스프링암 생성 및 설정
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(RootComponent); // 캡슐 컴포넌트(Root)에 부착
SpringArm->TargetArmLength = 300.0f; // 기본 거리
// 2. 카메라 생성 및 스프링암에 부착
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(SpringArm);
}
void ASandboxPlayerCharacter::StartFire()
{
// 무기가 유효한지 확인 후 발사
if (CurrentWeapon)
{
CurrentWeapon->Fire();
}
}
void ASandboxPlayerCharacter::OnStartAiming()
{
if (CurrentWeapon) CurrentWeapon->SetAiming(true);
// 조준 시작: 타겟 FOV를 60으로 설정 (Tick이 알아서 90 -> 60으로 이동시킴)
TargetFOV = 60.0f;
TargetArmLength = 100.f;
}
void ASandboxPlayerCharacter::OnStopAiming()
{
if (CurrentWeapon) CurrentWeapon->SetAiming(false);
// 조준 해제: 타겟 FOV를 다시 90으로 설정 (Tick이 알아서 60 -> 90으로 복구시킴)
TargetFOV = 90.0f;
TargetArmLength = 300.f;
}
// Called when the game starts or when spawned
void ASandboxPlayerCharacter::BeginPlay()
{
Super::BeginPlay();
// 무기 스폰 및 부착 로직
if (WeaponClass)
{
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this; // 캐릭터를 주인으로 설정
SpawnParams.Instigator = GetInstigator();
// 무기 스폰
CurrentWeapon = GetWorld()->SpawnActor<ASandboxWeaponBase>(WeaponClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
// 캐릭터 메쉬의 소켓에 부착
if (CurrentWeapon)
{
FAttachmentTransformRules AttachmentRules(EAttachmentRule::SnapToTarget, true);
CurrentWeapon->AttachToComponent(GetMesh(), AttachmentRules, FName("weapon_r_muzzle"));
}
}
}
// Called every frame
void ASandboxPlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 현재 FOV에서 타겟 FOV까지 부드럽게 보간 (FInterpTo)
float CurrentFOV = FollowCamera->FieldOfView;
FollowCamera->SetFieldOfView(FMath::FInterpTo(CurrentFOV, TargetFOV, DeltaTime, 10.0f));
// 2. SpringArm 거리 보간
float CurrentArmLength = SpringArm->TargetArmLength;
SpringArm->TargetArmLength = FMath::FInterpTo(CurrentArmLength, TargetArmLength, DeltaTime, 10.0f);
}
// Called to bind functionality to input
void ASandboxPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
EAttachmentRule::SnapToTarget의 의미
이 규칙은 이름 그대로 "대상(소켓)에 딱 붙여버려라(Snap)"는 뜻.
주의할 점:
AttachToComponent는 액터의 Root Component를 소켓에 붙인다. 만약 무기 블루프린트의 Root는 DefaultSceneRoot이고, 실제 총 모델(Static Mesh)이 그 아래에서 멀리 떨어져(Offset) 있다면, 소켓에는 DefaultSceneRoot가 붙고 총 모델은 여전히 엉뚱한 곳에 떠 있게 된다.
1. FOV (Field of View, 시야각)
카메라 렌즈의 화각을 의미.
- 90° (기본값): 일반적인 사람의 시야와 비슷하며, 주변 상황을 넓게 파악하기 좋습니다.
- 60° (조준 시): 화면이 확대(Zoom-in)되는 효과를 줍니다.
- 심리적 효과: 시야가 좁아지면서 주변 정보가 차단되고, 전방의 적에게만 집중하게 만드는 긴장감을 줍니다.
- 물리적 효과: 멀리 있는 적이 더 크게 보여 정밀 조준이 가능해집니다.
2. TargetArmLength (스프링암 길이)
캐릭터와 카메라 사이의 물리적 거리.
- 300 (기본값): 캐릭터의 전신이나 뒷모습이 시원하게 보이는 거리입니다.
- 100~150 (조준 시): 카메라가 캐릭터의 어깨 쪽으로 바짝 다가붙습니다. (흔히 말하는 '숄더 뷰')
조준 기능은 캐릭터에 OnStartAiming과 OnStopAiming 함수를 만들고, 우클릭 인풋을 넣어 만들어준다.
Camera->FieldOfView 값을 60.f로 줄였다가 90.f로 돌린다. TargetArmLength는 300과 100을 왔다갔다하게 한다.



'게임 개발 > 학습 일지' 카테고리의 다른 글
| 언리얼 큐브 그리드로 맵 디자인하기 (0) | 2026.05.12 |
|---|---|
| 언리얼 마스터 머티리얼 만들기 (0) | 2026.05.10 |
| 언리얼 심화 - 템플릿 패턴, 샌드박스 패턴 (0) | 2026.05.01 |
| 언리얼 C++ - 파티클과 사운드로 게임 효과 연출하기 (0) | 2026.04.30 |
| 언리얼 엔진 심화 3주차 - 콜리전, 트레이스 및 데미지 타입 (0) | 2026.04.30 |