Quake식 무기 픽업: 인벤토리 없는 단순함
이전 버전 Dogkov는 Tarkov에서 영감을 받아 36칸 그리드 인벤토리, 슬롯 장착(헬멧/방탄복/탄약/조준경/소음기 등), 우클릭 컨텍스트 메뉴까지 다 만들었습니다. 다 합쳐서 ~600줄. 그런데 5분짜리 데스매치엔 너무 무거웠습니다. Q3식 자동 픽업으로 단순화한 결과를 정리합니다.
이전: Tarkov 식
- 36칸 그리드 인벤토리
- 장비 슬롯: weapon · head · body · ammo · attachments(4종)
- 맵에 루트박스 → F 키로 열기 → 드래그/우클릭으로 장착
- 킬 드롭 배낭 → 다시 루팅
- 탄약 타입 매칭 (5.56 / 7.62 / .300WM / .408)
몰입감은 좋지만, 한 매치 동안 인벤토리 만지는 시간이 사격하는 시간보다 길었습니다.
이후: Q3식
완전히 갈아엎고 두 개의 dict만 남겼습니다:
player = {
hasWeapon: { gauntlet:true, machinegun:true, shotgun:false, ... },
weapons: { gauntlet:0, machinegun:100, shotgun:0, ... },
currentWeapon: 'machinegun',
...
}
hasWeapon은 "들고 있나", weapons는 탄약. 픽업 닿으면 둘 다 갱신. 끝.
무기 정의
const WEAPON_DEFS = {
gauntlet: {
kind: 'melee', damage: 50, rateMs: 400, range: 36,
maxAmmo: Infinity, alwaysOwned: true, ...
},
rocket: {
kind: 'projectile', damage: 100, splashDamage: 80, splashRadius: 110,
rateMs: 800, projSpeed: 900, maxAmmo: 50, pickupAmmo: 5, ...
},
...
};
alwaysOwned:true면 스폰 시 자동 보유 (gauntlet, MG). 나머지는 픽업해야 들 수 있습니다.
픽업 처리
_tryGrabPickup(p, pk, now) {
const def = pk.def;
if (def.type === 'weapon') {
const wd = WEAPON_DEFS[def.sub];
const before = p.weapons[def.sub];
p.hasWeapon[def.sub] = true;
p.weapons[def.sub] = Math.min(wd.maxAmmo, p.weapons[def.sub] + wd.pickupAmmo);
// 처음 줍는 강한 무기는 자동 전환
if (!before && p.currentWeapon === 'machinegun' && def.sub !== 'machinegun') {
p.currentWeapon = def.sub;
}
return true;
}
if (def.type === 'health') {
const cap = def.sub >= 100 ? 200 : 100;
if (p.hp >= cap) return false;
p.hp = Math.min(cap, p.hp + def.sub);
return true;
}
// ammo / armor / powerup 동일 패턴
}
HUD 동적 슬롯
이전엔 9개 슬롯 고정 표시였는데, 보유 안 한 무기는 회색이라 시각적으로 시끄러웠습니다. 보유한 것만 표시하도록 변경:
WEAPONS.forEach((k, i) => {
if (!me.hasWeapon[k]) return; // 안 들고 있으면 건너뜀
const wd = WEAPON_DEFS[k];
ws.appendChild(createWeaponBox(k, wd, me, i));
});
마우스 휠 / Q · E 순환도 같은 필터를 거칩니다. 결과적으로 막 시작했을 땐 슬롯 2개 (gauntlet + MG), 픽업할수록 점점 늘어남.
탄약 표시: Infinity 처리
gauntlet은 무한 사용입니다 (maxAmmo: Infinity). 그러나 JSON.stringify로 wire 전송하면 null이 됩니다. 클라가 isFinite로 체크해서 "∞" 표시:
const wd = WEAPON_DEFS[me.cw];
$('hud-ammo').textContent = isFinite(wd.maxAmmo) ? me.weapons[me.cw] : '∞';
플레이어의 weapons[k] 값이 아니라 무기 정의의 maxAmmo로 판정하는 게 핵심. JSON 전송 시 null로 변환된 값을 기준으로 하면 잘못된 결과 나옴.
잃은 것 / 얻은 것
인벤토리 UI 코드: 600줄 → 0줄. 픽업 코드: 200줄 → 60줄. 신규 플레이어 이해 시간: 5분 → 30초. 다만 무기 부착물·헬멧 같은 깊이는 포기. 단순함이 데스매치 컨셉에 잘 맞습니다.