WORM KNIGHT 기술문서
9개 기술 그룹, 27개 구현 주제. 기존 코드 발췌·자료 확인 기록을 담았습니다. 이 페이지 개편에서 Unity 원문 및 런타임을 새로 검증한 결과로 해석하지 않습니다.
공용 계약과 코어 상태
몬스터·레벨·세그먼트·UI가 서로의 내부 구현이 아니라 작은 전달 데이터와 단일 코어 진입점으로 연결됩니다.
몬스터는 코어를 몰라도 보상을 보냅니다.
몬스터 측은 RewardData만 만들어 제출하고, 누적·배율·HUD 갱신은 CoreStatProvider가 책임집니다. 팀 경계를 타입 하나로 고정한 구조입니다.
구현 흐름
- 경험치·골드·다이아와 출처 위치를 값 타입으로 전달합니다.
- IsValid에서 의미 없는 지급을 입구 전에 차단합니다.
- 보상 소비 규칙과 UI 알림은 코어 한곳에 남깁니다.
선택과 남은 책임
작은 DTO와 단일 제출 경로
계약 필드가 바뀌면 생산자와 소비자를 함께 버전 관리해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Shared/Contracts/RewardData.cs · L6–17
[System.Serializable]
public struct RewardData
{
public int Experience;
public int Gold;
public int Diamond;
public int EnemyId;
public Vector3 Position;
public bool IsValid =>
Experience > 0 || Gold > 0 || Diamond > 0;
}선택 결과는 한 번의 ApplyGrowth로 합류합니다.
공통 강화, 세그먼트 추가, 특정 세그먼트 강화가 같은 GrowthStatData로 들어오되 적용 책임은 코어에서 분기합니다.
구현 흐름
- 데미지·공속·회전·충돌·재결합 보너스를 누적합니다.
- 세그먼트 추가와 강화 요청은 별도 내부 함수에 위임합니다.
- 적용 후 CurrentStats를 한 번 발행해 HUD와 런타임을 동기화합니다.
선택과 남은 책임
코어를 런타임 능력치의 단일 소스로 유지
CoreStatProvider가 비대해지지 않도록 도메인별 내부 함수와 계약 경계를 계속 관리해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Core/CoreStatProvider.cs · L145–170
public bool ApplyGrowth(GrowthStatData growth)
{
if (!growth.HasAnyValue || !CanApplyGrowth(growth))
return false;
ApplyLevelDeltaIfNeeded(growth.LevelDelta);
DamageMultiplier = Mathf.Max(0f,
DamageMultiplier + growth.DamageMultiplierBonus);
AttackSpeedMultiplier = Mathf.Max(0.01f,
AttackSpeedMultiplier + growth.AttackSpeedMultiplierBonus);
TurnSpeedBonus += growth.TurnSpeedBonus;
CollisionForceBonus += growth.CollisionForceBonus;
RejoinRangeBonus = Mathf.Max(0f,
RejoinRangeBonus + growth.RejoinRangeBonus);
ApplySegmentAdd(growth);
ApplySegmentUpgrade(growth.SegmentUpgrade);
StatsChanged?.Invoke(CurrentStats);
return true;
}몬스터를 제외한 주요 시스템과 콘텐츠를 구현했습니다.
컨보이와 코어뿐 아니라 레벨·세그먼트 등 주요 콘텐츠 구현과 최종 통합을 담당했습니다. 역할표의 담당 구분과 실제 구현 기여는 구별해 설명합니다.
구현 흐름
- 직접 설계·구현: 컨보이 이동, 절단·재결합, 넥서스/씬 구조, 메타와 공용 계약.
- 통합 책임: 보상·성장·세그먼트·HUD가 코어 상태와 만나는 경로.
- 협업 구현: 몬스터. 레벨·세그먼트·주요 콘텐츠 구현은 개인 담당 범위에 포함합니다.
선택과 남은 책임
소유권과 통합 책임을 명시
팀 전체 결과를 개인 성과처럼 요약하지 않고 기능마다 기여 유형을 밝혀야 합니다.
코드 근거 · 기존 발췌 기록
TeamDocs/03_관리_역할분담표.md + Docs/99_코덱스공통지침.md · role evidence
Core ownership
├─ Convoy movement / control modes
├─ Path following / segment connection
├─ Self collision / cut / detached physics / rejoin
├─ Nexus / StageScene / camera / public API
└─ Final integration and execution contract
Team collaboration
├─ Monster implementation
├─ Level and card system
└─ Individual segment contents컨보이 경로와 자동 궤도
프레임별 이동, 거리 기반 경로, 소켓 정렬, 몬스터 충돌 보정, 자동 궤도를 하나의 이동 파이프라인에서 조정합니다.
입력·상태·충돌을 한 프레임의 순서로 고정했습니다.
자동 궤도 취소, 모드별 조향, 몬스터 넉백, 슬로우, 지면 스냅, 장애물 보정을 순서대로 적용한 뒤 최종 위치를 기록합니다.
구현 흐름
- 수동 입력은 자동 궤도보다 우선합니다.
- 루트 이동과 시각적 공중 높이를 분리해 경로 데이터가 흔들리지 않게 합니다.
- 몬스터 측은 이동을 직접 쓰지 않고 보정 요청만 전달합니다.
선택과 남은 책임
변환 기반 이동을 한곳에서 최종 확정
파이프라인 순서가 규칙이 되므로 새 효과를 끼울 위치를 신중히 정해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Convoy/ConvoyController.cs · L235–268
if (IsAutoOrbitActive && HasAutoOrbitCancelInput(input))
CancelAutoOrbit();
ApplyControl(input, deltaTime);
Vector3 currentPosition = transform.position;
float slowMultiplier =
MonsterInteractionApi.GetConvoySpeedMultiplier(currentPosition);
Vector3 forwardDisplacement = transform.forward
* (currentForwardSpeed * slowMultiplier * deltaTime);
Vector3 knockbackDisplacement =
ConsumeKnockbackDisplacement(deltaTime, out float visualHeight);
Vector3 desiredPosition = SnapHeadToGround(
currentPosition + forwardDisplacement + knockbackDisplacement);
desiredPosition = MonsterInteractionApi.ResolveConvoyPosition(
currentPosition, desiredPosition, HeadMonsterBlockRadius,
GetHeadObstacleCorrectionDistance(deltaTime));
transform.position = desiredPosition;위치가 튀어도 꼬리는 순간이동하지 않습니다.
거리 기반으로 머리 위치를 샘플링하고, 비정상적으로 큰 간격은 한 점으로 넣지 않고 제한된 직선 샘플로 재구성합니다.
구현 흐름
- 프레임 속도가 달라도 최소 샘플 거리가 기준입니다.
- 리셋·넉백·외부 보정으로 큰 간격이 생겨도 중간 경로를 복원합니다.
- 오래된 경로는 마지막 꼬리에 필요한 거리 뒤에서 정리합니다.
선택과 남은 책임
이상치를 버리지 않고 연속 경로로 복원
복원 샘플 상한이 너무 작으면 각지고, 너무 크면 순간 비용이 늘어납니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Convoy/ConvoyController.Path.cs · L49–76
private void AddPathSample(
Vector3 last, Vector3 anchorPosition, float sampleDistance)
{
if (!ShouldRepairAbnormalPathSample(sampleDistance))
{
path.Add(anchorPosition);
return;
}
AddRepairedStraightPathSamples(last, anchorPosition, sampleDistance);
}
private void AddRepairedStraightPathSamples(
Vector3 last, Vector3 anchorPosition, float sampleDistance)
{
float step = Mathf.Max(MinPathSampleDistance, RepairedPathSampleStep);
int count = Mathf.Clamp(Mathf.CeilToInt(sampleDistance / step),
1, Mathf.Max(1, MaxRepairedPathSamples));
for (int i = 1; i <= count; i++)
path.Add(Vector3.Lerp(last, anchorPosition, (float)i / count));
}세그먼트가 늘면 궤도도 스스로 넓어집니다.
고정 반경은 긴 컨보이가 자기 몸을 감는 문제를 만듭니다. 실제 체인 길이, 넥서스 Collider 반경, 여유 거리를 함께 사용해 목표 원을 다시 계산합니다.
구현 흐름
- 원 둘레가 체인 길이를 수용하도록 L / 2π를 사용합니다.
- 넥서스 충돌체보다 작은 궤도가 나오지 않게 별도 하한을 둡니다.
- 길이 변화가 임계값을 넘으면 Approach 상태로 돌아가 새 궤도에 진입합니다.
선택과 남은 책임
실제 길이에 반응하는 기하학적 반경
세그먼트 배치가 불규칙할 때 원둘레 근사는 보수적인 여유값이 필요합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Convoy/ConvoyController.AutoOrbit.cs · L176–184
private float CalculateAutoOrbitRadius(Transform center)
{
float spacing = Mathf.Max(0.1f, SegmentSpacing);
float chainLength = segments.Count > 0
? GetSegmentDistanceBehindHead(segments.Count - 1)
: 0f;
float bufferedLength = chainLength
+ spacing * (1 + Mathf.Max(0, AutoOrbitExtraSegmentBuffer));
float tailSafeRadius = bufferedLength / (Mathf.PI * 2f);
float nexusSafeRadius = GetNexusHorizontalRadius(center)
+ Mathf.Max(0f, AutoOrbitNexusClearance);
float baseRadius = Mathf.Max(
AutoOrbitMinimumRadius, tailSafeRadius, nexusSafeRadius);
return baseRadius + Mathf.Max(0f, AutoOrbitComfortRadiusBonus);
}절단·물리·재결합
붙어 있을 때는 안정적인 경로 체인, 잘린 뒤에는 물리 체인, 회수하면 다시 런타임 체인으로 전환합니다.
충돌한 지점 뒤의 전력만 분리합니다.
머리와 부착 세그먼트의 평면 거리를 검사하되 스타터 구간은 제외합니다. 충돌 인덱스부터 끝까지 별도 그룹으로 옮깁니다.
구현 흐름
- Y축을 제거한 평면 거리로 지형 높이 차의 오판을 줄입니다.
- 분리 대상만 RemoveRange로 부착 리스트에서 제거합니다.
- 세그먼트 수 변경 이벤트로 자동 궤도와 HUD가 새 길이를 알게 합니다.
선택과 남은 책임
손실을 리스트 삭제가 아닌 필드 상태 전환으로 표현
부착 목록, 지면 검사, 런타임 등록을 같은 인덱스 범위로 정리해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Convoy/ConvoyController.Tail.cs · L31–84
Vector3 offset = segment.position - headPosition;
offset.y = 0f;
if (offset.sqrMagnitude <= radiusSqr)
{
CutTailFromIndex(i, headPosition);
return true;
}
// CutTailFromIndex
index = Mathf.Max(index, GetFirstDetachableSegmentIndex());
DetachedTailGroup group = CreateDetachedTailGroup(
segments[index].position, segments[index].rotation);
int cutCount = segments.Count - index;
for (int i = index; i < segments.Count; i++)
{
Transform segment = segments[i];
segment.SetParent(group.Root, true);
PrepareDetachedSegmentCollider(segment);
group.Segments.Add(segment);
}
segments.RemoveRange(index, cutCount);
RegisterDetachedTailGroup(group);
ApplyTailBurst(group, burstCenter);각 조각은 자유롭지만 체인 자체는 유지됩니다.
분리 세그먼트에 Rigidbody를 붙이고 앞 조각과 ConfigurableJoint로 연결합니다. 이동 축은 잠그고 각도는 제한해 끈처럼 무너지는 감각을 만듭니다.
구현 흐름
- FrontSocket과 RearSocket을 조인트 앵커로 사용합니다.
- PositionAndRotation projection으로 늘어짐을 보정합니다.
- 인접 조각끼리의 충돌은 꺼 체인 폭발을 줄입니다.
선택과 남은 책임
붙은 체인과 떨어진 체인에 서로 다른 운동 모델
물리 객체·조인트의 생성과 제거, 수면 상태, 충돌 모드를 모두 수명주기에 맞춰 정리해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Convoy/ConvoyController.Tail.cs · L175–201
Rigidbody previousBody = previous.GetComponent<Rigidbody>();
Rigidbody currentBody = segment.GetComponent<Rigidbody>();
ConfigurableJoint joint =
segment.gameObject.AddComponent<ConfigurableJoint>();
joint.connectedBody = previousBody;
joint.autoConfigureConnectedAnchor = false;
joint.anchor = GetSegmentSocketLocalPointOrFallback(
segment, FrontSocketName, Vector3.forward * (SegmentSpacing * 0.5f));
joint.connectedAnchor = GetSegmentSocketLocalPointOrFallback(
previous, RearSocketName, Vector3.back * (SegmentSpacing * 0.5f));
joint.xMotion = ConfigurableJointMotion.Locked;
joint.yMotion = ConfigurableJointMotion.Locked;
joint.zMotion = ConfigurableJointMotion.Locked;
joint.angularXMotion = ConfigurableJointMotion.Limited;
joint.angularYMotion = ConfigurableJointMotion.Limited;
joint.angularZMotion = ConfigurableJointMotion.Limited;
joint.projectionMode = JointProjectionMode.PositionAndRotation;
joint.enableCollision = false;멈춘 뒤에만 회수 지점을 보여줍니다.
분리 직후의 격렬한 물리 상태에서 재결합이 발동하지 않도록 선형·각속도와 최소 시간을 함께 검사합니다.
구현 흐름
- 움직임이 다시 커지면 SettledTime과 RejoinReady를 즉시 취소합니다.
- 안착 시간과 분리 후 최소 나이를 모두 만족해야 합니다.
- 머리가 아니라 현재 부착 꼬리 끝이 회수 영역에 들어와야 재결합합니다.
선택과 남은 책임
물리 안정성과 플레이어 선택을 모두 통과한 재결합
속도 임계값이 너무 낮으면 기다림이 길고, 너무 높으면 움직이는 체인이 갑자기 붙을 수 있습니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Convoy/ConvoyController.Tail.cs · L279–338
group.Age += deltaTime;
if (!IsDetachedTailSettled(group))
{
group.SettledTime = 0f;
group.RejoinReady = false;
HideRejoinArea(group);
return false;
}
group.SettledTime += deltaTime;
if (group.SettledTime < DetachedTailSettleTime
|| group.Age < DetachedTailMinRejoinAge)
return false;
group.RejoinReady = true;
UpdateRejoinArea(group);
if (!IsPlayerTailEndInsideRejoinArea(group))
return false;
return ReattachDetachedTail(group);세그먼트 데이터 생산 구조
Definition → Profile → Prefab → Catalog → Runtime 순서를 고정해 여러 담당이 같은 형식으로 콘텐츠를 확장합니다.
정체성과 레벨별 실행 데이터를 분리했습니다.
SegmentDefinition은 세그먼트 ID와 레벨별 정의를 묶고, 공격 방식의 세부값은 SegmentAttackProfile로 이동합니다.
구현 흐름
- 같은 세그먼트가 레벨별 Prefab과 Profile을 가질 수 있습니다.
- 공격 방식은 공통 런타임이 읽는 데이터로 표현합니다.
- 새 콘텐츠는 코어 코드를 수정하기보다 Catalog에 등록해 합류합니다.
선택과 남은 책임
콘텐츠 정체성·수치·실행을 단계별 자산으로 분리
Definition/Profile/Prefab 사이 참조가 빠지지 않도록 제작 체크와 검증 도구가 필요합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Segments/Definitions/SegmentLevelDefinition.cs · serialized data
[System.Serializable]
public struct SegmentLevelDefinition
{
[Min(1)] public int Level;
public GameObject SegmentPrefab;
public GameObject BodyPrefab;
public GameObject HeadPrefab;
public SegmentAttackProfile AttackProfile;
[TextArea(1, 3)] public string Memo;
public bool HasSegmentPrefab => SegmentPrefab != null;
}
// Runtime resolves a definition by SegmentId,
// then selects the requested level and attaches
// the matching behavior profile to the segment.발표의 숫자를 현재 YAML에서 다시 셌습니다.
포트폴리오 표면의 수치는 문서 문장을 그대로 옮기지 않고 현재 저장소의 직렬화 목록을 직접 집계했습니다.
구현 흐름
- SegmentCatalog의 Segments 항목 15개.
- StarterCatalog의 WormId 항목 5개.
- StatUpgradeCatalog Cards 8개, WeaponEnhancementCatalog 강화 참조 36개.
선택과 남은 책임
현재 데이터 파일을 근거로 숫자 표기
팀 프로젝트 전체 등록량이므로 개인 기여량과 반드시 분리해 설명해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Segments/_Catalog/*.asset + Assets/Resources/LevelCard/*.asset · 2026-09-11 snapshot
SegmentCatalog.asset
Segments: 15 references
StarterCatalog.asset
Starters: 5 WormId entries
StatUpgradeCatalog.asset
Cards: 8 references
WeaponEnhancementCatalog.asset
Cannon: 4 + Missile: 4
Additional segment enhancements: 28
Total: 36 references새 무기는 데이터가 공통 실행기를 조합합니다.
GenericSegmentWeapon 계열은 MoveType, ImpactType, TargetPriority, 발사 수, 연쇄, 레이저 등을 Profile에서 읽어 공통 실행 흐름으로 연결합니다.
구현 흐름
- 직선·포물선·레이저·연쇄 등 이동 방식을 Profile enum으로 분기합니다.
- 공통 피해 계산은 CoreStatData와 세그먼트 강화 데이터를 함께 적용합니다.
- 특수 규칙은 partial 파일로 나눠 공통 흐름의 크기를 제어합니다.
선택과 남은 책임
새 콘텐츠의 차이를 Profile에, 공통 규칙을 Runtime에
모든 특수 무기를 억지로 한 실행기에 넣으면 조건문이 늘어나므로 예외의 독립 기준이 필요합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Segments/Attacks/GenericSegmentWeapon.cs · L135–186
private bool CanUseWeapon()
{
return IsWeaponActive
&& Segment != null
&& Segment.Owner != null
&& AttackProfile != null;
}
private DamageData CreateDamageData(Vector3 position)
{
CoreStatData coreStats = CoreStatProvider.GetCurrentOrDefault();
WeaponStatBonusData weaponBonus = CoreStatProvider
.GetWeaponStatBonusOrDefault(GetEffectiveSegmentId());
float runBaseDamage = CoreStatProvider
.ApplyRunBaseAttackBonusOrDefault(AttackProfile.BaseDamage);
float baseDamage = weaponBonus.ResolveBaseDamage(runBaseDamage);
float commonDamage = CoreStatProvider
.GetCommonBaseDamageBonusOrDefault(
GetEffectiveSegmentId(), runBaseDamage);
float damage = GetUpgrade().ApplyDamage(
baseDamage + commonDamage + coreStats.FlatDamageBonus);
return DamageData.Create(
damage, GetDamageType(), Segment.ChainIndex, position, gameObject);
}
private void ResetCooldown()
{
WeaponStatBonusData weaponBonus = CoreStatProvider
.GetWeaponStatBonusOrDefault(GetEffectiveSegmentId());
float runBaseCooldown = CoreStatProvider
.ApplyRunAttackSpeedBonusOrDefault(AttackProfile.Cooldown);
float cooldown = weaponBonus.ResolveCooldown(runBaseCooldown);
float baseInterval = Mathf.Max(0.05f, GetRandomizedCooldown(cooldown));
float coreInterval = CoreStatProvider.GetCurrentOrDefault()
.ApplyFireInterval(baseInterval);
fireTimer = GetUpgrade().ApplyFireInterval(coreInterval);
}밸런스 계측과 제작 도구
세그먼트별 기여도를 런타임에서 관찰하고, 표 형식 에디터로 자산을 비교하며, 작은 수치 손실도 누적 규칙으로 보정합니다.
총합과 현재 웨이브를 분리해 봅니다.
컨보이가 길어질수록 눈으로는 어떤 세그먼트가 과하거나 약한지 판단하기 어렵습니다. 공격 소스별 피해를 집계해 비중과 변화를 함께 확인합니다.
구현 흐름
- 누적 DPS와 웨이브 DPS를 동시에 비교합니다.
- 세그먼트 이름별 절대값과 전체 비중을 표시합니다.
- 자동 궤도와 함께 사용해 조향 변수를 줄인 측정 상태를 만듭니다.
선택과 남은 책임
플레이 중 계측과 에디터 비교표를 함께 사용
짧은 표본과 특정 웨이브 결과만으로 전체 밸런스를 확정하면 안 됩니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Debug + Assets/Editor/SegmentBalanceTableWindowV02.cs · runtime and editor tooling
Measurement loop
├─ identify damage source / segment id
├─ accumulate total damage and current-wave damage
├─ normalize by elapsed combat time
├─ sort contributors for quick comparison
└─ tune serialized profiles in the editor table
The meter is evidence for iteration,
not an automatic declaration of final balance.작은 배율이 매번 버려지지 않게 나머지를 보존합니다.
정수 재화에 1.1배 같은 보너스를 적용할 때 매번 내림하면 소량 드롭에서 보너스가 영원히 체감되지 않을 수 있습니다.
구현 흐름
- 이전 소수 나머지를 다음 지급에 더합니다.
- 이번에 지급한 정수만 빼고 나머지를 다시 저장합니다.
- 많은 작은 드롭에서도 장기 기대값이 배율에 가까워집니다.
선택과 남은 책임
정수 UI를 유지하면서 장기 기대값 보존
나머지 상태도 런 초기화 시점과 저장 범위를 명확히 해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Core/CoreStatProvider.cs · L809–819
private static int ApplyRunRewardGain(
int baseAmount, float multiplier, ref float remainder)
{
if (baseAmount <= 0)
return 0;
float rawAmount = Mathf.Max(0, baseAmount)
* Mathf.Max(0f, multiplier)
+ Mathf.Max(0f, remainder);
int result = Mathf.Max(0,
Mathf.FloorToInt(rawAmount + 0.0001f));
remainder = Mathf.Max(0f, rawAmount - result);
return result;
}도구는 판단을 돕지만 플레이 검증을 대체하지 않습니다.
DPS만 맞아도 사거리, 조준 시간, 과잉 피해, 군중 제어, 이동 중 명중률에 따라 실제 체감은 달라집니다.
구현 흐름
- 단일 수치보다 같은 웨이브·같은 궤도에서 비교합니다.
- 지원 세그먼트는 DPS가 낮아도 생존과 회수 성공률에 기여합니다.
- 이번 포트폴리오 제작에서는 기존 영상과 소스를 대조했으며 새 PlayMode 계측은 실행하지 않았습니다.
선택과 남은 책임
정적 근거와 실행 검증을 구분
포트폴리오가 덜 화려해 보이더라도 확인하지 않은 수치는 확정처럼 말하지 않습니다.
코드 근거 · 기존 발췌 기록
Portfolio evidence boundary · declared limitation
Confirmed in this audit
✓ runtime meter footage exists
✓ balance editor source exists
✓ presentation graphs exist
Not re-run in this audit
— Unity PlayMode benchmark
— device performance profile
— long-session balance sample절차적 초원 생성
중앙 넥서스와 길 네트워크를 기준으로 높이, 재질 혼합, 장식, 로딩 진행을 같은 생성 루틴에서 조립합니다.
큰 맵 생성 작업을 프레임에 나눴습니다.
로딩 오버레이를 먼저 렌더한 뒤 길을 만들고, 정점과 삼각형 행을 일정 묶음씩 처리하며 yield합니다.
구현 흐름
- 첫 yield로 로딩 UI가 실제로 한 프레임 그려질 시간을 줍니다.
- 정점과 삼각형 루프가 서로 다른 진행 구간을 사용합니다.
- Terrain 템플릿 경로가 실패하면 메쉬 생성 경로로 폴백합니다.
선택과 남은 책임
메인 스레드 작업을 단계적으로 양보
총 생성 시간은 남아 있으므로 작업량 상한과 진행 단계의 실제 비용을 계속 계측해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/World/Map/MeadowTerrainRuntimeGenerator.cs · L368–455
private IEnumerator BuildRoutine()
{
PrepareSceneVisuals();
overlay = MeadowLoadingOverlay.Create(...);
yield return null;
Vector2 center = ResolveNexusCenter();
List<TrailSpline> trails = CreateTrailNetwork(center);
MeshBuildContext context = CreateMeshBuildContext(center, trails);
for (int z = 0; z < context.VertexRows; z++)
{
FillVertexRow(context, z);
if (z % Mathf.Max(1, RowsPerFrame) == 0)
{
overlay?.SetProgress(...);
yield return null;
}
}
for (int z = 0; z < context.Rows; z++)
{
FillTriangleRow(context, z);
if (z % Mathf.Max(1, TriangleRowsPerFrame) == 0)
yield return null;
}
CreateGeneratedMeshObject(parent, CompleteMesh(context), center, trails);
}길 경계까지의 거리 하나로 3개 지형층을 섞습니다.
넥서스 공터와 각 Trail의 가장 가까운 signed distance를 구해 흙길·중간층·풀의 RGB 가중치로 변환합니다.
구현 흐름
- 거리 0 이하는 흙길 100%입니다.
- 첫 블렌드 폭에서 흙→중간층, 다음 폭에서 중간층→풀로 전환합니다.
- 경계 노이즈를 더해 지나치게 매끈한 인공 곡선을 줄입니다.
선택과 남은 책임
길 형태와 재질 경계를 같은 거리장으로 공유
Trail 수와 alphamap 해상도가 커지면 거리 계산량이 늘어 편집 영역 축소가 필요합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/World/Map/MeadowTerrainRuntimeGenerator.cs · L3251–3300
float bestSigned = CalculateClearingSignedDistance(point, center);
for (int i = 0; i < trails.Count; i++)
{
float signed = CalculateTrailSignedDistance(point, trails[i]);
if (signed < bestSigned)
{
bestSigned = signed;
bestMid = trails[i].MidBlendWidth;
bestGrass = trails[i].GrassBlendWidth;
}
}
return SignedDistanceToBlend(bestSigned, bestMid, bestGrass);
// signed distance -> RGB terrain weights
if (signedDistance <= 0f) return new Color(1f, 0f, 0f, 1f);
if (signedDistance <= mid)
return new Color(1f - t, t, 0f, 1f);
return new Color(0f, 1f - grassT, grassT, 1f);생성기는 StageScene에 실제로 직렬화되어 있습니다.
문서 설명만으로 “적용”을 주장하지 않고 현재 Unity 씬 YAML에서 컴포넌트 바인딩을 확인했습니다.
구현 흐름
- StageScene.unity에 Assembly-CSharp 타입으로 연결되어 있습니다.
- CoreTest, LevelTest, MonsterTest, SegmentTest에도 같은 생성기가 존재합니다.
- 현재 포트폴리오 검증은 직렬화 연결까지이며 PlayMode 재생성 시간은 새로 측정하지 않았습니다.
선택과 남은 책임
문서가 아니라 실제 씬 연결로 적용 범위 확인
씬 연결은 실행 성공의 필요조건이지 충분조건은 아니므로 PlayMode 검증과 구분합니다.
코드 근거 · 기존 발췌 기록
Assets/Scenes/StageScene.unity · L41316
m_EditorClassIdentifier:
Assembly-CSharp::TeamProject01.Gameplay.MeadowTerrainRuntimeGenerator
Additional serialized scene bindings found in:
Assets/Scenes/Dev/CoreTest_StageScene.unity
Assets/Scenes/Dev/LevelTest_StageScene.unity
Assets/Scenes/Dev/MonsterTest_StageScene.unity
Assets/Scenes/Dev/SegmentTest_StageScene.unity런 시작·결과·메타 저장
타이틀 선택을 런 시작 데이터로 묶고, 스테이지 결과를 별도 컨텍스트로 돌려보내며, 장기 진행을 버전 있는 JSON으로 저장합니다.
씬을 넘길 때 UI 오브젝트 대신 값만 보냅니다.
선택한 지렁이와 맵, 영구 업그레이드의 결과를 RunStartBonusData로 합성해 RunLoadoutContext에 보관합니다.
구현 흐름
- 지렁이 고유 보너스와 공통 업그레이드 보너스를 합성합니다.
- 씬의 UI 계층을 DontDestroyOnLoad로 끌고 가지 않습니다.
- 스테이지 진입 시 코어가 한 번 소비해 런 능력치에 반영합니다.
선택과 남은 책임
씬 사이 전달을 명시적인 값 객체로 제한
정적 Context는 소비·초기화 시점을 놓치지 않도록 진입 흐름에서 관리해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Core/Meta/MetaProgressionManager.cs · L116–126
public RunStartBonusData BuildStartBonus()
{
RunStartBonusData bonus = RunStartBonusData.Create(
SelectedWormId, SelectedMapId);
bonus.AddValues(GetWormBonus(SelectedWormId));
bonus.AddValues(GetUpgradeBonus());
return bonus;
}
public void PushStartBonusToContext()
{
RunLoadoutContext.SetStartBonus(BuildStartBonus());
}저장 전후에 상태를 정규화하고 버전을 검사합니다.
PlayerPrefs는 JSON 문자열의 저장소로만 사용하고, 실제 구조는 SaveData로 직렬화합니다. 깨진 JSON과 잘못된 버전은 기본값 경로로 보냅니다.
구현 흐름
- 저장 전에 음수·빈 ID·범위 밖 값을 정규화합니다.
- 역직렬화 예외와 null, 잘못된 버전을 각각 거부합니다.
- 변경 시 자동 저장 여부를 한 설정으로 통제합니다.
선택과 남은 책임
간단한 로컬 저장에도 데이터 구조와 버전 경계 유지
PlayerPrefs는 보안·대규모 마이그레이션·원자적 파일 교체가 필요한 제품 단계에는 한계가 있습니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Core/Meta/MetaProgressionManager.cs · L264–305
public void SaveProgress()
{
if (string.IsNullOrWhiteSpace(PlayerPrefsKey)) return;
NormalizeState();
PlayerPrefs.SetString(PlayerPrefsKey,
JsonUtility.ToJson(CreateSaveData()));
PlayerPrefs.Save();
}
public bool LoadProgress()
{
if (!HasSavedProgress()) return false;
string json = PlayerPrefs.GetString(PlayerPrefsKey, string.Empty);
if (string.IsNullOrWhiteSpace(json)) return false;
try { data = JsonUtility.FromJson<SaveData>(json); }
catch (ArgumentException) { return false; }
if (data == null || data.Version <= 0) return false;
ApplySaveData(data);
NormalizeState();
return true;
}현재 런의 선택과 다음 런의 선택을 다른 재화로 만들었습니다.
골드는 런 안에서 즉시 소비하고, 런 중 획득한 다이아는 결과 정산 후 메타 진행에 반영됩니다.
구현 흐름
- 런 중 골드는 CoreStatProvider의 CurrentGold에만 존재합니다.
- CurrentRunDiamond는 결과까지 별도로 누적됩니다.
- 결과 화면과 타이틀 복귀가 같은 RunResultData를 참조합니다.
선택과 남은 책임
런타임 경제와 장기 진행의 수명 분리
결과 확정 전에 씬을 이탈할 때 임시 재화 처리 규칙을 명확히 해야 합니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Core/CoreStatProvider.cs + Core/Meta/* · runtime economy flow
Title selection
-> RunStartBonusData
-> RunLoadoutContext
-> CoreStatProvider.ApplyRunStartBonus
Monster reward
-> RewardData
-> CurrentGold / CurrentRunDiamond
Run finish
-> RunResultData
-> RunResultContext
-> MetaProgressionManager
-> versioned save설계 선택과 절충
경로 기반 제어와 물리 표현, 직접 참조와 계약, 빠른 생산과 검증 범위를 상황에 따라 다르게 선택했습니다.
안정적으로 조작할 때와 무너질 때의 기술을 분리했습니다.
긴 부착 체인까지 전부 물리로 만들면 조작 응답과 재현성이 흔들립니다. 반대로 절단 순간까지 Transform만 쓰면 사건의 무게가 사라집니다.
구현 흐름
- 부착: 플레이어 의도와 거리 간격을 우선합니다.
- 분리: 충격과 붕괴의 가시성을 우선합니다.
- 재결합: 물리 구성요소를 제거한 뒤 제어 체인에 다시 등록합니다.
선택과 남은 책임
게임 상태에 맞는 운동 모델 전환
전환 경계에서 컴포넌트와 목록의 수명주기 버그가 생기기 쉽습니다.
코드 근거 · 기존 발췌 기록
ConvoyController.Path.cs + ConvoyController.Tail.cs · hybrid lifecycle
ATTACHED
Transform path following
+ socket-based spacing
+ deterministic control response
CUT
remove from attached runtime lists
+ Rigidbody / ConfigurableJoint
+ burst impulse
REJOIN
settle gate
+ remove joints and bodies
+ append to attached chain
+ register runtime again누락을 견디게 했지만 경고는 숨기지 않았습니다.
RewardGateway가 씬에 없을 때 보상을 완전히 잃지 않도록 CoreStatProvider 직접 전달 폴백이 남아 있습니다. 동시에 Warning으로 조립 누락을 드러냅니다.
구현 흐름
- 정상 구조는 Gateway를 통한 한 개의 진입점입니다.
- 통합 중 조립 실수로 유저 보상이 사라지는 실패는 방지합니다.
- 폴백이 조용히 굳지 않도록 콘솔 경고를 남깁니다.
선택과 남은 책임
데이터 손실 방지 폴백 + 가시적 경고
폴백이 오래 유지되면 Gateway 계약을 우회하는 경로가 사실상 표준이 될 수 있습니다.
코드 근거 · 기존 발췌 기록
Assets/Scripts/Core/RewardGateway.cs · L39–61
public static bool SubmitReward(RewardData reward)
{
if (!reward.IsValid) return false;
if (Active == null)
{
if (CoreStatProvider.TryApplyReward(reward))
{
Debug.LogWarning(
"[RewardGateway] 보상 입구 없음: " +
"CoreStatProvider로 직접 보상 적용");
return true;
}
return false;
}
return Active.ReceiveReward(reward);
}볼륨을 위해 품질을 버린 것이 아니라 반복 경로를 만들었습니다.
17일 안에 모든 콘텐츠를 코어 담당자가 직접 만들 수는 없습니다. 그래서 섹션 소유권, API 데이터 통로, Catalog 등록 순서를 생산 시스템으로 설계했습니다.
구현 흐름
- 연결 계약을 먼저 고정해 기능 간 대기 시간을 줄였습니다.
- 팀원은 자기 섹션 내부 구현에 집중했습니다.
- 통합 지점과 QA 기준은 코어에서 모았습니다.
선택과 남은 책임
속도 문제를 협업 구조와 반복 생산 구조로 해결
짧은 일정 때문에 장시간 플레이, 실기기 프로파일링, 저장 마이그레이션은 후속 과제로 남았습니다.
코드 근거 · 기존 발췌 기록
발표 자료 + TeamDocs + current repository · cross-checked narrative
Constraint
17-day focused team development
Response
section ownership
+ shared DTO contracts
+ catalog-based registration
+ editor/runtime measurement tools
+ integration checkpoints
Outcome
title -> stage -> wave -> reward -> growth
-> cut / recover -> result -> meta loop근거, 검증 범위, 남은 한계
현재 소스와 직렬화 데이터를 사실 근거로 사용하고, 팀 결과와 개인 기여, 정적 확인과 실행 확인을 분리합니다.
실제 저장소 규모와 핵심 파일을 다시 확인했습니다.
2026.09.11 현재 ProjectWormKnight의 Assets 아래 C# 299개를 집계했습니다. 이는 팀 저장소 전체 규모이며 개인 작성량이 아닙니다.
구현 흐름
- ConvoyController는 기능별 partial 파일로 분리되어 있습니다.
- StageScene에 MeadowTerrainRuntimeGenerator가 연결되어 있습니다.
- Segment/Starter/Card/Enhancement Catalog 수치를 YAML에서 직접 확인했습니다.
선택과 남은 책임
포트폴리오 문구를 현재 파일 스냅샷에 맞춤
저장소가 바뀌면 숫자와 파일 범위도 다시 집계해야 합니다.
코드 근거 · 기존 발췌 기록
D:/JC Program/유니티/팀프로젝트/11팀 미니프로젝트/ProjectWormKnight · audit snapshot 2026-09-11
Repository snapshot
Unity version: 6000.3.15f1
C# scripts under Assets: 299
SegmentCatalog entries: 15
StarterCatalog entries: 5
StatUpgradeCatalog cards: 8
Weapon enhancement references: 36
StageScene generator binding: present움직이는 결과는 원본 캡처에서 가져왔습니다.
개인 폴더의 원본 GIF를 브라우저용 H.264 MP4로 변환했습니다. 자동 궤도, 절단·재결합, 특수 웨이브·보상, DPS 계측을 페이지에 직접 배치했습니다.
구현 흐름
- 원본을 새 연출처럼 재구성하지 않고 실제 플레이 프레임을 유지했습니다.
- 대용량 GIF는 24fps, yuv420p, faststart MP4로 최적화했습니다.
- 무음 원본이므로 모든 영상은 muted·loop·playsinline으로 제공됩니다.
선택과 남은 책임
실제 플레이 증거를 설명 바로 옆에 배치
영상은 촬영 시점의 상태이며 현재 빌드 전체 회귀 테스트를 의미하지 않습니다.
코드 근거 · 기존 발췌 기록
OZCodingProject 개인파일/영상제작/GIF총모음 · source media conversion
field-loop.mp4 <- 특수웨이브+보상.gif
auto-orbit.mp4 <- 자동궤도+길어진버전.gif
cut-rejoin.mp4 <- 절단된세그먼트합치기.gif
dps-meter.mp4 <- dps.gif
segment-matrix.mp4 <- 4 segment GIF sources
Encoding: H.264 / yuv420p / faststart
Playback: autoplay / muted / loop / playsinline정적 확인을 PlayMode 성공처럼 말하지 않습니다.
이번 문서 제작에서는 소스·씬 YAML·Catalog·기존 영상·발표 자료를 교차 확인했습니다. Unity Editor를 열어 PlayMode나 테스트를 새로 실행하지는 않았습니다.
구현 흐름
- 컴파일 성공, 프레임 성능, 장시간 안정성은 이번 턴의 새 증거가 아닙니다.
- 기존 실행 영상은 기능 존재의 시각 근거로만 사용합니다.
- 출시 수준을 주장하려면 실기기 프로파일링, 저장 마이그레이션, 반복 회귀가 추가로 필요합니다.
선택과 남은 책임
확인한 범위를 그대로 공개
추가 런타임 검증 전에는 출시 안정성이나 성능 수치를 확정적으로 제시하지 않습니다.
코드 근거 · 기존 발췌 기록
Portfolio verification statement · scope boundary
Verified now
✓ source structure and concrete implementations
✓ serialized scene bindings
✓ catalog entry counts
✓ presentation dates and role documents
✓ supplied runtime footage
Not freshly verified now
— Unity compilation / PlayMode
— automated test suite
— target-device performance
— long-session save migration