오늘의 할일
1. 코드카타 정리
명함 지갑을 만드는 회사에서 지갑의 크기를 정하려고 합니다. 다양한 모양과 크기의 명함들을 모두 수납할 수 있으면서, 작아서 들고 다니기 편한 지갑을 만들어야 합니다. 이러한 요건을 만족하는 지갑을 만들기 위해 디자인팀은 모든 명함의 가로 길이와 세로 길이를 조사했습니다.
아래 표는 4가지 명함의 가로 길이와 세로 길이를 나타냅니다.
| 명함 번호 | 가로 길이 | 세로 길이 |
|---|---|---|
| 1 | 60 | 50 |
| 2 | 30 | 70 |
| 3 | 60 | 30 |
| 4 | 80 | 40 |
가장 긴 가로 길이와 세로 길이가 각각 80, 70이기 때문에 80(가로) x 70(세로) 크기의 지갑을 만들면 모든 명함들을 수납할 수 있습니다. 하지만 2번 명함을 가로로 눕혀 수납한다면 80(가로) x 50(세로) 크기의 지갑으로 모든 명함들을 수납할 수 있습니다. 이때의 지갑 크기는 4000(=80 x 50)입니다.
모든 명함의 가로 길이와 세로 길이를 나타내는 2차원 배열 sizes가 매개변수로 주어집니다. 모든 명함을 수납할 수 있는 가장 작은 지갑을 만들 때, 지갑의 크기를 return
#include <string>
#include <vector>
#include <algorithm> // max, min 함수 사용
using namespace std;
int solution(vector<vector<int>> sizes) {
int max_width = 0; // 가장 긴 가로 길이
int max_height = 0; // 가장 긴 세로 길이
// 모든 명함을 하나씩 확인
for (int i = 0; i < sizes.size(); i++) {
// 명함의 가로세로 중 더 긴 쪽을 가로, 더 짧은 쪽을 세로로 지정
int longer = max(sizes[i][0], sizes[i][1]);
int shorter = min(sizes[i][0], sizes[i][1]);
// 지금 확인하는 명함의 가로가 저장했던 최대 가로보다 길다면 갱신
if (longer > max_width) {
max_width = longer;
}
// 지금 확인하는 명함의 세로가 저장했던 최대 세로보다 길다면 갱신
if (shorter > max_height) {
max_height = shorter;
}
}
// 최종적으로 가장 큰 가로와 세로를 곱해서 지갑의 넓이 구하기
return max_width * max_height;
}
AI가 작성한 코드
#include <vector>
#include <algorithm>
using namespace std;
int solution(vector<vector<int>> sizes) {
int max_width = 0;
int max_height = 0;
// 1. 인덱스(i) 대신 범위 기반 for문과 참조(&) 사용
for (const auto& card : sizes) {
// 2. if문 없이 max 함수로 즉시 갱신
max_width = max(max_width, max(card[0], card[1]));
max_height = max(max_height, min(card[0], card[1]));
}
return max_width * max_height;
}
배열 복사 방지, 안전장치 (const auto&)
sizes.size()와 sizes[i]를 쓰는 대신 범위 기반 for문(for (auto card : sizes))을 사용해 인덱스 계산 실수 차단. 여기에 &(참조)를 붙여 쓸데없는 배열 복사를 막았고, const를 붙여 "이 데이터는 읽기 전용이니 절대 훼손하지 말라"는 안전장치까지.
변수 생략으로 인한 응집력
longer나 shorter 같은 중간 변수를 거치지 않고 식을 하나로 합쳤습니다. "가장 큰 값들 중의 최댓값(가로)"과 "가장 작은 값들 중의 최댓값(세로)"처럼 두 줄로 표현
Animation 패널 (Window - Animations)
애니메이션을 다루는 창
Keyframe(키 프레임)
특정 시간대에 UI 속성(크기, 위치, 투명도, 색상 등)을 어떻게 바꿀지를 기록하는 지점
여러 개의 키 프레임을 연결하여 하나의 애니메이션 타임라인을 완성
Text 위젯에 애니메이션 적용하기
WBP_MainMenu - TextBlock 하나를 추가(GameOverText), TextBlock을 추가(TotalScoreText)
GameOverText 와 TotalScoreText -> Hidden(Visibility) 상태
<애니메이션 트랙 생성>
Animation 패널 -> + Animation 버튼을 클릭 “Anim_TimeOver”
Track을 추가(GameOverText)
Keyframe (키 프레임) 설정 -> Render Opacity (투명도)를 트랙에 추가
처음 (0초 지점)
Render Opacity = 0 로 설정, Key 찍기
1초 지점
Render Opacity = 0.2로 변경, 다시 Key 찍기
2초 지점
Render Opacity = 1 (완전히 보이도록) 설정, Key 찍기
3초 지점
다시 Render Opacity = 0.2로 변경, 다시 Key 찍기
4초 지점
Render Opacity = 1 설정, Key 찍기

<애니메이션을 Blueprint 재생 함수로 묶어두기>
이벤트 그래프에서 PlayGameOverAnim Function 생성
시작시 GameOverText = visible로 처리, Play Animation 연결. “PlayTimeOverAnim” 호출하면 “Anim_TimeOver” 애니메이션이 재생되도
록 구성
애니메이션 재생이 끝나면 점수가 뜰 수 있도록 TotalScoreText = visible 처리를 하도록 연결

최종 점수 표시 애니메이션 호출하기
#include "SpartaPlayerController.h"
#include "SpartaGameState.h"
#include "SpartaGameInstance.h"
#include "EnhancedInputSubsystems.h"
#include "Blueprint/UserWidget.h"
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"
void ASpartaPlayerController::BeginPlay()
{
Super::BeginPlay();
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (InputMappingContext) {
Subsystem->AddMappingContext(InputMappingContext, 0);
}
}
}
FString CurrentMapName = GetWorld()->GetMapName();
if (CurrentMapName.Contains("MenuLevel")) {
ShowMainMenu(false);
}
}
UUserWidget* ASpartaPlayerController::GetHUDWidget() const
{
return HUDWidgetInstance;
}
void ASpartaPlayerController::ShowMainMenu(bool bIsRestart)
{
if (HUDWidgetInstance)
{
HUDWidgetInstance->RemoveFromParent();
HUDWidgetInstance = nullptr;
}
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->RemoveFromParent();
MainMenuWidgetInstance = nullptr;
}
if (MainMenuWidgetClass)
{
MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->AddToViewport();
bShowMouseCursor = true;
SetInputMode(FInputModeUIOnly());
}
if (UTextBlock* ButtonText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
{
if (bIsRestart)
{
ButtonText->SetText(FText::FromString(TEXT("Restart")));
}
else
{
ButtonText->SetText(FText::FromString(TEXT("Start")));
}
}
// Total Score 정상 적동하게
if (bIsRestart)
{
UFunction* PlayAnimFunc = MainMenuWidgetInstance->FindFunction(FName("PlayGameOverAnim"));
if (PlayAnimFunc) {
MainMenuWidgetInstance->ProcessEvent(PlayAnimFunc, nullptr);
}
if (UTextBlock* TotalScoreText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName("TotalScoreText")))
{
if (USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(UGameplayStatics::GetGameInstance(this)))
{
TotalScoreText->SetText(FText::FromString(
FString::Printf(TEXT("Total Score: %d"), SpartaGameInstance->TotalScore)
));
}
}
}
}
}
void ASpartaPlayerController::ShowGameHUD()
{
if (HUDWidgetInstance)
{
HUDWidgetInstance->RemoveFromParent();
HUDWidgetInstance = nullptr;
}
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->RemoveFromParent();
MainMenuWidgetInstance = nullptr;
}
if (HUDWidgetClass)
{
HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWidgetInstance)
{
HUDWidgetInstance->AddToViewport();
bShowMouseCursor = false;
SetInputMode(FInputModeGameOnly());
}
ASpartaGameState* SpartaGameState = GetWorld() ? GetWorld()->GetGameState<ASpartaGameState>() : nullptr;
if (SpartaGameState)
{
SpartaGameState->UpdateHUD();
}
}
}
void ASpartaPlayerController::StartGame()
{
if (USpartaGameInstance* SpartaGameInstance = Cast <USpartaGameInstance>(UGameplayStatics::GetGameInstance(this)))
{
SpartaGameInstance->CurrentLevelIndex = 0;
SpartaGameInstance->TotalScore = 0;
}
UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
SetPause(false); //추가해주기
}
WidgetComponent : UMG로 만든 위젯(텍스트, 이미지, 버튼 등)을 3D 월드에 붙일 수 있게 해주는 컴포넌트 (NPC 머리 위 체력바)
Widget Blueprint 준비

<Character 클래스에 WidgetComponent 추가>
<캐릭터 사망하였을 시 처리 로직 추가>
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SpartaCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
class UWidgetComponent;
struct FInputActionValue;
UCLASS()
class TESTPROJECT2_API ASpartaCharacter : public ACharacter
{
GENERATED_BODY()
public:
ASpartaCharacter();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera")
USpringArmComponent* SpringArmComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* CameraComp;
//OverheadWidget
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI")
UWidgetComponent* OverheadWidget;
UFUNCTION(BlueprintPure, Category = "Health")
int32 GetHealth() const;
UFUNCTION(BlueprintCallable, Category = "Health")
void AddHealth(float Amount);
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float MaxHealth;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Health")
float Health;
//BeginPlay 추가해주기
virtual void BeginPlay() override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual float TakeDamage(float DamageAmount,
struct FDamageEvent const& DamageEvent,
AController* EventInstigator, //데미지를 누가 입혔는지
AActor* DamageCauser) override; //데미지를 입힌 오브젝트
UFUNCTION()
void Move(const FInputActionValue& value);
UFUNCTION()
void StartJump(const FInputActionValue& value);
UFUNCTION()
void StopJump(const FInputActionValue& value);
UFUNCTION()
void Look(const FInputActionValue& value);
UFUNCTION()
void StartSprint(const FInputActionValue& value);
UFUNCTION()
void StopSprint(const FInputActionValue& value);
UFUNCTION(BlueprintCallable, Category = "Health")
virtual void OnDeath();
// 추가
void UpdateOverheadHP();
};
#include "SpartaCharacter.h"
#include "SpartaPlayerController.h"
#include "SpartaGameState.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/WidgetComponent.h"
#include "Components/TextBlock.h"
ASpartaCharacter::ASpartaCharacter()
{
PrimaryActorTick.bCanEverTick = false;
SpringArmComp 어쩌고...
OverheadWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadWidget"));
OverheadWidget->SetupAttachment(GetMesh());
OverheadWidget->SetWidgetSpace(EWidgetSpace::Screen); //스크린모드냐 월드 모드냐
NormalSpeed = 600.0f;
SprintSpeedMultiplier = 1.7f;
SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
MaxHealth = 100.0f;
Health = MaxHealth;
}
//UpdateOverheadHP 추가
void ASpartaCharacter::BeginPlay()
{
Super::BeginPlay();
UpdateOverheadHP();
}
int32 ASpartaCharacter::GetHealth() const {
return Health;
}
// 체력 회복 함수 수정
void ASpartaCharacter::AddHealth(float Amount)
{
Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);
UpdateOverheadHP();
}
// 데미지 처리 함수 수정
float ASpartaCharacter::TakeDamage(float DamageAmount,
FDamageEvent const& DamageEvent,
AController* EventInstigator,
AActor* DamageCauser)
{
float ActualDamage = Super::TakeDamage(DamageAmount,
DamageEvent, EventInstigator, DamageCauser);
Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
UpdateOverheadHP(); //추가해주기
if (Health <= 0.0f)
{
OnDeath();
}
return ActualDamage;
}
// 사망 처리 함수
void ASpartaCharacter::OnDeath()
{ //이것도 추가해주기
ASpartaGameState* SpartaGameState = GetWorld() ? GetWorld()->GetGameState<ASpartaGameState>() : nullptr;
if (SpartaGameState) {
SpartaGameState->OnGameOver();
}
}
//함수 작성
void ASpartaCharacter::UpdateOverheadHP()
{
if (!OverheadWidget) return;
UUserWidget* OverheadWidgetInstance = OverheadWidget->GetUserWidgetObject();
if (!OverheadWidgetInstance) return;
if (UTextBlock* HPText = Cast<UTextBlock>(OverheadWidgetInstance->GetWidgetFromName(TEXT("OverHeadHP"))))
{
HPText->SetText(FText::FromString(FString::Printf(TEXT("%.0f / % .0f"), Health, MaxHealth)));
}
}
Character 클래스에서 WidgetComponent 위치 설정
BP_SpartaCharacter -> User Interface -> Widget Class를 WBP_HP로 설정
Space는 World로 우선 셋팅을 한 후 위젯의 위치를 조정 -> Space를 다시 Screen 으로 설정

<캐릭터 사망하였을 시 처리 로직 추가>
#include "SpartaGameState.h"
#include "SpartaGameInstance.h"
#include "SpartaPlayerController.h"
#include "SpawnVolume.h"
#include "CoinItem.h"
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"
#include "Blueprint/UserWidget.h"
ASpartaGameState::ASpartaGameState()
{
Score = 0;
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
LevelDuration = 10.0f;
CurrentLevelIndex = 0;
MaxLevels = 3;
}
void ASpartaGameState::BeginPlay()
{
Super::BeginPlay();
StartLevel();
GetWorldTimerManager().SetTimer(
HUDUpdateTimerHandle,
this,
&ASpartaGameState::UpdateHUD,
0.1f,
true
);
}
void ASpartaGameState::AddScore(int32 Amount)
{
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
SpartaGameInstance->AddToScore(Amount);
}
}
}
void ASpartaGameState::StartLevel()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
{
SpartaPlayerController->ShowGameHUD();
}
}
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
CurrentLevelIndex = SpartaGameInstance->CurrentLevelIndex;
}
}
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
TArray<AActor*> FoundVolumes;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
const int32 ItemToSpawn = 40;
if (FoundVolumes.Num() > 0)
{
ASpawnVolume* SpawnVolume = Cast<ASpawnVolume>(FoundVolumes[0]);
if (SpawnVolume)
{
for (int32 i = 0; i < ItemToSpawn; i++) {
AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
{
SpawnedCoinCount++;
}
}
}
}
GetWorldTimerManager().SetTimer(
LevelTimerHandle,
this,
&ASpartaGameState::OnLevelTimeUp,
LevelDuration,
false
);
}
void ASpartaGameState::EndLevel()
{
UE_LOG(LogTemp, Warning, TEXT("[Debug] CurrentLevelIndex: %d, MapArraySize: %d"), CurrentLevelIndex, LevelMapNames.Num());
GetWorldTimerManager().ClearTimer(LevelTimerHandle);
CurrentLevelIndex++;
if (UGameInstance* GameInstance = GetGameInstance()) {
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance) {
AddScore(Score);
SpartaGameInstance->CurrentLevelIndex = CurrentLevelIndex;
}
}
if (CurrentLevelIndex >= MaxLevels)
{
OnGameOver();
return;
}
if (LevelMapNames.IsValidIndex(CurrentLevelIndex))
{
UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
}
else
{
OnGameOver();
}
}
//수정
void ASpartaGameState::OnGameOver()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
{
SpartaPlayerController->SetPause(true);
SpartaPlayerController->ShowMainMenu(true);
}
}
}
void ASpartaGameState::UpdateHUD()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (ASpartaPlayerController* SpartaPlayerController = Cast <ASpartaPlayerController>(PlayerController))
{
if (UUserWidget* HUDWidget = SpartaPlayerController->GetHUDWidget())
{
if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
{
float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time: %.1f"), RemainingTime)));
}
if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
{
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score: %d"), SpartaGameInstance->TotalScore)));
}
}
}
if (UTextBlock* LevelIndexText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
{
LevelIndexText->SetText(FText::FromString(FString::Printf(TEXT("Level: %d"), CurrentLevelIndex + 1)));
}
}
}
}
}
(SpartaGameController.cpp도 수정해주기(SetPause))
Cascade vs. Niagara
Cascade: 초급자가 배우기에 상대적으로 간단하고 빠르게 결과를 볼 수 있음
Niagara: 모듈 단위로 다양한 파티클 동작을 정교하게 제어 가능
<아이템 획득에 파티클 적용하기>
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemInterface.h"
#include "BaseItem.generated.h"
class USphereComponent;
UCLASS()
class TESTPROJECT2_API ABaseItem : public AActor, public IItemInterface
{
GENERATED_BODY()
public:
ABaseItem();
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FName ItemType;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
USceneComponent* Scene;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
USphereComponent* Collision;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
UStaticMeshComponent* StaticMesh;
//추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Effects")
UParticleSystem * PickupParticle;
};
#include "BaseItem.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"
void ABaseItem::OnItemOverlap(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult) {
if (OtherActor && OtherActor->ActorHasTag("Player")) {
ActivateItem(OtherActor);
}
}
void ABaseItem::ActivateItem(AActor* Activator) {
UParticleSystemComponent* Particle = nullptr;
if (PickupParticle)
{
Particle = UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
PickupParticle,
GetActorLocation(),
GetActorRotation(),
true
);
}
if (Particle)
{
FTimerHandle DestroyParticleTimerHandle;
TWeakObjectPtr<UParticleSystemComponent> WeakParticle = Particle;
GetWorld()->GetTimerManager().SetTimer(
DestroyParticleTimerHandle,
[WeakParticle]()
{
if (WeakParticle.IsValid())
{
WeakParticle->DestroyComponent();
}
},
2.0f,
false
);
}
}
<지뢰 아이템에 파티클 추가하기>
아이템 발동 후 실제 폭발 이펙트를 보여줄 수 있도록 수정
발동이 여러번 되는 것을 막기 위해, bHasExplode 설정
SpawnEmitterAtLocation() 사용해서 폭발 이펙트를 하나 더 배정
빌드하면 디테일 패널에 ExplosionEffect -> P_Explosion 할당
<지뢰 아이템에 사운드 효과음 추가>
#pragma once
#include "CoreMinimal.h"
#include "BaseItem.h"
#include "MineItem.generated.h"
UCLASS()
class TESTPROJECT2_API AMineItem : public ABaseItem
{
GENERATED_BODY()
public:
AMineItem();
USphereComponent* ExplosionCollision;
//추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Effects")
UParticleSystem* ExplosionParticle;
//추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Effects")
USoundBase* ExplosionSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 ExplosionDelay;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 ExplosionRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 ExplosionDamage;
//추가
bool bHasExploded;
FTimerHandle ExplosionTimerHandle;
virtual void ActivateItem(AActor* Activator) override;
void Explode();
};
#include "MineItem.h"
#include "Components/SphereComponent.h"
#include <Kismet/GameplayStatics.h>
#include "Particles/ParticleSystemComponent.h"
AMineItem::AMineItem() {
ExplosionDelay = 2.0f;
ExplosionRadius = 300.0f;
ExplosionDamage = 30.0f;
ItemType = "Mine";
bHasExploded = false; //추가
ExplosionCollision = CreateDefaultSubobject<USphereComponent>(TEXT("ExplosionCollision"));
ExplosionCollision->InitSphereRadius(ExplosionRadius);
ExplosionCollision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
ExplosionCollision->SetupAttachment(Scene);
}
void AMineItem::ActivateItem(AActor* Activator) {
//추가
if (bHasExploded) return;
Super::ActivateItem(Activator);
GetWorld()->GetTimerManager().SetTimer(
ExplosionTimerHandle,
this,
&AMineItem::Explode,
ExplosionDelay,
false
);
bHasExploded = true; //추가
}
void AMineItem::Explode() {
//추가
UParticleSystemComponent* Particle = nullptr;
if (ExplosionParticle)
{
Particle = UGameplayStatics::SpawnEmitterAtLocation( //독립적으로 스폰
GetWorld(),
ExplosionParticle,
GetActorLocation(),
GetActorRotation(),
false
);
}
if (ExplosionSound)
{
UGameplayStatics::PlaySoundAtLocation(
GetWorld(),
ExplosionSound,
GetActorLocation()
);
}
TArray<AActor*> OverlappingActors;
ExplosionCollision->GetOverlappingActors(OverlappingActors);
for (AActor* Actor : OverlappingActors) {
if (Actor && Actor->ActorHasTag("Player"))
{
UGameplayStatics::ApplyDamage(
Actor,
ExplosionDamage,
nullptr,
this,
UDamageType::StaticClass()
);
}
}
DestroyItem();
//추가
if (Particle)
{
FTimerHandle DestroyParticleTimerHandle;
GetWorld()->GetTimerManager().SetTimer(
DestroyParticleTimerHandle,
[Particle]()
{
Particle->DestroyComponent();
},
2.0f, //2초 뒤에 사라짐
false
);
}
}
사운드 웨이브 : 한 개의 오디오 파일, 단일 오디오 파일을 그대로 재생하는 경우.
사운드 큐 : 오디오 편집 그래프 시스템, 여러 사운드 웨이브를 조합하거나, 랜덤/페이드/모듈레이션 등을 적용해야 할 때.
아이템 획득에 사운드 적용하기
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemInterface.h"
#include "BaseItem.generated.h"
class USphereComponent;
UCLASS()
class TESTPROJECT2_API ABaseItem : public AActor, public IItemInterface
{
GENERATED_BODY()
public:
ABaseItem();
protected:
virtual void OnItemOverlap(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult) override;
virtual void OnItemEndOverlap(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex) override;
virtual void ActivateItem(AActor* Activator) override;
virtual FName GetItemType() const override;
virtual void DestroyItem();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FName ItemType;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
USceneComponent* Scene;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
USphereComponent* Collision;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
UStaticMeshComponent* StaticMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Effects")
UParticleSystem * PickupParticle;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Effects")
USoundBase* PickupSound;
};
#include "BaseItem.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"
void ABaseItem::OnItemOverlap(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult) {
if (OtherActor && OtherActor->ActorHasTag("Player")) {
ActivateItem(OtherActor);
}
}
void ABaseItem::OnItemEndOverlap(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex) {
}
void ABaseItem::ActivateItem(AActor* Activator) {
UParticleSystemComponent* Particle = nullptr;
if (PickupParticle)
{
Particle = UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
PickupParticle,
GetActorLocation(),
GetActorRotation(),
true
);
}
if (PickupSound) {
UGameplayStatics::PlaySoundAtLocation(
GetWorld(),
PickupSound,
GetActorLocation()
);
}
if (Particle)
{
FTimerHandle DestroyParticleTimerHandle;
TWeakObjectPtr<UParticleSystemComponent> WeakParticle = Particle;
GetWorld()->GetTimerManager().SetTimer(
DestroyParticleTimerHandle,
[WeakParticle]()
{
if (WeakParticle.IsValid())
{
WeakParticle->DestroyComponent();
}
},
2.0f,
false
);
}
}
사운드를 재생할 아이템 Blueprint - Details 창 -> Effects 카테고리에서 사운드 이펙트를 설정