UE5.7+ C++ 8인 팀 개발

The Others Inside

Technical Portfolio

공포 루프물 1인칭 게임의 핵심 시스템 설계부터
이상현상 매니저, 스테이지 관리, 게임 흐름 설계까지

기획서(PDF) 프로젝트 개요
개발 기간 2026.02 ~ 2026.03
팀 구성 총괄 1 · 프로그래머 1 · 기획 2 · 디자이너 4
엔진 Unreal Engine 5.7+
상태 2026.03 개발 종료
SCROLL

프로젝트 개요

스테이지를 반복(루프)하며 정상/이상현상을 판별하는 1인칭 공포 게임입니다. 핵심 로직은 C++ 100%로 구현하고, Blueprint는 데이터 할당·연출 바인딩에만 사용합니다. 각 섹션의 클래스 다이어그램에서 파일 배지를 클릭하면 실제 C++ 코드를 팝업으로 확인할 수 있습니다.

모듈러 아키텍처

  • WorldSubsystem 기반 시스템 분리 — 스테이지/이상현상/개발도구를 독립 서브시스템으로 격리
  • GameMode는 판정만, PlayerController는 입력/연출만 — 단일 책임 원칙
  • GameInstance를 데이터 허브로 활용해 레벨 전환 간 상태 유지

데이터 드리븐 설계

  • 8개 DataTable로 밸런싱 외부화 — DT_Anomalies, DT_StageConfig, DT_InterferenceRules
  • Google Sheets → CSV → UE DataTable 임포트 파이프라인으로 기획자 협업
  • FTableRowBase 기반 Row Struct로 타입 안전한 데이터 접근

이상현상 엔진

  • EAnomalyType 17종 — Transform, Material, Visibility, Gimmick 등 다형성 처리
  • FGameplayTag 타겟팅으로 월드의 UAnomalyTargetComponent 탐색/적용
  • 경험 기록(정답/오답)으로 중복 제거 + 오답 우선 재등장 밸런싱

2단계 판정 구조

  • 문(Exit)으로 “정상” 판정, 거울로 “이상현상” 판정 — 진입점 분리
  • 판정 후 연출 완료 대기 → 완료 콜백 → 결과 처리로 연출-로직 분리
  • 델리게이트 브로드캐스트로 HUD/PC에 결과를 느슨하게 전파

GameplayTag 활용

  • 이상현상 타겟, 스폰 포인트, 복도 문 역할 전환 대상을 일관된 태그로 식별
  • 레벨 하드코딩 의존도를 제거하고 데이터 기반 유연한 매핑

상호작용 · 연출

  • 카메라 Ray Trace 기반 IInteractable 감지 + CustomDepth 하이라이트
  • LevelSequence로 상호작용 연출, 완료 콜백으로 판정 흐름 연결

핵심 시스템

각 시스템의 핵심 설계와 담당 클래스를 요약합니다. 항목을 클릭하면 해당 섹션으로 이동합니다.

클래스 다이어그램

클래스를 클릭하여 상세 코드를 확인하세요.

Game Framework
WorldSubsystem
Component / Actor
Interaction
Game Framework
«GameMode» ATOIGameMode : AGameModeBase
+ CurrentStageIndex : int32
+ bIsCurrentStageAbnormal : bool
+ bPendingStageTransition : bool
+ SubmitDecision(bAbnormal, Actor)
+ OnStageClear()
+ OnStageFail(bInstantDeath)
+ StartStage(Idx, bAbnormal)
⚡ OnDecisionJudged
⚡ OnMapTransition
⚡ OnStageClearEvent
⚡ OnStageFailEvent
«GameInstance» UTOIGameInstance : UGameInstance
+ CurrentStageIndex : int32
+ CurrentMapID : int32
+ ConsecutiveNormalCount : int32
+ CurrentLoopCount : int32
+ AnomalyDataTable : UDataTable*
+ StageConfigDataTable : UDataTable*
+ RecordAnomalyExperience()
+ GetWronglyExperiencedAnomalies()
+ HasCorrectlyExperienced()
«PlayerController» ATOIPlayerController : APlayerController
+ MasterHUD : UTOIMasterHUD*
+ DefaultMappingContext : UIMC*
+ bIsPlayingSequence : bool
+ TogglePause()
+ StartSequencedInteraction()
+ FadeOut() / FadeIn()
+ NotifyJudgementDirectionComplete()
제어 · 델리게이트 · 상태 참조
World Subsystems
«WorldSubsystem» UStageManager : UWorldSubsystem
+ LoadStage(Loop, Stage, Map, bForce)
+ ResetStage()
+ TransitionToNextMap(MapID)
+ ConfigureDoorRolesForMap()
+ DetermineAndApplyAnomaly()
«WorldSubsystem» UAnomalyManager : UWorldSubsystem
+ CurrentAnomaly : TOptional<FAnomalyData>
- AffectedComponents : TArray<...>
+ SelectAnomalyForStage()
+ ApplyCurrentAnomaly()
+ RollbackAll()
+ FilterAvailableAnomalies()
«WorldSubsystem» UTOIDeveloperSubsystem : UWorldSubsystem
+ CurrentLightingScenario
+ CurrentGraphicsQuality
+ DevMappingContext : UIMC*
+ SwitchToDayLight()
+ SetGraphicsQuality(Quality)
+ SetupDevInputBindings(PC)
이상현상 적용 · 상태 초기화 · 상호작용
Actors & Components
«Character» ATOICharacter : ACharacter
+ FPCameraComponent
+ FlashlightComponent
+ CameraAnimComponent
«Interactable» ATOIDoor : ATOIInteractableBase
+ DoorType : EDoorType
+ CurrentState : EDoorState
⚡ OnDoorStateChanged
⚡ OnJudgementResult
«Interactable» ATOIMirror : ATOIInteractableBase
+ CurrentState : EMirrorState
⚡ OnMirrorJudgementResult
«ActorComponent» UAnomalyTargetComponent : UActorComponent
+ TargetTag : FGameplayTag
+ bAnomalyActive : bool
+ ApplyAnomaly(AnomalyData)
+ Rollback()
«ActorComponent» UTOIInteractionComponent : UActorComponent
+ InteractionDistance : float
+ TryInteract()
⚡ OnInteractionTargetChanged
Anomaly Manager
«WorldSubsystem» UAnomalyManager : UWorldSubsystem
+ AnomalyDataTable : UDataTable*
+ StageConfigDataTable : UDataTable*
+ CurrentAnomaly : TOptional<FAnomalyData>
+ CurrentAnomalyRowName : FName
- AffectedComponents : TArray<TWeakObjectPtr<UAnomalyTargetComponent>>
+ SelectAnomalyForStage(StageIndex, MapID, bForce) : bool
+ ApplyCurrentAnomaly()
+ RollbackAll()
+ ProcessPlayerDecision(bSelectedMirror) : bool
+ FindComponentsByTag(TargetTag) : TArray
- FilterAvailableAnomalies(MapID) : TArray
- GetStageConfig(StageIdx, MapID) : FStageConfigData*
FindComponentsByTag() → ApplyAnomaly()
Component Hierarchy
«ActorComponent» — Base UAnomalyTargetComponent : UActorComponent
+ TargetTag : FGameplayTag
+ AlternateMaterial : UMaterialInterface*
+ AnomalySound : USoundBase*
+ AnomalyMontage : UAnimMontage*
+ AnomalyParticle : UNiagaraSystem*
+ SpawnActorClass : TSubclassOf<AActor>
- OriginalState : FOriginalActorState
+ ApplyAnomaly(AnomalyData)
+ Rollback()
~ OnApplyGimmickAnomaly() [NativeEvent]
~ OnRollbackGimmick() [NativeEvent]
▲ 상속
«Gimmick» UCeilingLowTarget
+ StartDelay, DescentDuration
+ CrushThreshold : float
⚡ OnCeilingCrush
«Gimmick» UGradualScaleTarget
+ ScaleChangeRatePerSecond
+ WarpSound : USoundBase*
FAnomalyData 구조체로 동작 파라미터 전달
Data Types
«USTRUCT» FAnomalyData
+ Type : EAnomalyType
+ Map_ID : int32
+ TargetTag : FGameplayTag
+ Probability : float
+ ScaleMultiplier : float
+ bInstantDeath : bool
+ bCamcorder : bool
«UENUM» EAnomalyType
Transform
Material
Visibility
Scale
Gimmick
Sound
LightColor / Intensity
Spawn / Animation
Particle / Decal
«USTRUCT» FAnomalyExperienceRecord
+ AnomalyRowName : FName
+ bCorrectlyIdentified : bool
+ StageIndex : int32
+ LoopCount : int32
«USTRUCT» FOriginalActorState
+ OriginalTransform
+ OriginalScale
+ OriginalMaterial
+ bCached : bool
스테이지 라이프사이클
UStageManager.LoadStage() 호출 시 실행되는 전체 흐름
1 ResetStage() RollbackAll()
Door/Mirror 상태 초기화
2 ConfigureDoorRoles() MapID 기반
Entrance/Exit 전환
3 DetermineAnomaly() AnomalyManager에
선택·적용 위임
4 Update GI State GameInstance에
Stage/Map 저장
5 GM→StartStage() GameMode에
스테이지 시작 통보
판정 흐름 (SubmitDecision)
플레이어의 "정상/이상" 판단 → 결과 처리 → 다음 스테이지
Exit Door 상호작용 🚪 "정상이다"
SubmitDecision(false, Door)
or
Mirror 상호작용 🪞 "이상이다"
SubmitDecision(true, Mirror)
GameMode 판정: bPlayerThinksAbnormal == bIsCurrentStageAbnormal?
✔ 정답 (Correct)
NotifyJudgementResult → 연출 재생
OnJudgementDirectionComplete()
→ OnStageClear()
→ LoadStage(nextStage)
✘ 오답 (Wrong)
NotifyJudgementResult → 실패 연출
OnJudgementDirectionComplete()
→ OnStageFail()
→ LoadStage(0) 루프 리셋
MaxStageIndex 도달 시 → TransitionToNextMap() 또는 GameComplete
핵심 데이터
«USTRUCT» FStageConfigData
+ StageIndex : int32
+ Map_ID : int32
+ AnomalyChance : float
+ MaxConsecutiveNormal
+ bAlwaysNormal : bool
+ GuardSpawnChance
«UENUM» EDoorType
Entrance (일반)
Exit (판별용)
«UENUM» EDoorState
Closed → Opening
Open → Closing
«UENUM» EMirrorState
Normal
Interacting
WaitingForJudgement
UTOIGameInstance — DataTable References
«GameInstance» — 데이터 허브 UTOIGameInstance
+ AnomalyDataTable : UDataTable*
+ StageConfigDataTable : UDataTable*
+ DialogDataTable : UDataTable*
+ EnvironmentDataTable : UDataTable*
+ InteractableDataTable : UDataTable*
+ InterferenceRuleDataTable : UDataTable*
+ LocalizationDataTable : UDataTable*
+ SoundListDataTable : UDataTable*
DataTable → Row Struct → Subsystem/Component 소비
DataTable → Consumer 매핑
각 DataTable의 Row 구조체와 주요 소비 시스템
DT_Anomalies UAnomalyManager FAnomalyData
DT_StageConfig UAnomalyManager FStageConfigData
DT_Dialog Dialog System FDialogData
DT_Environment Environment Mgr FEnvironmentData
DT_Interactable InteractableBase FInteractableData
DT_Interference Interference Mgr FInterferenceRuleData
DT_Localization UI Widgets FLocalizationData
DT_SoundList Audio System FSoundListData
Row Struct 상세
TOIDataTypes.h — Row Structs
«USTRUCT» : FTableRowBase FAnomalyData
Type, Map_ID, TargetTag
Probability, ScaleMultiplier
bInstantDeath, bCamcorder
«USTRUCT» : FTableRowBase FStageConfigData
StageIndex, Map_ID
AnomalyChance
MaxConsecutiveNormal
«USTRUCT» : FTableRowBase FInterferenceRuleData
RuleCategory, Priority
Condition_Metric
GaugeChange
«USTRUCT» : FTableRowBase FDialogData
SourceString
SpeakerID : EDialogSpeaker
Duration
«USTRUCT» : FTableRowBase FSoundListData
Type : ESoundType
SoundPath : TSoftObjectPtr
Volume, Loop
«USTRUCT» : FTableRowBase FEnvironmentData
FogDensity
LightIntensityMult
PostProcess_Grain
공통 Enum 타입
«UENUM» EInteractType
Inspect, Sit, Open, Pickup
«UENUM» ESoundType
BGM, SFX, Ambience, Voice, UI
«UENUM» EDialogSpeaker
System, Ego_Avoid, Ego_Meta
Doctor, Player, NPC
«UENUM» EInterferenceCondition
LoopCount, StagnationTime
StageLevel, LoopFailCount

이상현상 시스템

DataTable + EAnomalyType enum 디스패치로 다양한 이상현상을 관리하고, UAnomalyTargetComponent 계층을 통해 월드 액터에 적용·복구하는 핵심 시스템입니다.

DataTable 기반 이상현상 풀

DT_Anomalies DataTable에 FAnomalyData Row로 전체 이상현상 풀을 관리합니다. EAnomalyType enum으로 적용 방식을 분기하고, FGameplayTag로 타겟 액터를 식별합니다.

DT_Anomalies DataTable — 에디터에서 이상현상 풀을 편집·밸런싱
FGameplayTag로 타겟 식별 — 컴포넌트의 TargetTag와 매칭
EAnomalyType enum 디스패치 — Transform, Material, Visibility, Scale, Gimmick 등 12종
// TOIDataTypes.h — FAnomalyData USTRUCT(BlueprintType) struct FAnomalyData : public FTableRowBase { GENERATED_BODY() UPROPERTY(EditAnywhere) EAnomalyType Type; UPROPERTY(EditAnywhere) int32 Map_ID; UPROPERTY(EditAnywhere) FGameplayTag TargetTag; UPROPERTY(EditAnywhere) float Probability; UPROPERTY(EditAnywhere) float ScaleMultiplier; UPROPERTY(EditAnywhere) bool bInstantDeath; UPROPERTY(EditAnywhere) bool bCamcorder; };

AnomalyTargetComponent 계층구조

각 이상현상은 UAnomalyTargetComponent 기반의 컴포넌트로 개발됩니다. 범용적인 설정(위치·머티리얼·스케일·사운드 등)은 부모 컴포넌트가 일괄 처리하고, 디테일한 동작이 필요한 이상현상은 자식 클래스로 상속받아 BlueprintNativeEvent를 오버라이드하여 고유 로직을 구현합니다.

UAnomalyTargetComponent — 부모: Transform, Material, Scale, Sound, Spawn 등 17종 범용 Apply/Rollback
UCeilingLowTargetComponent — 자식: 천장 하강 기믹 (StartDelay, DescentDuration, CrushThreshold)
UGradualScaleTargetComponent — 자식: 점진적 크기 변형 (ScaleChangeRatePerSecond)
FOriginalActorState — 원본 상태 캐싱 → Rollback 시 복원 보장
개발 종료 시점까지 C++ 자식 컴포넌트는 위 2종만 구현 — 나머지는 부모 컴포넌트 설정으로 처리
// UAnomalyTargetComponent.h UCLASS(BlueprintType) class UAnomalyTargetComponent : public UActorComponent { GENERATED_BODY() UPROPERTY(EditAnywhere) FGameplayTag TargetTag; UPROPERTY(EditAnywhere) UMaterialInterface* AlternateMaterial; UPROPERTY(EditAnywhere) USoundBase* AnomalySound; UPROPERTY(EditAnywhere) TSubclassOf<AActor> SpawnActorClass; void ApplyAnomaly(const FAnomalyData& Data); void Rollback(); // 기믹 자식 클래스가 오버라이드 virtual void OnApplyGimmickAnomaly(); virtual void OnRollbackGimmick(); };

컴포넌트 기반 확장 구조

각 이상현상은 액터에 컴포넌트로 부착하는 방식으로 개발됩니다. 부모 컴포넌트(UAnomalyTargetComponent)는 EAnomalyType에 따른 범용 Apply/Rollback을 이미 처리하므로, 대부분의 이상현상은 DataTable 파라미터 조정만으로 동작합니다. 천장 하강·점진적 스케일 변형처럼 Tick 기반 로직이나 전용 델리게이트가 필요한 경우에만 자식 클래스를 생성해 OnApplyGimmickAnomaly / OnRollbackGimmick을 오버라이드합니다.

DT_Anomalies
FAnomalyData Row
AnomalyManager
SelectForStage · Filter
FindByTag
TargetTag 매칭
TargetComponent
ApplyAnomaly
Delegate
결과 통보

에디터 화면 · 디버그 도구

이상현상 데이터는 DataTable(DT_Anomalies)과 DataAsset으로 에디터에서 관리됩니다. 디버깅 시에는 TOI 디버그 설정에서 특정 이상현상을 테이블에서 조회하여 강제 적용할 수 있어, 반복 테스트 없이도 원하는 이상현상을 즉시 확인할 수 있습니다.

디버그를 통한 이상현상 강제 적용 흐름

에디터의 "프로젝트 설정 → TOI 디버그" 메뉴에서 아래 과정으로 특정 이상현상을 강제할 수 있습니다. 이를 통해 반복 플레이 없이 특정 이상현상의 동작을 빠르게 검증할 수 있습니다.

1 디버그 모드"특정 이상현상 강제"로 설정
2 강제할 이상현상 드롭다운에서 DT_Anomalies 테이블의 Row를 선택 (예: AN_039_Locker_Shrink)
3 플레이 시 UAnomalyManager가 확률 계산을 건너뛰고, 선택된 이상현상을 즉시 적용
4 디버그 로그와 화면 메시지로 적용 결과를 실시간 확인 — 상세 로그 · 화면 디버그 메시지 옵션 활용

스테이지 관리 시스템

UStageManager WorldSubsystem이 스테이지 로드·초기화·맵 전환을 책임지고, 판정 로직은 ATOIGameMode가 분리하여 담당합니다.

UStageManager (WorldSubsystem)

스테이지 로드/리셋, 문 역할 전환, 맵 전환을 "실행"하는 서브시스템입니다. FStageConfigData DataTable에서 스테이지별 이상현상 확률·연속 정상 제한 등을 읽고, 이상현상 선택·적용은 UAnomalyManager에 위임합니다.

UWorldSubsystem 상속 — 레벨 독립적 생명주기
스테이지 라이프사이클: ResetStage → ConfigureDoorRoles → DetermineAnomaly → StartStage
FStageConfigData: AnomalyChance, MaxConsecutiveNormal, GuardSpawnChance
판정은 ATOIGameMode.SubmitDecision()이 담당 — 역할 분리
GameInstance 상태 업데이트의 유일한 진입점 — 데이터 소유권 명확화
// Systems/StageManager.cpp — LoadStage void UStageManager::LoadStage( int32 LoopCount, int32 StageIndex, int32 MapID, bool bForceAbnormal) { // 1) 이전 스테이지 정리 ResetStage(); // 2) 맵 ID에 따라 복도 문 역할 전환 ConfigureDoorRolesForMap(MapID); // 3) 이상현상 선택/적용 → AnomalyManager const bool bHasAnomaly = DetermineAndApplyAnomaly( StageIndex, MapID, bForceAbnormal); // 4) GameInstance 상태 갱신 if (UTOIGameInstance* GI = ...) { GI->CurrentStageIndex = StageIndex; GI->CurrentMapID = MapID; } // 5) GameMode에 "스테이지 시작" 알림 if (ATOIGameMode* GM = ...) { GM->StartStage(StageIndex, bHasAnomaly); } }

FStageConfigData (DataTable Row)

DT_StageConfig DataTable의 Row 구조체로 스테이지별 동작 규칙을 정의합니다. Stage 0은 항상 정상(비교 기준), Stage 1~7은 이상/정상 확률·경비원 기믹 등이 적용됩니다.

Row Name: "MapN_StageM" — Map_ID와 StageIndex 조합으로 조회
이상현상 확률 80%, 연속 정상 2회 제한 → 3번째는 강제 이상
Map 2 전용 경비원 기믹: GuardSpawnChance 20%, 10초 딜레이
bAllowInterference로 스테이지별 자아 간섭 허용 제어
// TOIDataTypes.h — FStageConfigData USTRUCT(BlueprintType) struct FStageConfigData : public FTableRowBase { GENERATED_BODY() UPROPERTY(EditAnywhere) int32 StageIndex = 0; UPROPERTY(EditAnywhere) int32 Map_ID = 1; UPROPERTY(EditAnywhere) bool bAlwaysNormal = false; UPROPERTY(EditAnywhere) float AnomalyChance = 0.8f; UPROPERTY(EditAnywhere) int32 MaxConsecutiveNormal = 2; UPROPERTY(EditAnywhere) float GuardSpawnChance = 0.0f; UPROPERTY(EditAnywhere) float GuardSpawnDelay = 10.0f; UPROPERTY(EditAnywhere) bool bAllowInterference = true; };

ATOIGameMode (심판 역할)

GameMode는 "판단/분기"만 수행하고, 상태 변경(스테이지·맵 전환)은 StageManager에 위임합니다. 판정 결과는 델리게이트로 브로드캐스트되어 PlayerController/HUD가 연출에 반응합니다.

2단계 판정 구조: SubmitDecision() → 연출 보류 → OnJudgementDirectionComplete() → 전환
5종 델리게이트: OnDecisionJudged, OnStageClearEvent, OnStageFailEvent, OnMapTransition, OnGameComplete
즉사형 실패(HandleInstantDeath)는 판정 연출을 건너뛰고 바로 OnStageFail(true)
맵별 초기 스폰 포인트를 GameplayTag로 매핑 — 레벨 의존도 감소
// Core/TOIGameMode.cpp — SubmitDecision void ATOIGameMode::SubmitDecision( bool bPlayerThinksAbnormal, AActor* JudgingActor) { if (bIsGameComplete || bIsTransitioning || bPendingStageTransition) return; const bool bIsCorrect = (bPlayerThinksAbnormal == bIsCurrentStageAbnormal); // ★ 연출 완료 전까지 전환 "보류" bPendingStageTransition = true; bPendingTransitionResult = bIsCorrect; // 문/거울에 결과 전달 → 시퀀서 연출 NotifyJudgementResult(bIsCorrect, TargetActor); // PC/HUD 에게 브로드캐스트 OnDecisionJudged.Broadcast( bIsCorrect, TargetActor); } void ATOIGameMode::OnJudgementDirectionComplete() { if (!bPendingStageTransition) return; const bool bWasCorrect = bPendingTransitionResult; bPendingStageTransition = false; if (bWasCorrect) OnStageClear(); else OnStageFail(false); }

역할 분리 설계

GameMode(심판)는 판단만 하고, StageManager(실행자)가 환경 구성·상태 갱신을 수행합니다. PlayerController(연출)는 델리게이트를 바인딩해 페이드·사운드·텔레포트를 처리합니다.

GameMode — 판정 규칙·분기 판단만 수행, GI 상태를 직접 조작하지 않음
StageManager — LoadStage/ResetStage 실행, GI 상태 업데이트의 단일 진입점
PlayerController — 델리게이트 바인딩으로 연출(페이드, 텔레포트, 사운드) 처리
// 역할 분리 아키텍처 GameMode (심판) │ 판단: 정답/오답 → 다음 스테이지 │ 맵 전환 / 엔딩 / 루프 ▼ StageManager (실행자) │ LoadStage / TransitionToNextMap ├─► AnomalyManager (이상현상) ├─► GameInstance (상태 갱신) ├─► TOIDoor/Mirror (역할 전환) └─► GameMode.StartStage() PlayerController (연출) │ OnStageClearEvent → 클리어 연출 │ OnStageFailEvent → 루프 연출 │ OnMapTransition → 맵 전환 페이드 └ OnGameComplete → 엔딩

스테이지 라이프사이클

LoadStage() 호출 시 5단계의 순차 처리가 이루어집니다. 각 단계는 별도 시스템에 위임되어 단일 책임 원칙을 준수합니다.

ResetStage
RollbackAll
Door/Mirror 리셋
ConfigureDoor
MapID별 문 역할
Exit ↔ Entrance
DetermineAnomaly
AnomalyManager에
선택·적용 위임
GI 갱신
StageIndex
MapID 업데이트
StartStage
GameMode에
시작 알림

이벤트별 분기 테이블

GameMode가 판단한 결과에 따라 StageManager가 실행하는 흐름입니다. Map 1의 Stage 7 클리어 시 Map 2로 전환되며, Map 2의 Stage 7 클리어 시 엔딩에 진입합니다.

이벤트 GameMode (판단) StageManager (실행)
게임 시작 StartPlay() LoadStage(0, 0, MapID, false)
정답 (Stage 0~6) OnStageClear()OnStageClearEvent LoadStage(Loop, NextStage, MapID)
정답 (Stage 7, Map 1) OnStageClear()OnMapTransition TransitionToNextMap(2)
정답 (Stage 7, Map 2) OnStageClear()OnGameComplete 엔딩 진입
오답 OnStageFail(false)OnStageFailEvent LoadStage(NewLoop, 0, MapID)
즉사 HandleInstantDeath()OnStageFail(true) LoadStage(NewLoop, 0, MapID)

문 역할 전환 (ConfigureDoorRoles)

복도 문은 GameplayTag(CorridorTag)로 식별되며, 맵 ID에 따라 Exit(판별용)/Entrance(일반) 역할이 런타임에 전환됩니다.

Map 1: Corridor2 = Exit(판별용), Corridor1 = Entrance(일반)
Map 2: Corridor1 = Exit(판별용), Corridor2 = Entrance(일반)
CorridorTag가 없는 문은 역할 변경 대상에서 제외
// Systems/StageManager.cpp void UStageManager::ConfigureDoorRolesForMap( int32 MapID) { for (ATOIDoor* Door : TActorRange<...>(...)) { if (!Door->CorridorTag.IsValid()) continue; if (MapID == 1) { // Corridor2 → Exit (판별용) // Corridor1 → Entrance (일반) } else if (MapID == 2) { // Corridor1 → Exit (판별용) // Corridor2 → Entrance (일반) } Door->SetDoorType(NewType); } }

에디터 화면 · 디버그 도구

스테이지 설정 데이터는 DT_StageConfig DataTable과 Google Sheets DB로 관리됩니다. 디버깅 시에는 TOI 디버그 설정에서 강제 맵 ID를 지정해 특정 맵의 스테이지를 즉시 테스트할 수 있습니다.

스테이지 디버그 워크플로우

에디터의 "프로젝트 설정 → TOI 디버그" 메뉴의 스테이지 디버그 카테고리에서 맵 전환 없이 특정 맵의 스테이지를 즉시 테스트할 수 있습니다.

1 강제 맵 ID1 또는 2로 설정 (0 = 사용 안 함)
2 플레이 시 StageManagerDT_StageConfig에서 해당 맵의 스테이지 설정을 로드
3 화면 디버그 메시지로 현재 스테이지·맵 ID·이상현상 여부·루프 카운트를 실시간 확인
4 이상현상 디버그와 조합해 특정 맵 + 특정 이상현상 동시 테스트 가능

데이터 파이프라인

UTOIGameInstance가 보유한 8개 DataTable에서 Row Struct를 읽어 WorldSubsystem → ActorComponent로 전달되는 흐름입니다. Google Sheets에서 기획 DB를 관리하고 CSV 임포트로 에디터에 반영합니다.

데이터 흐름 5단계

UTOIGameInstance가 레벨 전환에도 유지되는 DataTable 참조 허브 역할을 하며, WorldSubsystem이 GetGameInstance()로 접근해 Row를 조회·필터링하고, ActorComponent에 적용합니다. 결과는 Delegate로 브로드캐스트되어 연출 시스템이 느슨하게 반응합니다.

DataTable
Row Struct 정의
에디터에서 편집
GameInstance
DataTable 참조 허브
경험 기록 보관
WorldSubsystem
로직 실행
Filter · Apply
ActorComponent
월드 반영
Apply · Rollback
Delegate
결과 통보
이벤트 발행

UTOIGameInstance (데이터 허브)

레벨 전환에도 유지되는 GameInstance에서 8개 DataTable 참조와 루프/스테이지 상태, 이상현상 경험 기록을 중앙 보관합니다. DataTable 에셋은 C++에서 로드하지 않고 BP_TOIGameInstance에서 할당합니다.

8개 DataTable 참조EditDefaultsOnly로 선언 — BP에서 에셋 할당
CurrentLoopCount, CurrentStageIndex, ConsecutiveNormalCount로 루프 진행 상태 추적
FAnomalyExperienceRecord 배열 + 정답/오답 TSet<FName> 캐시로 빠른 조회
Subsystem은 GetGameInstance()로만 접근 — 데이터 소유권을 GI에 집중
// Core/TOIGameInstance.h — DataTable 참조 UCLASS() class UTOIGameInstance : public UGameInstance { GENERATED_BODY() // ── DataTable 참조 (BP에서 할당) ── UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> AnomalyDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> StageConfigDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> DialogDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> EnvironmentDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> InteractableDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> InterferenceRuleDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> LocalizationDataTable; UPROPERTY(EditDefaultsOnly, Category="Data") TObjectPtr<UDataTable> SoundListDataTable; // ── Getter (Subsystem용) ── UDataTable* GetAnomalyDataTable() const; UDataTable* GetStageConfigDataTable() const; // ... +6 Getter // ── Runtime State ── int32 CurrentStageIndex = 0; int32 CurrentMapID = 1; int32 ConsecutiveNormalCount = 0; int32 CurrentLoopCount = 0; // ── 경험 기록 (정답/오답 캐시) ── TArray<FAnomalyExperienceRecord> AnomalyExperienceRecords; TSet<FName> CorrectlyExperiencedAnomalies; TSet<FName> WronglyExperiencedAnomalies; };

Subsystem의 데이터 로딩

UAnomalyManager는 Initialize 시점에 GameInstance에서 DataTable 참조를 가져와 내부 포인터에 캐싱합니다. 이후 SelectAnomalyForStage() 호출 시 캐싱된 테이블에서 Row를 조회합니다.

LoadDataTables() — GI에서 DT_Anomalies, DT_StageConfig 참조 캐싱
FilterAvailableAnomalies(MapID) — bEnabled, Map_ID로 필터 후 후보 목록 생성
GetStageConfig(StageIndex, MapID) — Row Name "MapN_StageM" 패턴으로 조회
// Systems/AnomalyManager.cpp — 데이터 로딩 void UAnomalyManager::LoadDataTables() { UTOIGameInstance* GI = Cast<UTOIGameInstance>( GetWorld()->GetGameInstance()); if (!GI) return; AnomalyTable = GI->GetAnomalyDataTable(); StageConfigTable = GI->GetStageConfigDataTable(); } const FStageConfigData* UAnomalyManager::GetStageConfig( int32 StageIndex, int32 MapID) { // Row Name: "Map{MapID}_Stage{Index}" FName RowName = *FString::Printf( TEXT("Map%d_Stage%d"), MapID, StageIndex); return StageConfigTable-> FindRow<FStageConfigData>( RowName, TEXT("GetStageConfig")); }

Google Sheets → UE 워크플로우

기획 팀이 Google Sheets에서 DB를 편집하고, CSV로 내보내 UE 에디터에서 DataTable Reimport로 반영합니다. Row Struct의 필드명과 CSV 열 헤더가 1:1 대응하도록 설계되어 있습니다.

Google Sheets
기획 DB 편집
밸런싱 조정
CSV Export
시트별 CSV
헤더 = 필드명
UE Reimport
DataTable에
CSV 임포트
BP 할당
GameInstance에
에셋 참조
런타임
Subsystem이
Row 조회

8개 DataTable 총괄

TOIDataTypes.h에 Row Struct가 정의되어 있으며, 각 DataTable은 UTOIGameInstance에서 EditDefaultsOnly 프로퍼티로 참조됩니다. 핵심 시스템(이상현상·스테이지·간섭)은 런타임에 실시간 조회되고, 연출/환경 DataTable은 시스템 구현에 맞춰 순차적으로 연동됩니다.

핵심 시스템 DataTable

DT_Anomalies

Row: FAnomalyData

이상현상 풀 정의 — 타입(EAnomalyType 17종), 맵 ID, FGameplayTag 타겟, 발생 확률, 스케일/시간/강도 파라미터, 즉사 여부, 캠코더 전용 플래그

소비: UAnomalyManager

DT_StageConfig

Row: FStageConfigData

스테이지별 규칙 — 이상현상 확률(80%), 연속 정상 제한(2회), 경비원 스폰 확률/딜레이, 자아 간섭 허용 여부

소비: UAnomalyManager · UStageManager

DT_InterferenceRules

Row: FInterferenceRuleData

자아 간섭 규칙 — RuleCategory(GaugeIncrease/EgoActivation), 우선순위, 조건 지표(EInterferenceCondition 12종), 게이지 변동량, 맵 간 지속성

소비: 자아 간섭 서브시스템 (구현 예정)

DT_Interactables

Row: FInteractableData

상호작용 오브젝트 정의 — 이름, 타입(EInteractType: Open/Inspect/Pickup/Sit), UI 텍스트 키(로컬라이제이션 연동)

소비: UTOIInteractionComponent

연출 · 환경 · 공통 DataTable

DT_Dialog

Row: FDialogData

대사 데이터 — 원본 문자열, 화자 ID(EDialogSpeaker: System/Ego_Avoid/Ego_Meta/Doctor/NPC 등 8종), 표시 지속 시간

소비: 자아 간섭 · NPC 대화 (구현 예정)

DT_SoundList

Row: FSoundListData

사운드 에셋 관리 — 타입(ESoundType: BGM/SFX/Ambience/Voice/UI), FSoftObjectPath 경로, 볼륨, 루프 여부, 동시 재생 그룹

소비: 사운드 매니저 (구현 예정)

DT_Environment

Row: FEnvironmentData

환경 연출 설정 — 안개 밀도, 조명 강도 배율, Post Process 그레인, 환경 사운드 키

소비: 환경 연출 시스템 (구현 예정)

DT_Localization

Row: FLocalizationData

UI 텍스트 로컬라이제이션 — 원본 문자열, 카테고리(ELocalizationCategory: Common/MainMenu/HUD/Interaction 등 8종), 주석

소비: UI 시스템 (구현 예정)

TOIDataTypes.h — Row Struct 정의

모든 DataTable Row Struct는 TOIDataTypes.h 단일 파일에 정의됩니다. FAnomalyDataFStageConfigData는 각각 이상현상/스테이지 섹션에서 상세히 다루었으므로, 여기서는 나머지 핵심 구조체를 소개합니다.

FInterferenceRuleData

자아 간섭 규칙 Row — GaugeIncrease(수치 증가)와 EgoActivation(자아 개입 유도) 두 카테고리로 나뉘며, 조건 지표 12종과 우선순위로 어떤 자아가 개입할지 결정합니다.

RuleCategory — GaugeIncrease(수치 증가) vs EgoActivation(자아 개입 유도)
EInterferenceCondition 12종 — LoopCount, StagnationTime, StageLevel, InteractionCanceled 등
bPersistAcrossMaps — 메타 자아 조건의 맵 간 수치 유지
TargetObjectTag — 특정 오브젝트 상호작용 횟수 조건용 GameplayTag
// TOIDataTypes.h — FInterferenceRuleData USTRUCT(BlueprintType) struct FInterferenceRuleData : public FTableRowBase { GENERATED_BODY() UPROPERTY(EditAnywhere) bool bEnabled = true; UPROPERTY(EditAnywhere) FName RuleCategory; // "GaugeIncrease" 또는 "EgoActivation" UPROPERTY(EditAnywhere) int32 Priority = 0; // 메타=1, 회피=2, 강박=3 UPROPERTY(EditAnywhere) EInterferenceType Effect_Type; UPROPERTY(EditAnywhere) EInterferenceCondition Condition_Metric; UPROPERTY(EditAnywhere) float Condition_Value = 0.0f; UPROPERTY(EditAnywhere) int32 GaugeChange = 0; UPROPERTY(EditAnywhere) bool bResetCountOnTrigger = true; UPROPERTY(EditAnywhere) bool bPersistAcrossMaps = false; UPROPERTY(EditAnywhere) bool bAllowImmediateOnMap2 = false; UPROPERTY(EditAnywhere) FGameplayTag TargetObjectTag; };

FInteractableData

상호작용 오브젝트 Row — 문/거울/아이템 등 상호작용 가능 오브젝트의 이름, 타입, UI 텍스트 키를 정의합니다. InteractTextKeyDT_Localization과 연동되어 다국어 지원이 가능합니다.

EInteractType — Open(문), Inspect(거울), Pickup(아이템), Sit(의자) 4종
InteractTextKey — UI_Interact_Open, UI_Interact_Check 등 로컬라이제이션 키
Row Name(InteractID)으로 Door_Exit, Obj_Mirror, Item_Flashlight 등 식별
// TOIDataTypes.h — FInteractableData USTRUCT(BlueprintType) struct FInteractableData : public FTableRowBase { GENERATED_BODY() UPROPERTY(EditAnywhere) FString Name; // "뒷문", "거울", "손전등" 등 UPROPERTY(EditAnywhere) EInteractType Type; // Open, Inspect, Pickup, Sit UPROPERTY(EditAnywhere) FName InteractTextKey; // "UI_Interact_Open" 등 UPROPERTY(EditAnywhere) FString Description; };

FDialogData · FSoundListData · FEnvironmentData

연출/환경 관련 Row Struct입니다. 대사는 화자별 자아 구분, 사운드는 FSoftObjectPath 비동기 로딩, 환경은 스테이지별 분위기 조절을 지원합니다.

FDialogData — SpeakerID(EDialogSpeaker 8종)로 자아·NPC·시스템 화자 구분
FSoundListDataFSoftObjectPath + Concurrency 그룹으로 사운드 에셋 관리
FEnvironmentData — FogDensity, LightIntensityMult, PostProcess_Grain으로 공포 분위기 조절
// TOIDataTypes.h — 연출 Row Structs struct FDialogData : FTableRowBase { FString SourceString; EDialogSpeaker SpeakerID; float Duration = 3.0f; }; struct FSoundListData : FTableRowBase { ESoundType Type; // BGM/SFX/Voice FSoftObjectPath SoundPath; float Volume = 1.0f; bool Loop = false; FName Concurrency; }; struct FEnvironmentData : FTableRowBase { float FogDensity = 0.0f; float LightIntensityMult = 1.0f; float PostProcess_Grain = 0.0f; FName AmbienceSound; }; struct FLocalizationData : FTableRowBase { FString SourceString; ELocalizationCategory Category; FString Comment; };

에디터 화면 · Google Sheets DB

각 DataTable은 UE 에디터에서 직접 편집하거나, Google Sheets DB에서 관리 후 CSV 임포트로 반영합니다. 아래는 리소스가 남아 있는 DataTable의 에디터 화면과 Google Sheets DB 화면입니다.

DT_Anomalies — 이상현상 데이터

DT_StageConfig — 스테이지 설정

DT_InterferenceRules — 자아 간섭 규칙

DT_Interactables — 상호작용 오브젝트

리소스 미첨부 DataTable (2026.03 개발 종료 기준)

아래 DataTable들은 Row Struct 정의(TOIDataTypes.h)와 GameInstance 참조까지 끝냈습니다. 다만 해당 시스템을 구현하기 전에 개발이 종료되어 에디터 화면과 Google Sheets DB 리소스는 남기지 못했습니다.

DT_Dialog (FDialogData) — 자아 간섭 대사 · NPC 대화 시스템과 함께 채울 계획이었던 테이블
DT_SoundList (FSoundListData) — 동적 BGM/SFX 재생 시스템과 함께 사운드 에셋을 등록할 예정이었던 테이블
DT_Environment (FEnvironmentData) — 스테이지별 환경 연출(안개 · 조명 · Post Process) 시스템과 함께 채울 계획이었던 테이블
DT_Localization (FLocalizationData) — UI 시스템을 마친 뒤 다국어 텍스트를 넣을 계획이었던 테이블

자아 간섭 시스템

루프가 반복될수록 플레이어의 플레이를 방해하는 간섭 시스템입니다. 메타·회피·강박 세 유형의 자아가 플레이 패턴을 추적하여 개입하며, 데이터 스키마(FInterferenceRuleData, EInterferenceCondition 12종)와 DT_InterferenceRules 테이블 구조가 설계 완료된 상태입니다.

데이터 스키마 · Row Struct · Enum 정의 완료 DT_InterferenceRules 테이블 구조 완료 StageConfig에 bAllowInterference 제어 필드 연동 게이지 누적·발동 서브시스템 구현 예정 자아별 연출 트리거·대사 시스템 구현 예정

간섭 게이지 시스템

플레이어의 루프 횟수, 스테이지 이동, 특정 오브젝트 상호작용, 정체 시간 등을 누적하여 간섭 게이지가 100 이상 도달하면 다음 스테이지에서 간섭이 발동합니다.

Loop 2회마다 +30 · Stage 이동 3회마다 +30 · 문/거울 앞 5초 정체 +30
발동 시 게이지 50% 감소 (완전 초기화 X) — 반복 간섭 가능
조건 충족 시에도 현재 Stage가 아닌 다음 Stage 진입 3초 후 발동
// 간섭 발동 우선순위 Priority 1메타 자아 · Stage 6+ 진입 · 루프 실패 누적 5회+ · 자아 간섭 5회+ 경험 Priority 2회피 자아 · 출구/거울 상호작용 취소 · 출구/거울 3m 이내 3초+ 체류 · Stage 진입~상호작용 10초 이하 Priority 3강박 자아 · 메타·회피 조건 미충족 시 · 캠코더 사용 누적 4회+

데이터 스키마 (구현 완료)

TOIDataTypes.h에 Row Struct와 Enum이 정의되어 있으며, UTOIGameInstance에서 InterferenceRuleDataTable을 참조합니다. FStageConfigDatabAllowInterference 플래그로 스테이지별 허용 여부를 제어하고, TOIPlayerControllerInvertedMappingContextSetInputContext()가 준비되어 강박 간섭(조작 반전)의 입력 연동 훅이 마련된 상태입니다.

FInterferenceRuleData — RuleCategory(GaugeIncrease / EgoActivation), Priority, Condition_Metric, GaugeChange
EInterferenceCondition 12종 — LoopCount, StagnationTime, StageLevel, LoopFailCount, CamcorderUseCount, ObjectInteractCount 등
EDialogSpeaker / FDialogData — Ego_Avoid, Ego_Meta, Ego_Compulsion 자아별 화자·대사 스키마
SetInputContext(bInvert) — 강박 간섭의 조작 반전을 위한 입력 컨텍스트 전환 훅 (호출부 미연결)
// TOIDataTypes.h UENUM(BlueprintType) enum class EInterferenceCondition { LoopCount, StagnationTime, StageLevel, LoopFailCount, CamcorderUseCount, ObjectInteractCount, // ... 총 12종 }; USTRUCT(BlueprintType) struct FInterferenceRuleData : public FTableRowBase { GENERATED_BODY() UPROPERTY(EditAnywhere) FName RuleCategory; // GaugeIncrease | EgoActivation UPROPERTY(EditAnywhere) int32 Priority; UPROPERTY(EditAnywhere) EInterferenceCondition Condition_Metric; UPROPERTY(EditAnywhere) float GaugeChange; };

메타 간섭 Priority 1

ESC 강탈 연출(강제 ESC → 깨진 UI), 자막 UI 깨짐, 스팀 업적으로 말 걸기, Windows 사용자 이름/스팀 닉네임 호출, 가짜 OBS·메모장 실행, 실제 PC 시간 읊기

발동: Stage 6+ 진입 · 루프 실패 5회+ · 자아 간섭 5회+ 경험

회피 간섭 Priority 2

판단 직전 혼란 자막 3종 ("이거 맞는 판단일까?", "이상한 건 딱히 없어 보이는데", "아까 그거 다르지 않았나?"), NPC '안경녀' 근처 통과 시 자아 발언

발동: 출구/거울 상호작용 취소 · 3m 이내 3초+ 체류 · 진입~상호작용 10초 이하

강박 간섭 Priority 3

가짜 종료 팝업 + 자아 반응, 이동 조작 반전(2초, SetInputContext 연동), 캠코더 강제 사용 + 노이즈/프레임 드랍

발동: 메타·회피 조건 미충족 시 · 캠코더 사용 누적 4회+
간섭 종류별 연출(ESC 강탈, 메모장 실행, 조작 반전 등)은 서브시스템과 함께 에디터에서 시퀀스·위젯·사운드를 세팅해야 하는 부분이라 종료 시점까지 남아 있었습니다.

2026.03 개발 종료 시점 정리

스테이지 시스템, 이상현상 프레임워크, 상호작용, Map 전환, UI 구조, 플레이어 기본 로직 같은 핵심 시스템은 구현을 마쳤습니다. 2026년 3월에 프로젝트를 종료하면서 손대지 못한 범위는 아래와 같고, 프로그래밍 구현에디터 콘텐츠 작업으로 나뉩니다. 특히 개별 이상현상 세팅, 비주얼 연출, 플레이어 모션처럼 에디터에서 직접 만들어야 하는 콘텐츠가 많이 남은 상태였습니다.

프로그래밍 구현

자아 간섭 서브시스템

데이터 스키마(FInterferenceRuleData, EInterferenceCondition 12종) 완료 → WorldSubsystem으로 게이지 누적·임계치 판정·자아 우선순위 평가·발동 로직 구현, 메타·회피·강박 각 자아별 연출 트리거 연결

상태: 데이터 스키마 완료 · 런타임 로직 미구현

캠코더 시스템

Map 2 전용 이중 시각 레이어(육안/캠코더), F키 토글, Stage당 횟수·시간 제한, bCamcorder 이상현상 연동, 캠코더 전용 텍스처·텍스트 렌더링

상태: 태그·데이터 필드만 존재 · 액터·로직 미구현

NPC 시스템

학생 NPC 클래스(표정·자세 이상현상 프리셋 연동), 경비원 NPC AI(20% 확률 스폰, 복도 순찰, 감지→손전등 OFF 판정→즉사 기믹)

상태: TOI 전용 NPC 클래스 미구현

대사 · 다이얼로그 시스템

FDialogData/EDialogSpeaker 스키마 완료 → 자아 간섭 대사, NPC 대화, 플레이어 독백 UI 재생 로직 및 자막 위젯 구현

상태: 데이터 스키마 완료 · 재생 로직 미구현

환경 연출 매니저

FEnvironmentData 스키마(FogDensity, LightIntensityMult, PostProcess_Grain, AmbienceSound) 완료 → 런타임 안개·조명·Post Process 동적 제어 및 DT_SoundList 기반 BGM/SFX 매니저

상태: 데이터 스키마 완료 · 런타임 매니저 미구현

에디터 · 콘텐츠 작업

C++ 프레임워크는 올라갔지만, 에디터(블루프린트·레벨·데이터 테이블)에서 만들어야 하는 콘텐츠는 종료 시점까지 많이 남아 있었습니다.

개별 이상현상 구현 (71종 프리셋)

C++ 이상현상 프레임워크(AnomalyManager·AnomalyTargetComponent)는 완성 — 오브젝트 Transform/Scale/Material/Visibility/Sound/Spawn 등 11종 적용 타입 지원. 종료 시점 기준 C++ 기믹은 CeilingLowTargetComponent, GradualScaleTargetComponent 2종까지 구현했고, 71종 프리셋을 에디터에서 하나씩 세팅하는 작업은 남아 있었습니다. 나머지는 블루프린트 기믹이나 DT_Anomalies 데이터로 개별 세팅해야 하는 몫이었습니다.

플레이어 모션 · 애니메이션

기본 이동(Idle/Walk/Run), 카메라 셰이크, 사망 몽타주는 구현 완료. 캠코더 들기/내리기 모션, 손전등 조작 모션, 거울 인터랙션 연출 애니메이션, 복도 리스폰 시퀀스 애니메이션, 즉사 리액션 같은 상황별 모션은 만들지 못한 채 종료했습니다.

레벨 디자인 · 오브젝트 배치

Map 1·2 교실 내부에 ATOIDoor, ATOIMirror, ATOISpawnPoint 배치, AnomalyTargetComponent를 이상현상 대상 오브젝트에 부착, NPC 배치 위치·책상 규격·창문·안내판 등 상세 오브젝트 레벨 세팅, 복도 구조 및 계단실 투명벽 설정

시퀀스 · 비주얼 연출

문/거울 판정 결과 레벨 시퀀스, 맵 전환 시퀀스, 경비원 NPC 등장 연출, 자아 간섭 개별 연출(ESC 강탈 UI, 가짜 메모장/OBS 창, 화면 왜곡), 캠코더 시각 필터(노이즈·그레인), 사물함·마지막 좌석 공포 연출

데이터 테이블 · 블루프린트

DT_Anomalies(71종 프리셋 데이터 입력), DT_StageConfig(스테이지별 규칙 수치), DT_InterferenceRules(자아 간섭 규칙), DT_Dialogs(대사), DT_SoundList(사운드), DT_Environment(환경) 실제 데이터 채우기 및 WBP_ 위젯 블루프린트 디자인·바인딩

사운드 · 오디오 리소스

이벤트 기반 SFX(판정 성공/실패, 맵 전환, 손전등)는 훅 완료. 스테이지별 동적 BGM, 경비원 속삭임 사운드, 이상현상별 개별 효과음, 자아 간섭 연출 사운드, 환경 앰비언스 오디오는 리소스 제작과 연결 전에 종료했습니다.

팀 협업 · 형상관리

8인 팀에서 PM 역할을 겸했습니다. Azure DevOps로 형상관리 환경을 세팅하고, 아트 파트 팀원들이 쓸 온보딩 가이드를 직접 작성해서 배포했습니다.

형상관리 — Azure DevOps

형상관리는 Azure DevOps Repos로 운영했습니다. 팀원 전원이 main 브랜치에 커밋했고, 프로그래밍·아트·기획 파트가 같은 이력을 봤습니다.

Private Repository — 팀 외부 접근 불가 (스크린샷으로 커밋 내역 첨부)
팀원별 커밋 컨벤션 운영 — 이름-작업내용 형식으로 커밋 추적 용이
프로그래밍 2인 · 아트 4인 · 기획 2인 — 전원 Azure DevOps 환경에서 협업

PM 역할 · 온보딩 가이드

형상관리가 처음인 아트 파트 팀원들이 막히지 않게 온보딩 가이드를 직접 써서 배포했습니다. Azure DevOps 사용법, 에셋 워크플로우, 아트 작업 가이드까지 세 종류를 만들어 질문이 반복되는 구간을 문서로 대체했습니다.

Onboarding Guide — Azure DevOps 계정 설정, Git 기본 사용법, 프로젝트 클론·커밋·푸시 절차
Asset Workflow Guide — UE5 에셋 네이밍 규칙, 폴더 구조, 아트 에셋 임포트·커밋 워크플로우
Art Guide — 아트 파트 전용 작업 가이드, 텍스처·머티리얼·모델링 컨벤션