๐Ÿ“˜TIL - ๋ฐฐ๊ฒฝ NPC AIยท๋Œ€ํ™” & ์ผ์‹œ์ •์ง€/์กฐ์ž‘ํ‚ค UI

HappyCircleยท2026๋…„ 7์›” 7์ผ

Unreal ๊ฐœ๋ฐœ

๋ชฉ๋ก ๋ณด๊ธฐ
160/163

๋ชฉ์ฐจ

  1. ๋งˆ์„ ๋ฐฐ๊ฒฝ NPC ์ˆœ์ฐฐ โ€” StateTree ๊ธฐ๋ฐ˜ ์‹ ๊ทœ AI ๊ตฌํ˜„
  2. NPC ๋Œ€ํ™” ํ™•์žฅ โ€” ๋ชฝํƒ€์ฃผ ์žฌ์ƒ, ์•„์ดํ…œ ๋ณด์ƒ, ๋Œ€ํ™” ์ค‘ ์ •์ง€
  3. ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด โ€” ์ˆœ์ˆ˜ ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ESC์™€ ๋ฐฐํƒ€ ํŒจ๋„ ์‹œ์Šคํ…œ ํŽธ์ž…
  4. ํŒจํ‚ค์ง€์—์„œ ์กฐ์šฉํžˆ ์‚ฌ๋ผ์ง„ ํŒจ๋„ โ€” TSoftClassPtr์™€ ์ฟ ํ‚น ๊ฒฝ๋กœ
  5. ๋ฉ”๋‰ด ๋ฒ„ํŠผ ๋ฐฐ์„  โ€” ๊ธฐ์กด ์ง„์ž…์  ์žฌ์‚ฌ์šฉ์œผ๋กœ ์„ค์ •ยท์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด ์—ฐ๊ฒฐ
  6. ์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด ์œ„์ ฏ โ€” ์‹ค์ œ IMC ๋ฐ”์ธ๋”ฉ์„ ๊ทผ๊ฑฐ๋กœ ํ•œ ํ‚ค๋งต ๋ทฐ
  7. Live Coding์˜ ๊ฒฝ๊ณ„ โ€” ์ƒˆ UPROPERTYยทUFUNCTION์€ ์ •์‹ ๋นŒ๋“œ๊ฐ€ ํ•„์š”

1. ๋งˆ์„ ๋ฐฐ๊ฒฝ NPC ์ˆœ์ฐฐ โ€” StateTree ๊ธฐ๋ฐ˜ ์‹ ๊ทœ AI ๊ตฌํ˜„

๋ชฉํ‘œ

๋งˆ์„์— ๋ฐฐ์น˜๋œ ๋ฐฐ๊ฒฝ NPC๊ฐ€ ์Šคํฐ ์ง€์  ์ฃผ๋ณ€์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋Œ์•„๋‹ค๋‹ˆ๊ฒŒ ํ•œ๋‹ค. ์ฐธ๊ณ  ์ž๋ฃŒ(D3kryption์˜ UE5 State Tree ์ˆœ์ฐฐ ๊ฐ€์ด๋“œ)์˜ "Move To Random Location ๋ฐ˜๋ณต" ๊ฐœ๋…์„ ์ด ํ”„๋กœ์ ํŠธ์˜ StateTree ์•„ํ‚คํ…์ฒ˜์— ๋งž๊ฒŒ ์ ์šฉํ•œ๋‹ค.

์›์ธ โ€” NPC ๋ฒ ์ด์Šค ํด๋ž˜์Šค์— AI ์ž์ฒด๊ฐ€ ์—†์—ˆ์Œ

ARetrieveNPCCharacter(์ƒ์  NPC ๋“ฑ ๋น„์ „ํˆฌ NPC ๊ณต์šฉ ๋ฒ ์ด์Šค)๋Š” AIControllerClass = nullptr๋กœ ์„ค์ •๋ผ ์žˆ์–ด ์ปจํŠธ๋กค๋Ÿฌ ์ž์ฒด๊ฐ€ ์—†๋Š” ์™„์ „ ์ •์  ์ƒํƒœ์˜€๋‹ค. Enemy/Lumen ์ชฝ์—๋Š” ์ด๋ฏธ StateTree AI(UStateTreeAIComponent + ์ปค์Šคํ…€ Task/Evaluator)๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ ์ „ํˆฌ ํŒ์ •(๊ณต๊ฒฉ ์‚ฌ๊ฑฐ๋ฆฌ, ์ธ์นด์šดํ„ฐ ์Šฌ๋กฏ, ์กฐ์šฐ ๋กœ์ง)์— ๊ฐ•ํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋ผ ์žˆ์–ด ๊ทธ๋Œ€๋กœ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์—ˆ๋‹ค. ์ฒ˜์Œ์—” ์ด ์ž‘์—…์„ Enemy ์ชฝ ํด๋”์— ์ž˜๋ชป ์‹œ์ž‘ํ–ˆ๋‹ค๊ฐ€("๋ชฌ์Šคํ„ฐ ์ˆœ์ฐฐ"๋กœ ์˜คํ•ด), ์‚ฌ์šฉ์ž๊ฐ€ "NPC์™€ Enemy๋Š” ๋ณ„๊ฐœ"๋ผ๊ณ  ์ •์ •ํ•ด ๋ฐฉํ–ฅ์„ ๋ฐ”๋กœ์žก์•˜๋‹ค โ€” ์—ญํ• ์ด ๋‹ค๋ฅธ ์•กํ„ฐ๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค๋ฅธ ํด๋ž˜์Šค ๊ณ„์ธต์—์„œ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ๋‹ค์‹œ ํ™•์ธํ•œ ์ˆœ๊ฐ„์ด์—ˆ๋‹ค.

ํ•ด๊ฒฐ โ€” Enemy์™€ ์™„์ „ํžˆ ๋ถ„๋ฆฌ๋œ ์ตœ์†Œ StateTree AI

// ARetrieveVillagerCharacter : ARetrieveNPCCharacter
ARetrieveVillagerCharacter::ARetrieveVillagerCharacter(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    AIControllerClass = ANPCPatrolAIController::StaticClass();
    if (UCharacterMovementComponent* MovementComp = GetCharacterMovement())
    {
        MovementComp->bOrientRotationToMovement = true;
        MovementComp->RotationRate = FRotator(0.f, 180.f, 0.f);
        MovementComp->MaxWalkSpeed = 250.f;          // ์—ฌ์œ ๋กญ๊ฒŒ ๊ฑท๋Š” ์†๋„
        MovementComp->MaxAcceleration = 400.f;        // ์™„๋งŒํ•œ ๊ฐ€๊ฐ์†
        MovementComp->BrakingDecelerationWalking = 400.f;
    }
}

ANPCPatrolAIController๋Š” PerceptionยทTeam ์—†์ด UStateTreeAIComponent ํ•˜๋‚˜๋งŒ ๊ตฌ๋™ํ•˜๋Š” ์ตœ์†Œ ์ปจํŠธ๋กค๋Ÿฌ๋‹ค. FStateTreeTask_VillagerPatrol์€ Evaluator ์—†์ด Task ํ•˜๋‚˜๋งŒ์œผ๋กœ ๋ฌดํ•œ ๋ฃจํ”„๋ฅผ ๋„๋Š” ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๊ตฌ์กฐ๋กœ ์งฐ๋‹ค โ€” EnterState์—์„œ ์Šคํฐ ์œ„์น˜๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์บก์ฒ˜ํ•ด ์บ์‹ฑํ•˜๊ณ , PatrolOrigin ๋ฐ˜๊ฒฝ ๋‚ด GetRandomReachablePointInRadius๋กœ ์ง€์ ์„ ๋ฝ‘์•„ ์ด๋™, ๋„์ฐฉ(๋˜๋Š” MaxMoveTime ์ดˆ๊ณผ๋กœ ๋ง‰ํž˜ ํŒ์ •)ํ•˜๋ฉด ๋žœ๋ค ์‹œ๊ฐ„๋งŒํผ ๋Œ€๊ธฐ ํ›„ ์žฌ์„ ์ •ํ•œ๋‹ค. State๊ฐ€ ํ•˜๋‚˜๋ฟ์ด๋ผ ์ „์ด(Transition) ์ •์˜๋„ ํ•„์š” ์—†๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€ ์˜ค๋Š” ๋™์•ˆ ์‹ค์ œ๋กœ ๊ฒช์€ ๋ฒ„๊ทธ ์„ธ ๊ฐ€์ง€๊ฐ€ ๋ฐฐ์šธ ์ ์ด ๋งŽ์•˜๋‹ค.

1) ์˜†์œผ๋กœ ๋ฏธ๋„๋Ÿฌ์ง€๋“ฏ ๊ฑท๋Š” ๋ฒ„๊ทธ. CharacterMovement->bOrientRotationToMovement = true(์บ๋ฆญํ„ฐ ์Šค์Šค๋กœ ์ด๋™ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „)๋กœ ์„ค์ •ํ•ด๋†“๊ณ  MoveToLocation์˜ bCanStrafe๋Š” false๋กœ ๋„˜๊ฒผ๋‹ค. bCanStrafe=false๋Š” "AIController๊ฐ€ ํšŒ์ „์„ ๋Œ€์‹  ํ†ต์ œํ•˜๊ฒ ๋‹ค"๋Š” ์‹ ํ˜ธ๋ผ, ๋‘ ํšŒ์ „ ์ฃผ์ฒด๊ฐ€ ์ถฉ๋Œํ•ด ๋ฏธ๋„๋Ÿฌ์ง€๋Š” ์ด๋™์ด ๋‚˜์™”๋‹ค. bCanStrafe=true๋กœ ๋งž์ถฐ ํšŒ์ „ ๊ถŒํ•œ์„ CharacterMovement ํ•˜๋‚˜๋กœ ํ†ต์ผํ•˜๋‹ˆ ํ•ด๊ฒฐ๋๋‹ค.

2) ๊ทธ๋ž˜๋„ ๋‚จ์•„์žˆ๋˜ ์˜†/๋’ค ๊ฑธ์Œ ๋ฒ„๊ทธ โ€” ๋ฉ”์‰ฌ ํšŒ์ „ ๋ณด์ • ๋ˆ„๋ฝ. bCanStrafe ์ˆ˜์ • ํ›„์—๋„ ์ฆ์ƒ์ด ๋‚จ์•„ ์žˆ์—ˆ๋‹ค. ์ •์ง€ ์ƒํƒœ์—์„  ์ ˆ๋Œ€ ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ๋ฒ„๊ทธ๋ผ, Monolith๋กœ ๊ธฐ์กด์— ์ž˜ ๋™์ž‘ํ•˜๋˜ BP_ShopNPC_General์˜ Mesh ์ปดํฌ๋„ŒํŠธ RelativeRotation์„ ๋น„๊ตํ•ด๋ณด๋‹ˆ Yaw โ‰ˆ 90ยฐ๋กœ ๋ณด์ •๋ผ ์žˆ์—ˆ๋‹ค. ์ƒˆ๋กœ ๋งŒ๋“  Villager BP๋Š” ์ด ๋ณด์ •๊ฐ’์ด Yaw=0์œผ๋กœ ๋น„์–ด ์žˆ์—ˆ๋˜ ๊ฒƒ โ€” ์ด ์บ๋ฆญํ„ฐ ๋ฉ”์‰ฌ(์Šค์ผˆ๋ ˆํ†ค)๊ฐ€ ์›๋ž˜ ์š”๊ตฌํ•˜๋Š” ๋ณด์ •๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์•ˆ ๊ฐ€์ ธ์˜จ ๊ฒŒ ์ง„์งœ ์›์ธ์ด์—ˆ๋‹ค. ๊ฐ™์€ ๊ฐ’์œผ๋กœ ๋งž์ถ”์ž ๋ฐ”๋กœ ํ•ด๊ฒฐ.

# Monolith Python: Rotator ์ƒ์„ฑ์ž ์ธ์ž ์ˆœ์„œ ํ•จ์ •
# unreal.Rotator(0, 89.999999, 0) โ†’ ์‹ค์ œ๋กœ๋Š” Pitch=90, Yaw=0์œผ๋กœ ๋“ค์–ด๊ฐ!
mesh.set_editor_property('relative_rotation', unreal.Rotator(roll=0.0, pitch=0.0, yaw=89.999999))

3) ์ •์ง€ ์ƒํƒœ์—์„  ๋ฌธ์ œ์—†๋‹ค๊ฐ€ ์›€์ง์ด๋ฉด์„œ๋งŒ ๋“œ๋Ÿฌ๋‚˜๋Š” ๋ฒ„๊ทธ๋Š” "์ •์ง€ ์ƒํƒœ" ์ž์ฒด๊ฐ€ ์›์ธ ์€ํ ์กฐ๊ฑด์ด๋‹ค. ์ƒ์  NPC๋Š” ์ ˆ๋Œ€ ์›€์ง์ด์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฉ”์‰ฌ ํšŒ์ „ ๋ณด์ •์ด ํ‹€๋ ค๋„ ํ‹ฐ๊ฐ€ ์•ˆ ๋‚ฌ๋‹ค. ์ƒˆ๋กœ์šด ๋™์ž‘(์ด๋™)์„ ์–น์„ ๋•Œ๋Š” ๊ธฐ์กด์— ๊ทธ ๋™์ž‘์„ ํ•œ ๋ฒˆ๋„ ํ•ด๋ณด์ง€ ์•Š์€ ์• ์…‹์˜ ์ˆจ์€ ์ „์ œ(์Šค์ผˆ๋ ˆํ†ค ๋ฐ”์ธ๋“œ ๋ฐฉํ–ฅ, ํšŒ์ „ ๋ณด์ •๊ฐ’)๋ฅผ ์˜์‹ฌํ•ด์•ผ ํ•œ๋‹ค.

๋ฐฐ์šด ์ 

์—ญํ• ์ด ๋‹ค๋ฅธ ์•กํ„ฐ๋Š” ์ฝ”๋“œ ์‹œ์ž‘ ์ง€์ ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•œ๋‹ค. "์ˆœ์ฐฐ"์ด๋ผ๋Š” ์š”์ฒญ๋งŒ ๋ณด๊ณ  ๊ธฐ์กด Enemy StateTree ์ธํ”„๋ผ์— ์†์„ ๋Œ€๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค๊ฐ€, ์‹ค์ œ๋กœ๋Š” ์™„์ „ํžˆ ์ƒˆ ํด๋”(Source/Retrieve/NPC/)์— ๋…๋ฆฝ๋œ Controller/Task๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒŒ ๋งž๋Š” ๋ฐฉํ–ฅ์ด์—ˆ๋‹ค. ์ „ํˆฌ AI์™€ ๋ฐฐ๊ฒฝ AI๋Š” ์š”๊ตฌ์‚ฌํ•ญ์ด ๊ทผ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅด๋ฏ€๋กœ(ํƒ€๊นƒํŒ… ์—ฌ๋ถ€, ์ธ์นด์šดํ„ฐ ์Šฌ๋กฏ ์—ฌ๋ถ€) ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ฐˆ๋ผ๋‘๋ฉด ์ดํ›„ ๋กœ์ง์ด ํ›จ์”ฌ ๋‹จ์ˆœํ•ด์ง„๋‹ค.
"์ •์ง€ ์ƒํƒœ์—์„œ๋งŒ ์จ์˜จ ์• ์…‹"์„ ์›€์ง์ด๋Š” ์šฉ๋„๋กœ ์žฌ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๊ทธ ์• ์…‹์ด ์ด๋™์„ ํ•œ ๋ฒˆ๋„ ๊ฒ€์ฆ๋ฐ›์€ ์  ์—†๋‹ค๋Š” ๊ฑธ ์ „์ œํ•ด์•ผ ํ•œ๋‹ค. ๋ฉ”์‰ฌ ํšŒ์ „ ๋ณด์ •์ฒ˜๋Ÿผ ์‹œ๊ฐ์ ์œผ๋กœ๋งŒ ๋“œ๋Ÿฌ๋‚˜๋Š” ๋ฒ„๊ทธ๋Š” ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋กœ๋Š” ๋ชป ์žก๊ณ , ๋ฐ˜๋“œ์‹œ ์‹ค์ œ๋กœ ์›€์ง์—ฌ๋ด์•ผ ๋ฐœ๊ฒฌ๋œ๋‹ค.


2. NPC ๋Œ€ํ™” ํ™•์žฅ โ€” ๋ชฝํƒ€์ฃผ ์žฌ์ƒ, ์•„์ดํ…œ ๋ณด์ƒ, ๋Œ€ํ™” ์ค‘ ์ •์ง€

๋ชฉํ‘œ

์ƒ์  NPC์— ์ด๋ฏธ ์žˆ๋˜ ๋Œ€ํ™” ์‹œ์Šคํ…œ์„ ๋งˆ์„ NPC์—๋„ ์ ์šฉํ•˜๋˜, NPC๋งˆ๋‹ค ๋‹ค๋ฅธ ๋Œ€์‚ฌยท๋ฐ˜์‘ ์• ๋‹ˆ๋ฉ”์ด์…˜ยท๋ณด์ƒ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ณ , ์ผ๋ถ€ NPC๋Š” ์—ฌ๋Ÿฌ ํ„ด์˜ ๋Œ€ํ™”(๋‹คํšŒ์ฐจ ๋Œ€ํ™”)์™€ ๋Œ€ํ™” ์ข…๋ฃŒ ์‹œ ๋žœ๋ค ์•„์ดํ…œ ์ง€๊ธ‰๊นŒ์ง€ ์ง€์›ํ•œ๋‹ค.

์›์ธ โ€” ์ด๋ฏธ ์žˆ๋Š” ๊ธฐ๋Šฅ๊ณผ ์—†๋Š” ๊ธฐ๋Šฅ์ด ์„ž์—ฌ ์žˆ์—ˆ์Œ

์กฐ์‚ฌํ•ด๋ณด๋‹ˆ DT_Dialogue(SpeakerTagยทTopicIdยทLinesยทFollowUpRows ๊ธฐ๋ฐ˜ ํŠธ๋ฆฌ ๊ตฌ์กฐ)์™€ URetrieveDialogueComponent(NPC๋ณ„ EditAnywhere๋กœ ๋Œ€์‚ฌยท์• ๋‹ˆ๋ฉ”์ด์…˜ ์ง€์ •)๋Š” ์ด๋ฏธ ์™„์„ฑ๋ผ ์žˆ์–ด์„œ ๋‹คํšŒ์ฐจ ๋Œ€ํ™”๋‚˜ NPC๋ณ„ ์ฐจ๋ณ„ํ™”๋Š” ์‚ฌ์‹ค ๋ฐ์ดํ„ฐ ์ž‘์—…๋งŒ์œผ๋กœ ์ถฉ๋ถ„ํ–ˆ๋‹ค. ๋ฐ˜๋ฉด ์‹ค์ œ๋กœ ์—†๋˜ ๊ฒƒ์€ ์„ธ ๊ฐ€์ง€: (1) ๋งˆ์„ NPC๋Š” InteractionResponse/InteractionTarget ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์–ด ์• ์ดˆ์— ๋ง์„ ๊ฑธ ์ˆ˜์กฐ์ฐจ ์—†์—ˆ๊ณ , (2) ๋Œ€ํ™” ์ข…๋ฃŒ ์‹œ ์•„์ดํ…œ์„ ์ง€๊ธ‰ํ•˜๋Š” ๋กœ์ง์ด ์ „ํ˜€ ์—†์—ˆ๊ณ , (3) "๋ชฝํƒ€์ฃผ ์žฌ์ƒ"์€ ๊ธฐ์กด ์ฝ”๋“œ๊ฐ€ PlayAnimation(์‹ฑ๊ธ€๋…ธ๋“œ, AnimSequence์šฉ)๋งŒ ์ง€์›ํ•ด ์ง„์งœ ๋ชฝํƒ€์ฃผ(์„น์…˜ยท๋ธ”๋ Œ๋“œ)๋Š” ๋ชป ์ผ๋‹ค.

ํ•ด๊ฒฐ โ€” AnimBP Slot ๊ธฐ๋ฐ˜ ๋ชฝํƒ€์ฃผ + ๊ฐ€์ค‘์น˜ ์•„์ดํ…œ ๋ณด์ƒ

// URetrieveDialogueComponent::PlayGreetingAnimation
if (GreetingMontage)
{
    // Slot ์žฌ์ƒ์€ AnimBP ์ƒํƒœ๋จธ์‹ (Idle/Run ๋ธ”๋ Œ๋“œ)์„ ๊ทธ๋Œ€๋กœ ๋‘” ์ฑ„ ์œ„์— ์–นํžŒ๋‹ค.
    // ์ˆœ์ฐฐ ์ค‘์ธ Villager๋„ ์ด๋™ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋Š๊ธฐ์ง€ ์•Š๋Š”๋‹ค.
    if (UAnimInstance* AnimInstance = CachedMesh->GetAnimInstance())
    {
        AnimInstance->Montage_Play(GreetingMontage);
        return;
    }
}

๊ธฐ์กด PlayAnimation ๋ฐฉ์‹(๋Œ€์‚ฌ ์‹œ์ž‘ ์‹œ CachedMesh->PlayAnimation(TalkingAnimation, true))์€ AnimBP๋ฅผ Single Node ๋ชจ๋“œ๋กœ ๊ฐ•์ œ ์ „ํ™˜ํ•ด๋ฒ„๋ ค ์ƒํƒœ๋จธ์‹ ์ด ๋ฉˆ์ถ˜๋‹ค. ์ •์ง€ ์ƒํƒœ์ธ ์ƒ์  NPC๋Š” ์ด๋ž˜๋„ ํ‹ฐ๊ฐ€ ์•ˆ ๋‚˜์ง€๋งŒ, ์ˆœ์ฐฐ ์ค‘์ธ Villager์—๊ฒŒ ์“ฐ๋ฉด ๋Œ€ํ™” ๋„์ค‘ ๊ฑท๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์–ผ์–ด๋ถ™๋Š”๋‹ค. Montage_Play๋Š” AnimBP๊ฐ€ ์ด๋ฏธ ๊ฐ–๊ณ  ์žˆ๋˜ DefaultSlot ๋…ธ๋“œ๋กœ ์žฌ์ƒ๋˜๋ฏ€๋กœ ์ƒํƒœ๋จธ์‹ ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค โ€” ๊ธฐ์กด Animation/TalkingAnimation ํ•„๋“œ๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ  Montage ํ•„๋“œ๋ฅผ ์˜ต์…˜์œผ๋กœ ์ถ”๊ฐ€ํ•ด, ๊ฐ’์ด ์žˆ์œผ๋ฉด Slot ์žฌ์ƒ, ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹์œผ๋กœ ํด๋ฐฑํ•˜๊ฒŒ ํ•ด์„œ ์ƒ์  NPC ์ชฝ์€ ์ „ํ˜€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์•˜๋‹ค.

bool URetrieveDialogueComponent::TryGrantItemReward(AActor* Instigator)
{
    if (ItemRewardPool.Num() == 0 || ItemRewardChance <= 0.f) return false;
    if (FMath::FRand() > ItemRewardChance) return false;
    // ๊ฐ€์ค‘์น˜ ๋ˆ„์  ํ›„ ๊ตด๋ ค์„œ ํ•˜๋‚˜ ์„ ํƒ โ†’ Instigator์˜ InventoryComponent::AddItem ํ˜ธ์ถœ
}

ItemRewardChance ๊ธฐ๋ณธ๊ฐ’์„ 0์œผ๋กœ ๋‘ฌ์„œ, ๊ธฐ์กด NPC๋Š” ์ด ํ•„๋“œ๋ฅผ ์•ˆ ๊ฑด๋“œ๋ฆฌ๋Š” ํ•œ ์ง€๊ธ‰์ด ์•„์˜ˆ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ˜ธ์ถœ ์ง€์ ์€ RetrievePlayerController::CloseConversation()(๋Œ€ํ™” ์ข…๋ฃŒ ์‹œ์ )์œผ๋กœ, ๊ธฐ์กด์— ์žˆ๋˜ CompleteStepOnConversationEnd ํ€˜์ŠคํŠธ ์™„๋ฃŒ ๋กœ์ง๊ณผ ๊ฐ™์€ ์ž๋ฆฌ์— ์–น์—ˆ๋‹ค.

์—ฌ๊ธฐ์„œ๋„ ์„ธ ๊ฐ€์ง€ ๋ฒ„๊ทธ๋ฅผ ์‹ค์ œ๋กœ ๊ฒช์—ˆ๋‹ค.

1) ๋Œ€์‚ฌ๊ฐ€ ์•ˆ ๋œจ๋Š” ๋ฒ„๊ทธ โ€” DataTable ๋ฐฐ์—ด ํ•„๋“œ ์“ฐ๊ธฐ๊ฐ€ ์กฐ์šฉํžˆ ๊นจ์ง. Monolith์˜ add_data_table_row๋กœ Lines(TArray\<FText>)์™€ FollowUpRows(TArray\<FName>)๋ฅผ ์ฑ„์› ๋Š”๋ฐ, ํˆด์€ "fields_set": 8๋กœ ์„ฑ๊ณต์„ ๋ณด๊ณ ํ–ˆ์ง€๋งŒ ์‹ค์ œ๋กœ ์ €์žฅ๋œ ๊ฐ’์€ ๋นˆ ๋ฌธ์ž์—ด์ด์—ˆ๋‹ค. ํŽธ์ง‘๊ธฐ ๋กœ๊ทธ์— "Json Value of type 'Array' used as a 'String'" ์—๋Ÿฌ๊ฐ€ ์žˆ์—ˆ๋˜ ๊ฑธ ๋’ค๋Šฆ๊ฒŒ ์—ฐ๊ฒฐ์ง€์—ˆ๋‹ค. ๊ฒ€์ฆ์€ ํ•ญ์ƒ ์“ฐ๊ธฐ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฏฟ์ง€ ๋ง๊ณ  get_data_table_rows๋กœ ๋‹ค์‹œ ์ฝ์–ด์„œ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ๋‹ค์‹œ ๋ฐฐ์› ๋‹ค.

# ๋ณต๊ตฌ: JSON ๊ฒฝ๋กœ๊ฐ€ ๋ฐฐ์—ด ํ•„๋“œ์—์„œ ๊นจ์ง€๋ฏ€๋กœ CSV ์™•๋ณต์œผ๋กœ ์šฐํšŒ
csv_text = dt.export_to_csv_string()
rows = list(csv.reader(io.StringIO(csv_text)))
# Lines/FollowUpRows ์ปฌ๋Ÿผ๋งŒ ์ง์ ‘ ์ฑ„์›Œ ๋„ฃ๊ณ 
dt.fill_from_csv_string(new_csv_text)  # ์ด ๊ฒฝ๋กœ๋Š” ๋ฐฐ์—ด์„ ์•ˆ ๊นจ๋œจ๋ฆผ

2) ๋Œ€ํ™” ์ค‘์—๋„ ์ˆœ์ฐฐ ์ด๋™์ด ์•ˆ ๋ฉˆ์ถ”๋Š” ๋ฒ„๊ทธ. StateTree๋Š” ๋Œ€ํ™” ์ƒํƒœ๋ฅผ ์ „ํ˜€ ๋ชฐ๋ผ์„œ, ๋Œ€ํ™”์ฐฝ์ด ๋–  ์žˆ์–ด๋„ Villager๊ฐ€ ๊ณ„์† ๊ฑธ์–ด ๋‹ค๋…”๋‹ค. ANPCPatrolAIController์— Deactivate()/Reactivate()๋ฅผ ์ถ”๊ฐ€ํ•ด URetrieveDialogueComponent::OpenConversationFor(๋Œ€ํ™” ์‹œ์ž‘)์—์„œ ์ •์ง€, ReturnToIdle()(๋Œ€ํ™” ์ข…๋ฃŒ ์ฝœ๋ฐฑ)์—์„œ ์žฌ๊ฐœํ•˜๋„๋ก ์—ฐ๊ฒฐํ–ˆ๋‹ค. AI ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์—†๋Š” ์ƒ์  NPC๋Š” Cast<ANPCPatrolAIController>๊ฐ€ ์‹คํŒจํ•˜๋ฏ€๋กœ ์ž์—ฐํžˆ ์•„๋ฌด ์˜ํ–ฅ์ด ์—†๋‹ค.

3) ์ƒํ˜ธ์ž‘์šฉ ํ”„๋กฌํ”„ํŠธ ํ…์ŠคํŠธ("Interact")๋ฅผ ๋ชป ๋ฐ”๊พผ ์‚ฌ๋ก€. Manager_InteractionTarget_C์˜ InteractionText ๋งต(TMap)์„ ์ƒˆ ๊ฐ’์œผ๋กœ ๋ฎ์–ด์จ๋„ ์—ฌ์ „ํžˆ "Interact"๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , ๋นˆ ์ƒํ˜ธ์ž‘์šฉ ์ฐฝ์ด ๊ฒน์ณ ๋ณด์ด๋Š” ์ƒˆ ์ฆ์ƒ๊นŒ์ง€ ๋‚˜ํƒ€๋‚ฌ๋‹ค. BP ์ปดํฌ๋„ŒํŠธ๋ฅผ ์•กํ„ฐ๊ฐ€ ์ด๋ฏธ ๋ ˆ๋ฒจ์— ๋ฐฐ์น˜๋œ ๋’ค์— ์ถ”๊ฐ€ํ•˜๋ฉด ์ธ์Šคํ„ด์Šค๊ฐ€ ๊ตฌ์‹ ๋ฐ์ดํ„ฐ๋ฅผ ๋“ค๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฐ€์„ค๋กœ, ์•กํ„ฐ๋ฅผ ์ง€์šฐ๊ณ  ์ƒˆ๋กœ ๋ฐฐ์น˜ํ•ด๋ณด๋Š” ์ ˆ์ฐจ๋กœ ๋„˜๊ฒผ๋‹ค โ€” ์ด ๋ถ€๋ถ„์€ ์„ธ์…˜์ด ์ข…๋ฃŒ๋˜๋Š” ์‹œ์ ๊นŒ์ง€ ๊ฒฐ๋ก ์ด ๋‚˜์ง€ ์•Š์•„ ๋‹ค์Œ ํ™•์ธ์ด ํ•„์š”ํ•œ ์ฑ„๋กœ ๋‚จ์•˜๋‹ค.

๋ฐฐ์šด ์ 

์“ฐ๊ธฐ API๊ฐ€ ์„ฑ๊ณต์„ ๋ณด๊ณ ํ•ด๋„, ๋ฐฐ์—ด/๋ณตํ•ฉ ํƒ€์ž… ํ•„๋“œ๋Š” ๋ฐ˜๋“œ์‹œ ์ฝ์–ด์„œ ์žฌ๊ฒ€์ฆํ•œ๋‹ค. DataTable์˜ TArray<FText>/TArray<FName> ํ•„๋“œ์ฒ˜๋Ÿผ JSON ์ง๋ ฌํ™”๊ฐ€ ๊นŒ๋‹ค๋กœ์šด ํƒ€์ž…์€ "์„ฑ๊ณต" ์‘๋‹ต๊ณผ ์‹ค์ œ ์ €์žฅ ๊ฐ’์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค. CSV ์™•๋ณต์ฒ˜๋Ÿผ ๋” ์•ˆ์ •์ ์ธ ์šฐํšŒ ๊ฒฝ๋กœ๋ฅผ ์•Œ์•„๋‘๋ฉด ๋ง‰ํ˜”์„ ๋•Œ ๋ฐ”๋กœ ๋Œ€์ฒด ์ˆ˜๋‹จ์œผ๋กœ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
๊ธฐ์กด ๋ฐฉ์‹์„ ๋Œ€์ฒดํ•˜์ง€ ๋ง๊ณ  ์˜ต์…˜์œผ๋กœ ์–น์œผ๋ฉด ํšŒ๊ท€ ์œ„ํ—˜์ด ์—†๋‹ค. PlayAnimation ์œ„์— Montage ํ•„๋“œ๋ฅผ ์˜ต์…˜์œผ๋กœ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, ์ƒˆ ๊ธฐ๋Šฅ์€ ๊ธฐ๋ณธ๊ฐ’์—์„œ ๊ธฐ์กด ๋™์ž‘์„ ๊ทธ๋Œ€๋กœ ์žฌํ˜„ํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด ์ด๋ฏธ ๋™์ž‘ํ•˜๋˜ 7์ข…์˜ ์ƒ์  NPC๋ฅผ ํ•˜๋‚˜๋„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  ์‹ ๊ทœ NPC ํƒ€์ž…์—๋งŒ ์ƒˆ ๊ธฐ๋Šฅ์„ ์ผค ์ˆ˜ ์žˆ๋‹ค.
AI ์ƒํƒœ(๋Œ€ํ™” ์ค‘)๋ฅผ ์ด๋™ ์‹œ์Šคํ…œ์ด ๋ชจ๋ฅด๋ฉด ๋ฐ˜๋“œ์‹œ ์ƒˆ์„œ ๋‚˜์˜จ๋‹ค. ์ƒํ˜ธ์ž‘์šฉ/๋Œ€ํ™” ๊ฐ™์€ "์ผ์‹œ์ ์œผ๋กœ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์ด ์บ๋ฆญํ„ฐ๋ฅผ ์ ์œ ํ•˜๋Š”" ์ƒํ™ฉ์€ ์ด๋™ ๋กœ์ง์— ๋ช…์‹œ์ ์œผ๋กœ ์•Œ๋ ค์•ผ ํ•œ๋‹ค โ€” ์ด๋ฒˆ์—” Deactivate/Reactivate ํŽ˜์–ด๋กœ ์—ฐ๊ฒฐํ–ˆ์ง€๋งŒ, ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ํ›…์€ ์ƒˆ NPC ํƒ€์ž…์„ ๋งŒ๋“ค ๋•Œ๋งˆ๋‹ค ๋น ์ง€๊ธฐ ์‰ฌ์šด ์ง€์ ์ด๋ผ ์ฒดํฌ๋ฆฌ์ŠคํŠธํ™”ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.


3. ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด โ€” ์ˆœ์ˆ˜ ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ESC์™€ ๋ฐฐํƒ€ ํŒจ๋„ ์‹œ์Šคํ…œ ํŽธ์ž…

๋ชฉํ‘œ

์ „ํˆฌ ์ค‘ ESC๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด(๊ณ„์†ํ•˜๊ธฐ / ์„ค์ • / ์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด / ๋ฉ”์ธ ๋ฉ”๋‰ด๋กœ / ๊ฒŒ์ž„ ์ข…๋ฃŒ)๊ฐ€ ๋œจ๊ฒŒ ํ•˜๊ณ , ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐฐํƒ€ ํŒจ๋„ ์‹œ์Šคํ…œ(์ธ๋ฒคํ† ๋ฆฌยท์›”๋“œ๋งตยท์ƒ์ ๊ณผ ๊ฐ™์€ OpenExclusivePanel ํ๋ฆ„)์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํŽธ์ž…์‹œํ‚จ๋‹ค.

์›์ธ โ€” ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ์ค‘ ESC์— ์ง„์ž…์ ์ด ์—†์—ˆ๊ณ , ๋ฉ”๋‰ด ์œ„์ ฏ๋„ ์—†์—ˆ์Œ

๊ธฐ์กด ARetrievePlayerController::InputKey๋Š” ESC๋ฅผ ํŒจ๋„์ด ์—ด๋ ค ์žˆ์„ ๋•Œ ๋‹ซ๋Š” ์šฉ๋„๋กœ๋งŒ ์ฒ˜๋ฆฌํ–ˆ๋‹ค(if (ActivePanel && Key == Escape) CloseActivePanel()). ํŒจ๋„์ด ํ•˜๋‚˜๋„ ์—†๋Š” ์ˆœ์ˆ˜ ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ์ƒํƒœ์—์„œ ESC๋Š” ์•„๋ฌด ๋™์ž‘๋„ ํ•˜์ง€ ์•Š์•˜๊ณ , ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด ์œ„์ ฏ ์ž์ฒด๋„ ์—†์—ˆ๋‹ค.

ํ•ด๊ฒฐ โ€” ESC ์ง„์ž…์  + C++ ๋ฒ ์ด์Šค ์œ„์ ฏ์œผ๋กœ ๋ฒ„ํŠผ ๋ฐฐ์„ ์„ ์ฝ”๋“œ์— ๊ณ ์ •

๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ์ค‘(ํŒจ๋„ยทํ€ต์Šฌ๋กฏ ํœ  ๋ชจ๋‘ ์—†์Œ) ESC๋ฅผ OpenPauseMenu๋กœ ์—ฐ๊ฒฐํ–ˆ๋‹ค.

// ์ˆœ์ˆ˜ ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ์ค‘ ESC โ†’ ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด
if (!ActivePanel && !bQuickSlotWheelOpen && Params.Key == EKeys::Escape)
{
    OpenPauseMenu();
    return true;
}

๋ฉ”๋‰ด ์œ„์ ฏ์€ ๋ฐฐํƒ€ ํŒจ๋„์˜ ๊ณตํ†ต ๋ฒ ์ด์Šค(URetrieveGamePanelWidget)๋ฅผ ์ƒ์†ํ•œ C++ ๋ฒ ์ด์Šค๋กœ ๋งŒ๋“ค์–ด, ๋ฒ„ํŠผ OnClicked ๋ฐฐ์„ ์„ ์ „๋ถ€ ์ฝ”๋“œ์—์„œ ์ฒ˜๋ฆฌํ–ˆ๋‹ค. WBP๋Š” ์•„๋ž˜ ์ด๋ฆ„์˜ ๋ฒ„ํŠผ๋งŒ ๋ฐฐ์น˜ํ•˜๋ฉด ๊ทธ๋ž˜ํ”„ ๋ฐฐ์„  ์—†์ด ๋™์ž‘ํ•œ๋‹ค.

UCLASS(Abstract, Blueprintable)
class URetrievePauseMenuWidget : public URetrieveGamePanelWidget
{
protected:
    virtual void NativeConstruct() override; // ๊ฐ ๋ฒ„ํŠผ OnClicked.AddDynamic

    UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> ResumeButton;   // RequestClose()
    UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> SettingsButton; // OpenSettingsPanel()
    UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> CommandsButton; // OpenControlsGuide()
    UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> MainMenuButton; // Server_RequestQuitToMenu()
    UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> QuitButton;     // QuitGame()
};

๋ฉ”๋‰ด๋Š” URetrieveGamePanelWidget ์ƒ์†์ด๋ผ ESC๋ฅผ ๋‹ค์‹œ ๋ˆ„๋ฅด๋ฉด ๋ฒ ์ด์Šค์˜ NativeOnKeyDown์ด ๋‹ซ์•„์ค€๋‹ค. ์ฒ˜์Œ์—” UGameplayStatics::SetGamePaused(true)๋กœ ์‹ค์ œ ์ •์ง€๋ฅผ ๋„ฃ์—ˆ๋‹ค๊ฐ€, ๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด์—์„œ๋Š” ํ•œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „์ฒด๋ฅผ ๋ฉˆ์ถœ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ฑท์–ด๋‚ด๊ณ , ํŒจ๋„์ด ์—ด๋ฆฐ ๋™์•ˆ ์ „ํˆฌ ์ž…๋ ฅ๋งŒ ํด๋ผ์ด์–ธํŠธ ๋กœ์ปฌ๋กœ ์ฐจ๋‹จํ•˜๋Š” ๊ธฐ์กด ๋ฐฉ์‹(PlayerTick์˜ if (!ActivePanel))์— ๋งก๊ฒผ๋‹ค.

๋ฐฐ์šด ์ 

์œ„์ ฏ ๋ฒ„ํŠผ ๋ฐฐ์„ ์€ ๊ทธ๋ž˜ํ”„๊ฐ€ ์•„๋‹ˆ๋ผ C++ BindWidgetOptional ๋ฒ ์ด์Šค์— ๋‘๋ฉด ๋„๊ตฌ ํ•œ๊ณ„๋ฅผ ์šฐํšŒํ•œ๋‹ค. Monolith ๊ฐ™์€ ์ž๋™ํ™”๋กœ WidgetBlueprint์˜ OnClicked๋ฅผ ๋ฐฐ์„ ํ•˜๋Š” ๊ฑด ๋ถˆ์•ˆ์ •ํ•˜์ง€๋งŒ, C++ ๋ฒ ์ด์Šค๊ฐ€ ์ด๋ฆ„์œผ๋กœ ๋ฒ„ํŠผ์„ ์žก์•„ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ถ™์ด๋ฉด WBP๋Š” ๋ฒ„ํŠผ ์ด๋ฆ„๋งŒ ๋งž์ถ”๋ฉด ๋œ๋‹ค.
๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด๋ฅผ ์ „์ œํ•˜๋ฉด "์ผ์‹œ์ •์ง€"๋Š” ์‹œ๊ฐ„ ์ •์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ํด๋ผ ๋กœ์ปฌ UI๋‹ค. SetGamePaused๋Š” ์‹ฑ๊ธ€์—์„œ๋งŒ ์˜๋ฏธ๊ฐ€ ์žˆ๊ณ , ๋„คํŠธ์›Œํฌ์—์„œ๋Š” ์ž…๋ ฅ ์ฐจ๋‹จ(ํด๋ผ ๋กœ์ปฌ)๋งŒ์œผ๋กœ ๋ฉ”๋‰ด UX๋ฅผ ๋งŒ๋“œ๋Š” ํŽธ์ด ์•ˆ์ „ํ•˜๋‹ค.


4. ํŒจํ‚ค์ง€์—์„œ ์กฐ์šฉํžˆ ์‚ฌ๋ผ์ง„ ํŒจ๋„ โ€” TSoftClassPtr์™€ ์ฟ ํ‚น ๊ฒฝ๋กœ

๋ชฉํ‘œ

์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด๊ฐ€ ์—๋””ํ„ฐ PIE๋ฟ ์•„๋‹ˆ๋ผ StandaloneยทํŒจํ‚ค์ง• ๋นŒ๋“œ์—์„œ๋„ ESC๋กœ ๋œจ๊ฒŒ ํ•œ๋‹ค.

์›์ธ โ€” ์†Œํ”„ํŠธ ์ฐธ์กฐ๋งŒ ์žˆ๋Š” UI ํด๋”๋ฅผ ์ฟ ์ปค๊ฐ€ ๋”ฐ๋ผ๊ฐ€์ง€ ์•Š์•„ ์œ„์ ฏ์ด ๋ฏธ์ฟก๋จ

PIE์—์„œ๋Š” ๋˜๋Š”๋ฐ Standalone/ํŒจํ‚ค์ง€์—์„œ ESC๋ฅผ ๋ˆŒ๋Ÿฌ๋„ ๋ฉ”๋‰ด๊ฐ€ ์•ˆ ๋–ด๋‹ค. ๋กœ๊ทธ์˜ ๊ฒฐ์ •์  ๋‹จ์„œ๋Š” Failed to open pause menu: PauseMenuClass is empty โ€” ์ฆ‰ ์ž…๋ ฅยท๋กœ์ง์€ ์ •์ƒ์ด๊ณ  PauseMenuClass.LoadSynchronous()๊ฐ€ null์ด์—ˆ๋‹ค. ํ™•์ธํ•ด ๋ณด๋‹ˆ ์ฟก๋œ ํด๋”์— WBP_SettingsScreen์€ ์žˆ๋Š”๋ฐ WBP_PauseMenu๋Š” ์•„์˜ˆ ์—†์—ˆ๋‹ค. PauseMenuClass๊ฐ€ TSoftClassPtr์ธ๋ฐ, ์ด ํ”„๋กœ์ ํŠธ์˜ ์ฟ ์ปค๋Š” ์†Œํ”„ํŠธ ์ฐธ์กฐ๋งŒ ๊ฑธ๋ฆฐ UI ํด๋”๊นŒ์ง€ ๋”ฐ๋ผ๊ฐ€์ง€ ์•Š์•„ ๋Œ€์ƒ ์œ„์ ฏ์„ ํŒจํ‚ค์ง€์— ๋„ฃ์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด๋‹ค(๊ณผ๊ฑฐ F10 ์„ค์ •์ฐฝ๋„ ๋˜‘๊ฐ™์€ ์ด์œ ๋กœ ์•ˆ ๋–ด๊ณ  /Game/Retrieve/UI/Settings๋ฅผ ๊ฐ•์ œ ์ฟก ๋ชฉ๋ก์— ๋„ฃ์–ด ๊ณ ์นœ ์ด๋ ฅ์ด ์žˆ์—ˆ๋‹ค).

ํ•ด๊ฒฐ โ€” ๊ฐ•์ œ ์ฟก ๋“ฑ๋ก + ๊ฐ’์€ C++ ์ƒ์„ฑ์ž์— ๊ณ ์ • + ๋Ÿฐํƒ€์ž„ ๊ฒฝ๋กœ ํด๋ฐฑ(์‚ผ์ค‘ ์•ˆ์ „์žฅ์น˜)

; DefaultGame.ini โ€” TSoftClassPtr๋กœ๋งŒ ์ฐธ์กฐ๋˜๋Š” UI ํด๋”๋Š” ์ฟ ์ปค๊ฐ€ ๋„๋‹ฌ ๋ชป ํ•จ
+DirectoriesToAlwaysCook=(Path="/Game/Retrieve/UI/Menu")

๊ฐ’ ์ž์ฒด๋„ ํˆด๋กœ ๋„ฃ์€ CDO ์˜ค๋ฒ„๋ผ์ด๋“œ๊ฐ€ ๋””์Šคํฌ์— ์•ˆ์ •์ ์œผ๋กœ ์ง๋ ฌํ™”๋˜์ง€ ์•Š์•„, ์ด๋ฏธ ์ž˜ ๋˜๋Š” SettingsPanelClass์™€ ๋˜‘๊ฐ™์ด C++ ์ƒ์„ฑ์ž์— ๊ณ ์ •ํ–ˆ๋‹ค.

// ๊ฐ’์€ ์ฝ”๋“œ์— ๋ฐ•์•„ BP ์ง๋ ฌํ™”ยทํˆด์— ์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค
PauseMenuClass = TSoftClassPtr<URetrieveGamePanelWidget>(
    FSoftObjectPath(TEXT("/Game/Retrieve/UI/Menu/WBP_PauseMenu.WBP_PauseMenu_C")));
// OpenPauseMenu โ€” ๊ฐ’์ด ๋น„์–ด๋„(์˜ˆ: BP์— None ์˜ค๋ฒ„๋ผ์ด๋“œ) ์•Œ๋ ค์ง„ ๊ฒฝ๋กœ๋กœ ํด๋ฐฑ
TSubclassOf<URetrieveGamePanelWidget> PanelClass = PauseMenuClass.LoadSynchronous();
if (!PanelClass)
{
    PanelClass = LoadClass<URetrieveGamePanelWidget>(
        nullptr, TEXT("/Game/Retrieve/UI/Menu/WBP_PauseMenu.WBP_PauseMenu_C"));
}

์ง„๋‹จ์˜ ํ•ต์‹ฌ์€ ๋กœ๊ทธ๊ฐ€ ์•„๋‹ˆ๋ผ ์ฟก ์‚ฐ์ถœ๋ฌผ ๋น„๊ต์˜€๋‹ค. Saved/Cooked/.../UI/Menu์— ํŠน์ • ์œ„์ ฏ์ด ์žˆ๋Š”์ง€/์—†๋Š”์ง€๊ฐ€ "๊ฒŒ์ž„์ด ์‹ค์ œ๋กœ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€"์˜ ๊ทธ๋ผ์šด๋“œ ํŠธ๋ฃจ์Šค์˜€๊ณ , ์—๋””ํ„ฐ ์ธ๋ฉ”๋ชจ๋ฆฌ CDO๋งŒ ํ™•์ธํ•˜๋ฉด ๊ณ„์† ํ—›๋‹ค๋ฆฌ๋ฅผ ์งš๊ฒŒ ๋๋‹ค.

๋ฐฐ์šด ์ 

TSoftClassPtr๋กœ๋งŒ ์ฐธ์กฐ๋˜๋Š” UI ํด๋”๋Š” DirectoriesToAlwaysCook์— ๋“ฑ๋กํ•ด์•ผ ํŒจํ‚ค์ง€์— ๋“ค์–ด๊ฐ„๋‹ค. ์†Œํ”„ํŠธ ์ฐธ์กฐ๋Š” ํ•˜๋“œ ์ฐธ์กฐ ์ฒด์ธ์ด ์—†์œผ๋ฉด ์ฟ ์ปค๊ฐ€ ๋ˆ„๋ฝํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ๋Ÿฌ๋ฉด ์—๋””ํ„ฐ์—์„œ๋Š” ๋ฉ€์ฉกํ•œ๋ฐ ํŒจํ‚ค์ง€์—์„œ๋งŒ ์กฐ์šฉํžˆ ์‚ฌ๋ผ์ง„๋‹ค.
"์—๋””ํ„ฐ์—์„œ ๋˜๋Š”๋ฐ ํŒจํ‚ค์ง€์—์„œ ์•ˆ ๋˜๋ฉด" ์ฟก ์‚ฐ์ถœ๋ฌผ์„ ์ง์ ‘ ๋ณธ๋‹ค. ์ธ๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ๋Š” ๋””์Šคํฌ/์ฟก ์ƒํƒœ์™€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, Saved/Cooked์— ์—์…‹์ด ์‹ค์ œ๋กœ ์กด์žฌํ•˜๋Š”์ง€๊ฐ€ ํŒ์ • ๊ธฐ์ค€์ด ๋œ๋‹ค.
์œ„์ ฏ ํด๋ž˜์Šค ๊ธฐ๋ณธ ํ• ๋‹น์€ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ C++ ์ƒ์„ฑ์ž(๋˜๋Š” ์‚ฌ๋žŒ์ด Details ํŒจ๋„์—์„œ) ๊ณ ์ •ํ•œ๋‹ค. ์ธ๋ฉ”๋ชจ๋ฆฌ CDO์—๋Š” ๋ณด์—ฌ๋„ ๋””์Šคํฌ ์ง๋ ฌํ™”์— ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด, ์žฌ์ปดํŒŒ์ผยท์žฌํŒจํ‚ค์ง• ํ›„ ์กฐ์šฉํžˆ ๋นˆ ๊ฐ’์ด ๋œ๋‹ค.


5. ๋ฉ”๋‰ด ๋ฒ„ํŠผ ๋ฐฐ์„  โ€” ๊ธฐ์กด ์ง„์ž…์  ์žฌ์‚ฌ์šฉ์œผ๋กœ ์„ค์ •ยท์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด ์—ฐ๊ฒฐ

๋ชฉํ‘œ

๋ฉ”์ธ ๋ฉ”๋‰ด์˜ "์„ค์ •" ๋ฒ„ํŠผ๊ณผ ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด์˜ "์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด" ๋ฒ„ํŠผ์„ ์ด๋ฏธ ์žˆ๋Š” ๊ธฐ๋Šฅ์— ์—ฐ๊ฒฐํ•œ๋‹ค. ์ƒˆ ํ๋ฆ„์„ ๋งŒ๋“œ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๊ธฐ์กด ์ง„์ž…์ ์„ ์žฌ์‚ฌ์šฉํ•ด ๋ฐฐ์„ ๋งŒ ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๋‹ค.

์›์ธ โ€” ๋ฒ„ํŠผ์ด ๋ช…์‹œ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋ผ ์žˆ๊ฑฐ๋‚˜ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์—†์—ˆ์Œ

๋ฉ”์ธ ๋ฉ”๋‰ด์˜ OptionsButton์€ NativeConstruct์—์„œ SetIsEnabled(false)๋กœ ์•„์˜ˆ ์ฃฝ์–ด ์žˆ์—ˆ๊ณ  ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ๋„ ์—†์—ˆ๋‹ค. ์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด์˜ CommandsButton(์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด) ์—ญ์‹œ ์œ„์ ฏ์—๋Š” ๋ฐฐ์น˜๋ผ ์žˆ์—ˆ์ง€๋งŒ ๋Œ€์‘ํ•˜๋Š” C++ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์—†์–ด ๋ˆŒ๋Ÿฌ๋„ ๋ฌด๋ฐ˜์‘์ด์—ˆ๋‹ค.

ํ•ด๊ฒฐ โ€” ์ฃฝ์€ ๋ฒ„ํŠผ์„ ์‚ด๋ ค OpenSettingsPanel / OpenControlsGuide์— ์—ฐ๊ฒฐ

// RetrieveMainMenuWidget.cpp โ€” ๋น„ํ™œ์„ฑํ™”๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ๊ธฐ์กด ์ง„์ž…์ ์— ์—ฐ๊ฒฐ
if (OptionsButton)
{
    OptionsButton->OnClicked.AddDynamic(this, &URetrieveMainMenuWidget::HandleOptionsClicked);
}

void URetrieveMainMenuWidget::HandleOptionsClicked()
{
    if (ARetrievePlayerController* PC = GetRetrievePlayerController())
    {
        PC->OpenSettingsPanel(); // F10๊ณผ ๋™์ผํ•œ ์ง„์ž…์  ์žฌ์‚ฌ์šฉ
    }
}

์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด๋„ ๊ฐ™์€ ํŒจํ„ด์œผ๋กœ, PC์— OpenControlsGuide๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ทธ ์•ˆ์—์„œ ๋ฐฐํƒ€ ํŒจ๋„ ์‹œ์Šคํ…œ(OpenExclusivePanel)์„ ์žฌ์‚ฌ์šฉํ–ˆ๋‹ค. ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ํŒจ๋„ ๋‹ซ๊ธฐ ์‹œ ์ž…๋ ฅ ๋ชจ๋“œ ๋ณต์› ๋กœ์ง์ด ์ด๋ฏธ ์„ธ์…˜ ์ƒํƒœ๋ณ„๋กœ ๋ถ„๊ธฐ๋ผ ์žˆ์—ˆ๋˜ ๋•์„ ๋ดค๋‹ค.

// RemoveActivePanelImmediately โ€” ๋ฉ”์ธ๋ฉ”๋‰ด/๊ฒฐ๊ณผ ํ™”๋ฉด์—์„œ ์—ฐ ํŒจ๋„์€ UIOnly+์ปค์„œ ์œ ์ง€
if (SessionState == ERetrieveSessionState::MainMenu ||
    SessionState == ERetrieveSessionState::Result)
{
    UpdateInputMode(SessionState);
}
else { /* ์ธ๊ฒŒ์ž„์—์„œ๋งŒ GameOnly๋กœ ๋ณต๊ท€ */ }

์ฆ‰ ๋ฉ”์ธ ๋ฉ”๋‰ด์—์„œ ์„ค์ •์ฐฝ์„ ์—ด์—ˆ๋‹ค ๋‹ซ์•„๋„ ์ปค์„œยทํ‚ค๋ณด๋“œ ์กฐ์ž‘์ด ์ฃฝ์ง€ ์•Š๊ณ  ์ •์ƒ ๋ณต๊ท€ํ–ˆ๋‹ค โ€” ์‹ ๊ทœ ํ๋ฆ„์„ ์œ„ํ•œ ๋ณ„๋„ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š” ์—†์—ˆ๋‹ค.

๋ฐฐ์šด ์ 

๋ฒ„ํŠผ์ด "๋ฌด๋ฐ˜์‘"์ผ ๋•Œ ๋จผ์ € ์˜์‹ฌํ•  ๊ฒƒ์€ ๋ฐฐ์„ ์ด ์•„๋‹ˆ๋ผ ํ™œ์„ฑ ์ƒํƒœ๋‹ค. SetIsEnabled(false)๋กœ ๋ง‰ํ˜€ ์žˆ๊ฑฐ๋‚˜ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์•„์˜ˆ ์—†๋Š” ๊ฒฝ์šฐ๊ฐ€ ํ”ํ•˜๊ณ , ์ด๊ฑด ๋ฐฐ์„  ๋กœ์ง์„ ์•„๋ฌด๋ฆฌ ๋ด๋„ ์•ˆ ๋ณด์ธ๋‹ค.
๊ธฐ์กด ์ง„์ž…์ (OpenSettingsPanel / OpenExclusivePanel)์„ ์žฌ์‚ฌ์šฉํ•˜๋ฉด ์‹ ๊ทœ UI๋Š” "๋ฐฐ์„ "๋งŒ ๋‚จ๋Š”๋‹ค. ์ž…๋ ฅ ๋ชจ๋“œ ๋ณต์›์ฒ˜๋Ÿผ ์„ธ์…˜ ์ƒํƒœ๋ณ„ ๋ถ„๊ธฐ๊ฐ€ ์ด๋ฏธ ๋งˆ๋ จ๋ผ ์žˆ์œผ๋ฉด, ์ƒˆ ํ™”๋ฉด์„ ๊ทธ ํ๋ฆ„์— ์–น๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์—ฃ์ง€ ์ผ€์ด์Šค๊นŒ์ง€ ๊ณต์งœ๋กœ ์ปค๋ฒ„๋œ๋‹ค.


6. ์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด ์œ„์ ฏ โ€” ์‹ค์ œ IMC ๋ฐ”์ธ๋”ฉ์„ ๊ทผ๊ฑฐ๋กœ ํ•œ ํ‚ค๋งต ๋ทฐ

๋ชฉํ‘œ

์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด ํ™”๋ฉด์„ ๋งŒ๋“ค๋˜, ํ‘œ์— ๋“ค์–ด๊ฐ€๋Š” ํ‚ค๋Š” ๋””์ž์ธ ๋ชฉ์—…์ด๋‚˜ ์ถ”์ธก์ด ์•„๋‹ˆ๋ผ ๊ฒŒ์ž„์— ์‹ค์ œ ๋งคํ•‘๋œ ํ‚ค๋กœ ์ฑ„์šด๋‹ค.

์›์ธ โ€” ์ฐธ๊ณ  ์ด๋ฏธ์ง€์˜ ํ‚ค๊ฐ€ ์‹ค์ œ ๋ฐ”์ธ๋”ฉ๊ณผ ๋‹ฌ๋ž์Œ

์ฒ˜์Œ์—” ์ฐธ๊ณ  ์ด๋ฏธ์ง€์˜ ์กฐ์ž‘ํ‘œ๋ฅผ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ฒผ๋Š”๋ฐ, ์‹ค์ œ ๊ฒŒ์ž„๊ณผ ์–ด๊ธ‹๋‚˜๋Š” ๊ณณ์ด ๋งŽ์•˜๋‹ค(์ด๋ฏธ์ง€์—์„  E๊ฐ€ ์ƒํ˜ธ์ž‘์šฉ์ด์—ˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” E=๋ฒ„์ŠคํŠธยทF=์ƒํ˜ธ์ž‘์šฉ, R=๋ฐฉ์–ด, Tab=ํƒ€๊ฒŸ ๊ณ ์ • ๋“ฑ). "์‹ค์ œ ํ‚ค"์˜ ๊ทผ๊ฑฐ๋ฅผ ์ฝ”๋“œ/๋ฐ์ดํ„ฐ์—์„œ ์ง์ ‘ ๋ฝ‘์•„์•ผ ํ–ˆ๋‹ค.

ํ•ด๊ฒฐ โ€” Enhanced Input IMC_Default์—์„œ ๋งคํ•‘์„ ๋คํ”„ํ•˜๊ณ  ํ•˜๋“œ์ฝ”๋”ฉ ํ‚ค์™€ ํ•ฉ์น˜๊ธฐ

UE5.7์—์„œ๋Š” UInputMappingContext์˜ ๊ตฌ mappings ํ”„๋กœํผํ‹ฐ๊ฐ€ deprecated ๋˜์–ด ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ƒˆ ๊ฒฝ๋กœ(default_key_mappings โ†’ mappings)๋กœ ์ ‘๊ทผํ•ด ์‹ค์ œ IAโ†’ํ‚ค๋ฅผ ๋คํ”„ํ–ˆ๋‹ค.

imc = unreal.load_asset('/Game/Retrieve/Input/IMC_Default')
# UE5.7: ๊ตฌ 'mappings'๋Š” deprecated(๋นˆ ๋ฐฐ์—ด) โ†’ default_key_mappings ๊ฒฝ์œ 
ms = imc.get_editor_property('default_key_mappings').get_editor_property('mappings')
for m in ms:
    a = m.get_editor_property('action')
    k = m.get_editor_property('key')
    print(a.get_name(), '=>', k.get_editor_property('key_name'))
# IA_Move=>W/S/A/D, IA_Jump=>SpaceBar, IA_Attack=>LMB, IA_HeavyAttack=>RMB,
# IA_Guard=>R, IA_LockOn=>Tab, IA_Burst=>E, IA_Absorb=>Q,
# IA_ElementMode1/2/3=>1/2/3, IA_Interaction=>F, IA_UseItem1/2=>4/5, IA_Zoom=>ํœ  ...

IMC์— ์—†๊ณ  PlayerController::InputKey์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌ๋˜๋Š” ํ•˜๋“œ์ฝ”๋”ฉ ํ‚ค(์„ค์ •=F10, ํ€ต์Šฌ๋กฏ ํœ =T, ๋ฏธ๋‹ˆ๋งต ํšŒ์ „=N, ์ธ๋ฒค=I, ์›”๋“œ๋งต=M, ์ผ์‹œ์ •์ง€=ESC)๋Š” ๋”ฐ๋กœ ์ทจํ•ฉํ•ด ํ•ฉ์ณค๋‹ค. ์œ„์ ฏ์€ URetrieveGamePanelWidget ์ƒ์†์œผ๋กœ ๋งŒ๋“ค์–ด ESC๋กœ ๋‹ซํžˆ๊ฒŒ ํ•˜๊ณ , ์นดํ…Œ๊ณ ๋ฆฌ(์ด๋™/์ „ํˆฌ/์›์†Œ/์‹œ์Šคํ…œ)๋ณ„ ์ƒ‰์ƒ ์ฝ”๋”ฉ๊ณผ ํ‚ค์บก ๋ฐฐ์น˜๋กœ ๊ตฌ์„ฑํ–ˆ๋‹ค.

๋ฐฐ์šด ์ 

"์‹ค์ œ ์กฐ์ž‘"์˜ ๊ทผ๊ฑฐ๋Š” ๋””์ž์ธ ๋ชฉ์—…์ด ์•„๋‹ˆ๋ผ IMC์™€ ์ฝ”๋“œ์˜ ํ•˜๋“œ์ฝ”๋”ฉ ํ‚ค๋‹ค. Enhanced Input์„ ์“ฐ๋ฉด ๋ฌผ๋ฆฌ ํ‚ค๋Š” IMC์—, ๋‚˜๋จธ์ง€๋Š” ์ปจํŠธ๋กค๋Ÿฌ์˜ InputKey ๋ถ„๊ธฐ์— ํฉ์–ด์ ธ ์žˆ์œผ๋ฏ€๋กœ ๋‘˜ ๋‹ค ํ›‘์–ด์•ผ ์ง„์งœ ๋งคํ•‘์ด ๋‚˜์˜จ๋‹ค.
์—”์ง„ ๋ฒ„์ „์ด ์˜ฌ๋ผ๊ฐ€๋ฉด ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ฒฝ๋กœ๊ฐ€ ๋ฐ”๋€๋‹ค. UE5.7์˜ IMC๋Š” mappings(deprecated)๊ฐ€ ์•„๋‹ˆ๋ผ default_key_mappings.mappings๋กœ ์ฝ์–ด์•ผ ํ•˜๊ณ , ์ด๋Ÿฐ ๊ฑด ์Šคํ‚ค๋งˆ๋ฅผ ์ง์ ‘ ํŒŒํ—ค์ณ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.


7. Live Coding์˜ ๊ฒฝ๊ณ„ โ€” ์ƒˆ UPROPERTYยทUFUNCTION์€ ์ •์‹ ๋นŒ๋“œ๊ฐ€ ํ•„์š”

๋ชฉํ‘œ

์œ„์ ฏ ๋ฒ„ํŠผ ๋ฐฐ์„  ๊ฐ™์€ C++ ๋ณ€๊ฒฝ์„ ์—๋””ํ„ฐ๋ฅผ ์ผ  ์ฑ„ ๋น ๋ฅด๊ฒŒ(Live Coding) ๋ฐ˜์˜ํ•˜๋˜, ๋ฐ˜์˜์ด ๋˜๋Š” ๋ณ€๊ฒฝ๊ณผ ์•ˆ ๋˜๋Š” ๋ณ€๊ฒฝ์„ ๊ตฌ๋ถ„ํ•œ๋‹ค.

์›์ธ โ€” ๋ผ์ด๋ธŒ ์ปดํŒŒ์ผ์€ ์„ฑ๊ณตํ–ˆ๋Š”๋ฐ ์ƒˆ ๋ฒ„ํŠผ๋งŒ ๋ฌด๋ฐ˜์‘

"์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด" ๋ฒ„ํŠผ์šฉ CommandsButton(์ƒˆ UPROPERTY)๊ณผ HandleCommandsClicked/OpenControlsGuide(์ƒˆ UFUNCTION)๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  Live Coding์œผ๋กœ ์ปดํŒŒ์ผํ–ˆ๋Š”๋ฐ, patch_applied๊ฐ€ ๋–ด๋Š”๋ฐ๋„ ๊ทธ ๋ฒ„ํŠผ๋งŒ ๋ˆŒ๋Ÿฌ๋„ ๋ฌด๋ฐ˜์‘์ด์—ˆ๋‹ค. ๋ฐ˜๋ฉด ์ด์ „ ์ •์‹ ๋นŒ๋“œ์— ์ด๋ฏธ ๋“ค์–ด ์žˆ๋˜ ResumeButton์€ ์ž˜ ๋™์ž‘ํ–ˆ๋‹ค.

ํ•ด๊ฒฐ โ€” ํ•จ์ˆ˜ ๋ณธ๋ฌธ์€ ๋ผ์ด๋ธŒ, ๋ฆฌํ”Œ๋ ‰์…˜ ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋Š” ์ •์‹ ์žฌ๋นŒ๋“œ

// ์ด๋Ÿฐ "์ƒˆ ๋ฆฌํ”Œ๋ ‰์…˜ ๋ฉค๋ฒ„"๋Š” Live Coding์œผ๋กœ ๋“ฑ๋ก๋˜์ง€ ์•Š๋Š”๋‹ค
UPROPERTY(meta = (BindWidgetOptional)) TObjectPtr<UButton> CommandsButton; // ํด๋ž˜์Šค ๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ
UFUNCTION() void HandleCommandsClicked();                                   // ์‹ ๊ทœ ๋ฆฌํ”Œ๋ ‰์…˜ ํ•จ์ˆ˜

Live Coding์€ ํ•จ์ˆ˜ ๋ณธ๋ฌธ ๋ณ€๊ฒฝ๋งŒ ํ•ซํŒจ์น˜ํ•˜๊ณ , ์ƒˆ UPROPERTY/UFUNCTION์ฒ˜๋Ÿผ ํด๋ž˜์Šค ๋ ˆ์ด์•„์›ƒยท๋ฆฌํ”Œ๋ ‰์…˜์ด ๋ฐ”๋€Œ๋Š” ๋ณ€๊ฒฝ์€ ๋“ฑ๋กํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๊ทธ๋ž˜์„œ BindWidgetOptional์ด CommandsButton์„ ์ฑ„์šฐ์ง€ ๋ชปํ•ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ถ™์ง€ ์•Š์•˜๋‹ค. ํ•ด๊ฒฐ์€ ๋‹จ์ˆœํ•˜์ง€๋งŒ ํ™•์‹คํ•˜๋‹ค โ€” ์—๋””ํ„ฐ๋ฅผ ๋‹ซ๊ณ  ์ •์‹ ์žฌ๋นŒ๋“œ ํ›„ ์žฌ์‹คํ–‰(๋˜๋Š” ์žฌํŒจํ‚ค์ง•). ์ฆ์ƒ์œผ๋กœ ์›์ธ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค: ๊ธฐ์กด ๋ฐ”์ด๋„ˆ๋ฆฌ์— ์žˆ๋˜ ๋ฒ„ํŠผ์€ ๋˜๊ณ , ๋ผ์ด๋ธŒ๋กœ ์ถ”๊ฐ€ํ•œ ์‹ ๊ทœ ๋ฉค๋ฒ„๋งŒ ๋ฌด๋ฐ˜์‘.

๋ฐฐ์šด ์ 

Live Coding์€ "ํ•จ์ˆ˜ ๋ณธ๋ฌธ ํ•ซํŒจ์น˜" ์ „์šฉ์œผ๋กœ ์ƒ๊ฐํ•œ๋‹ค. ์ƒˆ UPROPERTY/UFUNCTION/BindWidget ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋Š” ์ •์‹ ๋นŒ๋“œ๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, ๋ผ์ด๋ธŒ ์ปดํŒŒ์ผ์ด ์„ฑ๊ณต(patch_applied)ํ•ด๋„ ๋ฆฌํ”Œ๋ ‰์…˜์—๋Š” ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.
"๋ฐฉ๊ธˆ ์ถ”๊ฐ€ํ•œ ๊ฒƒ๋งŒ ์•ˆ ๋˜๊ณ  ๊ธฐ์กด ๊ฒƒ์€ ๋œ๋‹ค"๋ฉด ๋ฆฌํ”Œ๋ ‰์…˜ ๋“ฑ๋ก์„ ์˜์‹ฌํ•œ๋‹ค. ๋™์ผ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ธ๋ฐ ์‹ ๊ทœ ๋ฉค๋ฒ„๋งŒ ์ฃฝ์–ด ์žˆ์œผ๋ฉด ๋ฐฐ์„  ๋กœ์ง์ด ์•„๋‹ˆ๋ผ ๋นŒ๋“œ ๋ฐฉ์‹(๋ผ์ด๋ธŒ vs ์ •์‹)์˜ ๋ฌธ์ œ๋‹ค.


์˜ค๋Š˜์˜ ํ•ต์‹ฌ ์š”์•ฝ

์ž‘์—…ํ•ต์‹ฌ ๋ฌธ์ œํ•ด๊ฒฐ ์›์น™
๋งˆ์„ NPC ์ˆœ์ฐฐ AINPC ๋ฒ ์ด์Šค์— AI ์ž์ฒด๊ฐ€ ์—†์—ˆ๊ณ , ์ „ํˆฌ AI(Enemy)์— ๊ฒฐํ•ฉํ•ด ์žฌ์‚ฌ์šฉ ๋ถˆ๊ฐ€Enemy์™€ ์™„์ „ํžˆ ๋ถ„๋ฆฌ๋œ ์ตœ์†Œ StateTree(Task 1๊ฐœ, Evaluator ์—†์Œ) ์‹ ์„ค
์ด๋™ ์‹œ ํšŒ์ „ ๋ฒ„๊ทธbCanStrafe/bOrientRotationToMovement ์ถฉ๋Œ + ๋ฉ”์‰ฌ ํšŒ์ „ ๋ณด์ • ๋ˆ„๋ฝํšŒ์ „ ๊ถŒํ•œ์„ ํ•œ ์ฃผ์ฒด๋กœ ํ†ต์ผ + ๊ธฐ์กด ์ •์ƒ ๋™์ž‘ ์• ์…‹๊ณผ ์ปดํฌ๋„ŒํŠธ ๊ฐ’ ๋น„๊ต๋กœ ์›์ธ ํŠน์ •
NPC ๋Œ€ํ™” ํ™•์žฅ๋ชฝํƒ€์ฃผ ์žฌ์ƒยท์•„์ดํ…œ ๋ณด์ƒ ์—†์Œ, ๋Œ€ํ™” ์ค‘ ์ด๋™ ์•ˆ ๋ฉˆ์ถคAnimBP Slot ๊ธฐ๋ฐ˜ ๋ชฝํƒ€์ฃผ(์˜ต์…˜ ํด๋ฐฑ), ํ™•๋ฅ  ๊ธฐ๋ฐ˜ ๋ณด์ƒ, Deactivate/Reactivate ํ›…
DataTable ๋ฐฐ์—ด ํ•„๋“œJSON ์“ฐ๊ธฐ ๊ฒฝ๋กœ๊ฐ€ ๋ฐฐ์—ด ํ•„๋“œ์—์„œ ์กฐ์šฉํžˆ ์‹คํŒจCSV export/fill ์™•๋ณต์œผ๋กœ ์šฐํšŒ + ์“ฐ๊ธฐ ํ›„ ํ•ญ์ƒ ์žฌ๊ฒ€์ฆ
์ผ์‹œ์ •์ง€ ๋ฉ”๋‰ด๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ์ค‘ ESC ์ง„์ž…์ ยท๋ฉ”๋‰ด ์œ„์ ฏ ๋ถ€์žฌESCโ†’OpenPauseMenu + C++ ๋ฒ ์ด์Šค ์œ„์ ฏ์œผ๋กœ ๋ฒ„ํŠผ ๋ฐฐ์„ ์„ ์ฝ”๋“œ์— ๊ณ ์ •, MP๋Š” ์ •์ง€ ๋Œ€์‹  ํด๋ผ ๋กœ์ปฌ ์ž…๋ ฅ ์ฐจ๋‹จ
ํŒจํ‚ค์ง€ ๋ฏธํ‘œ์‹œ ํŒจ๋„์†Œํ”„ํŠธ ์ฐธ์กฐ UI ํด๋”๋ฅผ ์ฟ ์ปค๊ฐ€ ๋ˆ„๋ฝDirectoriesToAlwaysCook ๋“ฑ๋ก + ๊ฐ’์€ C++ ์ƒ์„ฑ์ž ๊ณ ์ • + ๊ฒฝ๋กœ ํด๋ฐฑ(์‚ผ์ค‘ ์•ˆ์ „์žฅ์น˜)
๋ฉ”๋‰ด ๋ฒ„ํŠผ ๋ฐฐ์„ ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑ/ํ•ธ๋“ค๋Ÿฌ ๋ถ€์žฌ์ฃฝ์€ ๋ฒ„ํŠผ์„ ์‚ด๋ ค ๊ธฐ์กด ์ง„์ž…์ (OpenSettingsPanel/OpenExclusivePanel) ์žฌ์‚ฌ์šฉ
์กฐ์ž‘ํ‚ค ์•ˆ๋‚ด์ฐธ๊ณ  ์ด๋ฏธ์ง€๊ฐ€ ์‹ค์ œ ๋ฐ”์ธ๋”ฉ๊ณผ ๋ถˆ์ผ์น˜IMC_Default + ํ•˜๋“œ์ฝ”๋”ฉ ํ‚ค์—์„œ ์‹ค์ œ ๋งคํ•‘์„ ์ง์ ‘ ๋คํ”„
Live Coding ํ•œ๊ณ„์ƒˆ UPROPERTY/UFUNCTION ๋ฏธ๋ฐ˜์˜ํ•จ์ˆ˜ ๋ณธ๋ฌธ๋งŒ ๋ผ์ด๋ธŒ, ๋ฆฌํ”Œ๋ ‰์…˜ ๋ฉค๋ฒ„ ์ถ”๊ฐ€๋Š” ์ •์‹ ์žฌ๋นŒ๋“œ

๊ณตํ†ต ๊ตํ›ˆ: ์˜ค๋Š˜ ๋‘ ๊ฐˆ๋ž˜ ์ž‘์—…(๋ฐฐ๊ฒฝ NPC, ์ผ์‹œ์ •์ง€/์กฐ์ž‘ํ‚ค UI)์€ ๊ฒฐ๊ตญ ๊ฐ™์€ ์›์น™์œผ๋กœ ์ˆ˜๋ ดํ–ˆ๋‹ค.

  1. ์žˆ๋Š” ๊ฒƒ๊ณผ ์—†๋Š” ๊ฒƒ์„ ์ •ํ™•ํžˆ ๊ฐ€๋ฅธ๋‹ค. ๋Œ€ํ™” ํŠธ๋ฆฌยท๋‹คํšŒ์ฐจ ๋Œ€ํ™”ยท์„ค์ •์ฐฝยท๋ฐฐํƒ€ ํŒจ๋„ยท์ž…๋ ฅ๋ชจ๋“œ ๋ณต์›์€ ์ด๋ฏธ ์žˆ์—ˆ๊ณ , AIยท๋ชฝํƒ€์ฃผยท์•„์ดํ…œ ๋ณด์ƒยท์ผ์‹œ์ •์ง€ ์ง„์ž…์ ยท์กฐ์ž‘ํ‚ค ์œ„์ ฏ์€ ์—†์—ˆ๋‹ค. ์žˆ๋Š” ๊ฑธ ๋‹ค์‹œ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ์—†๋Š” ๊ฒƒ๋งŒ ์–น์œผ๋‹ˆ, ์ƒ์  NPC 7์ข…๋„ ๊ธฐ์กด ๋ฉ”๋‰ด ํ๋ฆ„๋„ ํ•˜๋‚˜ ์•ˆ ๊ฑด๋“œ๋ฆฌ๊ณ  ์‹ ๊ทœ ๊ธฐ๋Šฅ์„ ๋ถ™์ผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.
  2. ์—ญํ• ์ด ๋‹ค๋ฅธ ์•กํ„ฐยทUI๋Š” ์‹œ์ž‘ ์ง€์ ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•˜๊ณ , ์ƒˆ ๊ธฐ๋Šฅ์€ ๊ธฐ์กด ๋™์ž‘์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌํ˜„ํ•˜๋Š” ์˜ต์…˜์œผ๋กœ ์–น๋Š”๋‹ค. Enemy์—์„œ ๊ฐˆ๋ผ๋‚ธ NPC AI, PlayAnimation ์œ„์— ์–น์€ Montage ์˜ต์…˜์ด ๊ทธ ์˜ˆ๋‹ค.
  3. ๋ฌธ์ œ๋Š” ๋Œ€๋ถ€๋ถ„ ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ํˆด์ฒด์ธ์˜ ๊ฒฝ๊ณ„์—์„œ ๋‚ฌ๋‹ค. DataTable ๋ฐฐ์—ด ์“ฐ๊ธฐ, ์ฟ ํ‚น ๋ˆ„๋ฝ, Live Coding์˜ ๋ฆฌํ”Œ๋ ‰์…˜ ํ•œ๊ณ„, IMC ์ ‘๊ทผ ๊ฒฝ๋กœ ๋ณ€๊ฒฝ โ€” ๋ชจ๋‘ "์“ฐ๊ธฐ ์„ฑ๊ณต ์‘๋‹ต / ์—๋””ํ„ฐ์—์„œ ๋จ"์„ ๋ฏฟ๋Š” ์ˆœ๊ฐ„ ํ—›๋‹ค๋ฆฌ๋ฅผ ์งš๊ฒŒ ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํŒ์ • ๊ธฐ์ค€์„ "์“ฐ๊ณ  ๋‚˜์„œ ์ฝ์–ด ์žฌ๊ฒ€์ฆํ–ˆ๋Š”๊ฐ€ / ์ฟก ์‚ฐ์ถœ๋ฌผ์— ์žˆ๋Š”๊ฐ€ / ์ •์‹ ๋นŒ๋“œ์— ๋“ค์–ด๊ฐ”๋Š”๊ฐ€"๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ์˜ค๋Š˜ ๋ฐ˜๋ณต๋œ ๊ตํ›ˆ์ด์—ˆ๋‹ค.
profile
๊ฐœ๋ฐœํ•ฉ์‹œ๋‹ค!

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