타르코프 스타일 인벤토리 시스템 구현기
Escape from Tarkov의 가장 매력적인 요소 중 하나는 촘촘한 인벤토리 관리입니다. 무엇을 가져가고 무엇을 버릴지 판단하는 순간의 긴장감이 게임의 재미를 극대화합니다. Dogkov에서도 이 경험을 재현하기 위해 Canvas 기반 그리드 인벤토리를 구현했습니다.
데이터 구조 설계
인벤토리의 핵심은 데이터 구조입니다. 36칸(6x6) 그리드와 별도의 장비 슬롯으로 구성됩니다.
const inventory = {
grid: new Array(36).fill(null), // 6x6 그리드
slots: {
weapon: null, // 주무기
helmet: null, // 헬멧
armor: null, // 방탄복
laser: null, // 레이저 부착물
}
};
// 아이템 정의
const ITEMS = {
'ammo_556': { name: '5.56mm 탄약', type: 'ammo', rarity: 'common', stackable: true },
'ammo_762': { name: '7.62mm 탄약', type: 'ammo', rarity: 'common', stackable: true },
'medkit': { name: '구급팩', type: 'medical', rarity: 'uncommon', heal: 40 },
'painkiller':{ name: '진통제', type: 'medical', rarity: 'common', heal: 20 },
'armor_lv1': { name: '방탄복 Lv.1', type: 'armor', rarity: 'common', reduction: 0.05 },
'armor_lv2': { name: '방탄복 Lv.2', type: 'armor', rarity: 'rare', reduction: 0.10 },
'armor_lv3': { name: '방탄복 Lv.3', type: 'armor', rarity: 'epic', reduction: 0.15 },
'helmet': { name: 'FAST MT 헬멧', type: 'helmet', rarity: 'rare' },
'laser_red': { name: 'PEQ-15', type: 'laser', rarity: 'uncommon', recoilBonus: 0.1 },
'laser_grn': { name: '녹색 레이저', type: 'laser', rarity: 'epic', recoilBonus: 0.2 },
// ...무기 아이템들
};
각 아이템은 타입(type)과 등급(rarity)을 가집니다. 등급은 common(흰색), uncommon(초록), rare(파랑), epic(보라)로 나뉘며, 루트 테이블에서 드롭 확률에 직접 영향을 줍니다.
가중치 기반 루트 시스템
루트박스를 열 때 어떤 아이템이 나올지는 가중치 기반 확률로 결정됩니다. 흔한 아이템은 높은 가중치, 희귀 아이템은 낮은 가중치를 가집니다:
const LOOT_TABLE = [
{ id: 'ammo_556', weight: 25 }, // 가장 자주 나옴
{ id: 'ammo_762', weight: 15 },
{ id: 'painkiller', weight: 12 },
{ id: 'splint', weight: 10 },
{ id: 'medkit', weight: 8 },
{ id: 'armor_lv1', weight: 7 },
{ id: 'weapon_ak47',weight: 5 },
{ id: 'armor_lv2', weight: 4 },
{ id: 'laser_red', weight: 4 },
{ id: 'helmet', weight: 3 },
{ id: 'weapon_scar',weight: 2 },
{ id: 'laser_grn', weight: 2 },
{ id: 'armor_lv3', weight: 1.5 },
{ id: 'weapon_mk13',weight: 1 },
{ id: 'weapon_m200',weight: 0.5 }, // 가장 희귀
];
function rollLoot() {
const totalWeight = LOOT_TABLE.reduce((sum, item) => sum + item.weight, 0);
let roll = Math.random() * totalWeight;
for (let item of LOOT_TABLE) {
roll -= item.weight;
if (roll <= 0) return item.id;
}
return LOOT_TABLE[0].id;
}
이 방식의 장점은 새 아이템을 추가할 때 테이블에 한 줄만 넣으면 되고, 밸런스 조정도 weight 값만 수정하면 됩니다.
인벤토리 UI 렌더링
인벤토리 화면은 Canvas 위에 직접 그립니다. DOM 요소를 사용하지 않은 이유는 게임 화면과 인벤토리가 겹치면서도 게임 시간이 계속 흘러야 하기 때문입니다. Canvas 렌더링이면 한 프레임 안에서 게임 화면과 인벤토리를 모두 처리할 수 있습니다.
function drawInventory() {
// 반투명 배경
ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
ctx.fillRect(invX, invY, invWidth, invHeight);
// 6x6 그리드 그리기
for (let i = 0; i < 36; i++) {
const col = i % 6;
const row = Math.floor(i / 6);
const cellX = gridStartX + col * cellSize;
const cellY = gridStartY + row * cellSize;
// 셀 배경
ctx.strokeStyle = '#444';
ctx.strokeRect(cellX, cellY, cellSize, cellSize);
// 아이템이 있으면 그리기
if (inventory.grid[i]) {
const item = ITEMS[inventory.grid[i].id];
ctx.fillStyle = RARITY_COLORS[item.rarity];
ctx.fillRect(cellX + 2, cellY + 2, cellSize - 4, cellSize - 4);
ctx.fillStyle = '#fff';
ctx.fillText(item.name, cellX + 5, cellY + cellSize/2);
}
}
// 장비 슬롯 그리기
drawEquipSlot('weapon', weaponSlotX, weaponSlotY, '주무기');
drawEquipSlot('helmet', helmetSlotX, helmetSlotY, '헬멧');
drawEquipSlot('armor', armorSlotX, armorSlotY, '방탄복');
drawEquipSlot('laser', laserSlotX, laserSlotY, '부착물');
}
등급별 색상은 직관적으로 구분됩니다: 흰색(common), 초록(uncommon), 파란색(rare), 보라색(epic). Tarkov 플레이어라면 익숙한 색상 체계입니다.
루팅 인터페이스
루트박스에 접근하면 왼쪽에 루트박스 내용물, 오른쪽에 내 인벤토리가 표시됩니다. 이 때 주요 상호작용은 두 가지입니다:
- 좌클릭 — 루트박스에서 아이템을 내 인벤토리로 이동 (빈 칸에 자동 배치)
- 우클릭 — 인벤토리의 장비 아이템을 즉시 장착 (해당 슬롯에 배치)
자동 배치 로직은 단순합니다. 첫 번째 빈 그리드 칸을 찾아서 넣습니다:
function addToInventory(itemId) {
// 탄약처럼 스택 가능한 아이템은 기존 스택에 추가
if (ITEMS[itemId].stackable) {
const existing = inventory.grid.findIndex(
slot => slot && slot.id === itemId
);
if (existing !== -1) {
inventory.grid[existing].count += 30;
return true;
}
}
// 첫 번째 빈 칸에 배치
const emptySlot = inventory.grid.findIndex(slot => slot === null);
if (emptySlot === -1) return false; // 인벤토리 가득 참
inventory.grid[emptySlot] = { id: itemId, count: 1 };
return true;
}
장비 장착과 효과 적용
인벤토리에서 우클릭으로 장비를 장착하면 즉시 효과가 적용됩니다:
- 무기 교체 — 현재 무기를 인벤토리로 돌려보내고 새 무기 장착. 무기별 데미지, 연사속도, 반동이 변경
- 방탄복 장착 — 피해 감소율 적용 (Lv.1: 5%, Lv.2: 10%, Lv.3: 15%)
- 헬멧 장착 — 머리 보호 효과
- 레이저 장착 — 반동 회복 속도 향상 + 시각적 레이저 표시
루팅 중에도 게임 시간은 계속 흐릅니다. 루팅에 너무 몰두하면 스캐브에게 습격당할 수 있으므로, 빠르게 필요한 아이템을 골라 가져가는 판단력이 중요합니다. 이 긴장감이 Tarkov 스타일 루팅의 핵심 재미입니다.
킬 드롭 배낭
스캐브를 처치하면 사망 위치에 킬 드롭 배낭이 생성됩니다. 배낭에는 탄약이 필수로 포함되며, 추가로 랜덤 아이템 1~2개가 들어있습니다. 이를 통해 전투가 보상으로 이어지는 선순환 구조를 만들었습니다:
- 스캐브와 전투 → 탄약 소모
- 스캐브 처치 → 킬 드롭에서 탄약 보급
- 보급된 탄약으로 다음 전투 수행
마치며
인벤토리 시스템은 코드량이 꽤 많지만, 핵심은 데이터 구조(그리드 배열 + 슬롯 객체)와 가중치 기반 루트 테이블입니다. 이 두 가지만 잘 설계하면 나머지 UI와 로직은 자연스럽게 따라옵니다. "무엇을 가져가고 무엇을 버릴 것인가"의 고민은 플레이어에게 전략적 깊이를 제공하고, 매 판 다른 경험을 만들어줍니다.