[UE5 / C++] ๐Ÿ‘ฟ ์  ๋งŒ๋“ค๊ธฐ (Feat. FSM) (2)

Singery00ยท2024๋…„ 4์›” 29์ผ
0

UE5 C++

๋ชฉ๋ก ๋ณด๊ธฐ
12/20
post-thumbnail

๊ฐœ์š”

๐Ÿ’ก C++๋กœ ์ ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ๋˜ํ•œ ์œ ํ•œ์ƒํƒœ๊ธฐ๊ณ„๋ฅผ ํฌํ•จํ•˜์—ฌ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ๋‹ค.

์ด๋ฒˆ์—๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ ์šฉ๊ณผ ์ถ”๊ฐ€ ์ƒํƒœ์— ๋Œ€ํ•ด์„œ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

State Machine Overview


๋ณธ๋ก 


์• ๋‹ˆ๋ฉ”์ด์…˜ ์ ์šฉ

์–ธ๋ฆฌ์–ผ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ABP_Manny์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ ˆํผ๋Ÿฐ์Šค ๋ณต์‚ฌํ•˜์‹œ๊ณ ,

Enemy์˜ ์ƒ์„ฑ์ž ํ•จ์ˆ˜์—์„œ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
TEXT()์—๋Š” ๊ฐ์ž์˜ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒ ์ฃ ?

AEnemy::AEnemy()
{
 	// ์ƒ๋žต

	// ์ถ”๊ฐ€ ConstructorHelpers::FObjectFinder<UAnimBlueprint>ABP_Manny(TEXT("/Script/Engine.AnimBlueprint'/Game/Characters/Mannequins/Animations/ABP_Manny.ABP_Manny'"));

	if (ABP_Manny.Succeeded())
	{
		GetMesh()->SetAnimClass(ABP_Manny.Object->GeneratedClass);
	}

}

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ›„ ์–ธ๋ฆฌ์–ผ ์—๋””ํ„ฐ๋กœ ๋Œ์•„์™€ ์‹คํ–‰ํ•ด์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

ํ˜น์‹œ ์• ๋‹ˆ๋ฉ”์ด์…”์ด ์ ์šฉ๋˜์ง€ ์•Š์•—๋‹ค๋ฉด BP_Enemy๋ฅผ ์‚ญ์ œํ•˜๊ณ  ๋‹ค์‹œ ๋งŒ๋“ค์–ด ๋ณด์„ธ์š”.


AttackState

Enemy.h ์ˆ˜์ •

Enemy.h๋กœ ์ด๋™ํ•ด์„œ AM_Slash๋ฅผ ์ถ”๊ฐ€ํ•ด์ค์‹œ๋‹ค.

์‹คํ–‰ํ•  ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ชฝํƒ€์ฅฌ๋ฅผ ๋„ฃ์–ด์ค„ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค.

public:
	UPROPERTY(EditAnywhere)
	UAnimMontage* AM_Slash;

AttackState() ์ˆ˜์ •

๋กœ๊ทธ ์ถœ๋ ฅ ๋Œ€์‹  ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋‚˜์˜ค๋„๋ก ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
EnemyFSM.cpp๋กœ ์ด๋™ํ•˜์‹œ๊ณ  ์šฐ์„  ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.

#include "Animation/AnimInstance.h"

๊ทธ๋ฆฌ๊ณ  AttackState()์˜ ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜๋˜ ๋ถ€๋ถ„์— ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถœ๋ ฅํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

me์˜ AnimInstance๋ฅผ ๋ฐ›์•„ ์˜ต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  Montage_Play๋ฅผ ํ†ตํ•ด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.



void UEnemyFSM::AttackState()
{
	FVector Direction = target->GetActorLocation() - me->GetActorLocation();

	if (Direction.Size() <= attackRange)
	{
		if (currentTime > attackDelayTime)
		{
			UAnimInstance* AnimInstance = me->GetMesh()->GetAnimInstance();
			if (AnimInstance != nullptr)
			{
				AnimInstance->Montage_Play(me->AM_Slash);
				currentTime = 0;
			}
		}
	}
	else
	{
		mState = EEnemyState::MOVE;
		currentTime = 0;
	}

}

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ›„ ์–ธ๋ฆฌ์–ผ ์—๋””ํ„ฐ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

AM_Slash ํ• ๋‹น

์ €๋Š” AM_Slash๋ผ๋Š” ๊ณต๊ฒฉ ๋ชจ์…˜์„ ๋ฏธ๋ฆฌ ๋ชฝํƒ€์ฅฌ๋กœ ๋งŒ๋“ค์–ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

BP_Enemy๋ฅผ ์—ด์–ด์„œ AM_Slash๋ฅผ ํ• ๋‹นํ•ด์ฃผ์„ธ์š”.

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ›„ ์‹คํ–‰ํ•ด์ฃผ์‹œ๋ฉด ์•„์ฃผ ์ž˜ ๊ณต๊ฒฉํ•˜๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


ํ”ผํ•ด ์ž…ํžˆ๊ธฐ

Damage, Die State์˜ ๊ตฌํ˜„์— ์•ž์„œ ์šฐ์„  ์ ์—๊ฒŒ ํ”ผํ•ด๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

MyPlayer.cpp

MyPlayer.cpp๋กœ ์ด๋™ํ•ด์„œ Fire()๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฐœ์‚ฌํ•œ ๊ณต์˜ BeginOverlap์ด๋‚˜ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์œผ๋กœ ๋ฐ๋ฏธ์ง€๋ฅผ ์ค„ ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ,
LineTrace๋ฅผ ์ถ”๊ฐ€๋กœ ๋ฐœ์‚ฌํ•˜์—ฌ ๋ฐ๋ฏธ์ง€๋ฅผ ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

LineTrace๋ฅผ ๋ฐœ์‚ฌํ•  ์‹œ์ž‘, ๋์„ ์ง€์ •ํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

void AMyPlayer::Fire()
{
	FVector startPos = camera->GetComponentLocation();
	FVector endPos = camera->GetComponentLocation() + camera->GetForwardVector()*10000;
}

๊ทธ๋ฆฌ๊ณ  HitResult๋ฅผ ๋ฐ›์„ hitInfo์™€ LineTrace์— ํ”Œ๋ ˆ์ด์–ด๋Š” ๋งž์œผ๋ฉด ์•ˆ๋˜๋„ค params์„ ์‚ฌ์šฉํ•ด์„œ ์ œ์™ธํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

void AMyPlayer::Fire()
{
	// ์ƒ๋žต

	// ์ถ”๊ฐ€
	FHitResult hitInfo;

	FCollisionQueryParams params;
	params.AddIgnoredActor(this);
}

๊ทธ๋ฆฌ๊ณ  ์œ„์—์„œ ์„ค์ •ํ•œ ๊ฐ’๋“ค๋กœ LineTrace๋ฅผ ์ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.
์ฑ„๋„๊ฐ’์€ ECC_Visibility๋ฅผ ์ž„์‹œ๋กœ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

void AMyPlayer::Fire()
{

	// ์ƒ๋žต

	// ์ถ”๊ฐ€
	bool bHit = GetWorld()->LineTraceSingleByChannel(hitInfo, startPos, endPos, ECC_Visibility, params);

	if (bHit)
	{

	}

}

๋˜ํ•œ ์ ์ค‘ํ•œ ์ง€์ ์˜ ์œ„์น˜๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ„๋‹จํžˆ ์ดํŽ™ํŠธ๋ฅผ ์†Œํ™˜ํ•˜๋„๋ก ํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

void AMyPlayer::Fire()
{
	// ์ƒ๋žต

	if (bHit)
	{
            	// ์ถ”๊ฐ€
		FTransform bulletTrans;

		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), effectFactory, hitInfo.ImpactPoint);

	}
}

effectFactory ์„ ์–ธ

MyPlayer.h๋กœ ๊ฐ€์„œ effectFactory๋ฅผ ์„ ์–ธํ•ด์ค์‹œ๋‹ค.

public:
	UPROPERTY(EditAnywhere,Category=effectFactory)
	class UParticleSystem* effectFactory;
    

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ•ด์ฃผ์‹œ๊ณ  ์–ธ๋ฆฌ์–ผ ์—๋””ํ„ฐ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

BP_Player์˜ effectFactory

BP_Player๋ฅผ ์—ด์–ด์„œ Effect Factory์— ์ดํŽ™ํŠธ๋ฅผ ๋„ฃ์–ด ์ค๋‹ˆ๋‹ค.
๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” P_Explosion์„ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ•œ ํ›„ ํ”Œ๋ ˆ์ดํ•ด๋ณด๋ฉด LineTrace๊ฐ€ ๋ฐœ์‚ฌ๋˜๊ณ , ์ถฉ๋Œ์ ์— ์ดํŽ™ํŠธ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค.


ApplyDamage

์ด์ œ LineTrace์— ์ ์ค‘ํ•œ ๋Œ€์ƒ์ด ์ ์ด๋ผ๋ฉด ํ”ผํ•ด๋ฅผ ์ค˜์•ผ๊ฒ ์ฃ ?

2๊ฐ€์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. GetDefaultSubobjectByName()
  2. ApplyDamage()
  3. TakeDamage()

GetDefaultSubobjectByName()์œผ๋กœ ํŠน์ • ์ด๋ฆ„์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ๋ชจ๋“  ์ ์ด FSM์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
"FSM์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๊ฐ€?"๋กœ ์ ์ธ์ง€ ์•„๋‹Œ์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ApplyDamage()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ๋ฏธ์ง€๋ฅผ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋Œ€์ƒ์€ TakeDamage()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋– ํ•œ Damage์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ทธ ๊ฐ’์„ ๋ฐ›์•„ ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MyPlayer.cpp

hitInfo.GetActor()๋กœ FSM์ด ์žˆ๋‹ค๋ฉด enemy๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  enemy๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ฐ›์•„์™€์กŒ๋‹ค๋ฉด, ApplyDamage()๋ฅผ ํ†ตํ•ด ์ ์ค‘ ๋Œ€์ƒ์—๊ฒŒ 1์˜ ๋ฐ๋ฏธ์ง€๋ฅผ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

void AMyPlayer::Fire()
{
	//์ƒ๋žต

	if (bHit)
	{
		FTransform bulletTrans;

		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), effectFactory, hitInfo.ImpactPoint);

		// ์ถ”๊ฐ€
		auto enemy = hitInfo.GetActor()->GetDefaultSubobjectByName(TEXT("FSM"));
		
        if (enemy)
		{
			UGameplayStatics::ApplyDamage(hitInfo.GetActor(), 1, NULL, NULL, NULL);
		}
	}
}

BP_Enemy์˜ Collision ์„ค์ •

ECC_Visibility๋กœ LineTrace์˜ ์ฑ„๋„์„ ์„ค์ •ํ•˜์˜€์œผ๋‹ˆ BP_Enemy์˜ Capsule Collision์˜ Visibility๋ฅผ Block์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.


TakeDamage

์ด์ œ ApplyDamage()๋กœ ์ „์†กํ•œ ๋ฐ๋ฏธ์ง€๋ฅผ TakeDamage๋กœ ๋ฐ›์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Enemy Class๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

Hp ๋ฐ TakeDamage() ์„ ์–ธ

์šฐ์„  ์ฒด๋ ฅ๊ณผ TakeDamage๋ฅผ ์„ ์–ธํ•ด์ฃผ์„ธ์š”.

์ฒด๋ ฅ์€ 3์œผ๋กœ ์ฃผ์—ˆ๊ณ ,
TakeDamage๋Š” override๋กœ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค.

public:
	int Hp = 3;

	virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

TakeDamage() ์ •์˜

Super::TakeDamage()๋กœ damage ๊ฐ’์„ ๋ฐ›์•„ ์˜ต๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  Hp๋ฅผ ๊ฐ์†Œํ•˜๊ณ , 0์ดํ•˜๋ผ๋ฉด mState๋ฅผ DIE๋กœ,
์ด์ƒ์ด๋ฉด DAMAGE๋กœ ๋งŒ๋“ค์–ด ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	float damage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
	Hp -= damage;

	if (Hp<=0)
	{
		fsm->SetDieState();
	}
	else {
		fsm->SetDamageState();
	}


	return damage;
}

EnemyFSM์˜ ์ƒํƒœ ๋ณ€ํ™˜ ํ•จ์ˆ˜

SetDieState์™€ SetDamageState๋Š” ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

void UEnemyFSM::SetDamageState()
{
	mState = EEnemyState::DAMAGE;
}

void UEnemyFSM::SetDieState()
{
	mState = EEnemyState::DIE;
}

DamageState

ํ”ผ๊ฒฉ ์ƒํƒœ์ผ ๋•Œ ๋งž๋Š” ๋ชจ์…˜์„ ๋„ฃ์–ด ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

AM_Hit

AM_Hit๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ  Category๋ฅผ Monate๋กœ ๋ฌถ์–ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

public:
	UPROPERTY(EditAnywhere, Category = Montage)
	UAnimMontage* AM_Slash;

	UPROPERTY(EditAnywhere, Category = Montage)
	UAnimMontage* AM_Hit;

DamageState()

DamageState๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.
AttackState์™€ ๋˜‘๊ฐ™์Šต๋‹ˆ๋‹ค.

Play ๋Œ€์ƒ๋งŒ AM_Hit๋กœ ๋‹ค๋ฅผ ๋ฟ์ด์ฃ .

๊ทธ๋ฆฌ๊ณ  ํ”ผ๊ฒฉ์‹œ IDLE์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

void UEnemyFSM::DamageState()
{
	UAnimInstance* AnimInstance = me->GetMesh()->GetAnimInstance();
	if (AnimInstance != nullptr)
	{
		AnimInstance->Montage_Play(me->AM_Hit);
		mState = EEnemyState::IDLE;
		currentTime = 0;
	}
}

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ•˜๊ณ  ์–ธ๋ฆฌ์–ผ ์—๋””ํ„ฐ๋กœ ๊ฐ€์ฃผ์„ธ์š”.

AM_Hit ํ• ๋‹น

๋งŒ๋“ค์–ด๋‘” AM_Hit๋ฅผ ๋„ฃ์–ด ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ•˜๊ณ  ์‹คํ–‰ํ•ด์ฃผ์„ธ์š”.

๊ทธ๋Ÿผ ํ”ผ๊ฒฉ ์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์žฌ์ƒ๋˜๊ณ  ์ฒด๋ ฅ์ด ๊ฐ์†Œ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


DieState

์ฒด๋ ฅ์ด 0์ด ๋˜๋ฉด DieState๋กœ ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
DieState๊ฐ€ ๋˜๋ฉด ๋” ์ด์ƒ ๋‹ค๋ฅธ State๋กœ ๋ณ€๊ฒฝํ•ด์ค„ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋ž˜๊ทธ๋Œ ์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ , Capsule Collision์„ NoCollision์œผ๋กœ ์„ค์ •ํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

#include "Components/CapsuleComponent.h"

void UEnemyFSM::DieState()
{
	mState = EEnemyState::DIE;
	me->GetMesh()->SetSimulatePhysics (true);
	me->GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	me->GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

โ—์ปดํŒŒ์ผ ์ €์žฅโ—ํ•˜๊ณ  ์‹คํ–‰ํ•ด์ฃผ์‹œ๊ณ  ์ ์„ 3๋ฒˆ ๊ณต๊ฒฉํ•˜๋ฉด ์“ฐ๋Ÿฌ์ง€๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋งˆ๋ฌด๋ฆฌ

์—ฌ๊ธฐ๊นŒ์ง€ ์ ์˜ ์ƒํƒœ๋ณ„ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๋ฐ ๊ณต๊ฒฉ ์ƒํ˜ธ์ž‘์šฉ๊นŒ์ง€ ์™„๋ฃŒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์—๋Š” Spawn Manager๋ฅผ ๋งŒ๋“ค์–ด ์ ์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋„๋ก ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์•„ ๊ทธ๋ฆฌ๊ณ  ์ ์ด ๊ณต๊ฒฉํ•  ๋•Œ ํ”Œ๋ ˆ์ด๊ฐ€ ํ”ผ๊ฒฉ๋˜๋Š” ๊ฒƒ๋„ ์ถ”ํ›„ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

profile
๊ฒŒ์ž„ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด๋ณด์ž

0๊ฐœ์˜ ๋Œ“๊ธ€