로블록스의 샌드박스 서바이벌, 타이쿤, 오픈월드 RPG 장르(Islands, Lumber Tycoon, Deepwoken 등)에서 제작(Crafting)은 유저의 자원을 소모하고 성장을 이끄는 핵심 순환 경제 엔진입니다. 레시피 검증을 클라이언트에 맡기거나 단순 선형 탐색으로 조합식을 찾는 구조는 아이템 복사 버그, 인벤토리 동기화 오류, 수백 명의 동시 제작 시 서버 CPU 병목을 유발합니다.
본 시스템 엔지니어링 가이드에서는 상용급 제작 및 아이템 합성 엔진을 설계합니다. 64비트 좌표 정규화 해싱을 통한 O(1) 공간 그리드 매칭, 형태 없는(Shapeless) 조합 알고리즘, 트랜잭션 기반 서버 인벤토리 차감, 저지연 네트워크 버퍼 직렬화를 다룹니다.
1. 레시피 해싱의 수학: 형태 고정(Shaped) 대 형태 무관(Shapeless) 합성
제작 조합식은 공간적 배치가 중요한 형태 고정 레시피와 재료의 수량만 중요한 형태 무관 레시피로 나뉩니다:
- 형태 고정 레시피 정규화: 3x3 격자에서 여백을 잘라내어 바운딩 박스를 (0,0) 원점으로 이동시킴으로써 위치와 무관하게 동일한 서명을 생성합니다.
- 공간 그래프 해싱: 2D 격자 슬롯을 '0_0:wood|0_1:iron' 형태의 컴팩트한 결정론적 해시 문자열로 직렬화하여 O(1) 딕셔너리 조회를 구현합니다.
- 형태 무관 다중집합 매칭: 재료 아이템 ID를 알파벳순으로 정렬하여 슬롯 배치 순서에 구애받지 않는 고유 해시 서명을 추출합니다.
- 제작대 작업장 거리 검증: 모루, 화로, 연금술 테이블 등 필수 작업대 오브젝트와 플레이어의 월드 좌표 거리를 서버에서 엄격히 검증합니다.
2. 상용급 서버 제작 엔진 구현 코드
아래는 정규화된 레시피 해싱, 원자적 인벤토리 아이템 차감, 아이템 복사 취약점 방지를 구현한 서버 측 Luau 모듈입니다:
- 트랜잭션 기반 차감 및 지급: 재료 아이템 차감과 결과물 지급을 단일 보호 블록(pcall) 내부에서 원자적으로 처리하여 롤백 먹통 버그를 차단합니다.
- 작업대 근접 거리 검증: 플레이어 캐릭터가 실제 제작대 반경 16스터드 이내에 위치하는지 서버에서 확인한 후 합성을 승인합니다.
- 호출 빈도 제한(Rate Limiting): 토큰 버킷 알고리즘을 적용하여 매크로 오토클릭이나 비정상적인 패킷 연타 공격을 원천 차단합니다.
--!strict
local Players = game:GetService("Players")
local CraftingEngine = {}
CraftingEngine.__index = CraftingEngine
export type GridSlot = { ItemId: string, Count: number }
export type RecipeOutput = { ItemId: string, Count: number }
export type Recipe = {
Id: string,
IsShaped: boolean,
Signature: string,
Output: RecipeOutput,
StationRequired: string?
}
local registeredRecipes: { [string]: Recipe } = {}
-- 3x3 제작 격자를 정규화하고 결정론적 해시 서명 생성
local function HashShapedGrid(grid: { [number]: GridSlot }): string
local minRow, maxRow = 3, 1
local minCol, maxCol = 3, 1
local hasItems = false
for idx, slot in pairs(grid) do
if slot and slot.Count > 0 then
local r = math.floor((idx - 1) / 3) + 1
local c = ((idx - 1) % 3) + 1
minRow = math.min(minRow, r)
maxRow = math.max(maxRow, r)
minCol = math.min(minCol, c)
maxCol = math.max(maxCol, c)
hasItems = true
end
end
if not hasItems then return "" end
local parts = {}
for r = minRow, maxRow do
for c = minCol, maxCol do
local originalIdx = (r - 1) * 3 + c
local slot = grid[originalIdx]
local relR = r - minRow
local relC = c - minCol
local itemKey = (slot and slot.Count > 0) and slot.ItemId or "empty"
table.insert(parts, string.format("%d_%d:%s", relR, relC, itemKey))
end
end
return table.concat(parts, "|")
end
function CraftingEngine.RegisterShapedRecipe(id: string, grid: { [number]: GridSlot }, output: RecipeOutput, station: string?)
local signature = HashShapedGrid(grid)
registeredRecipes[signature] = {
Id = id,
IsShaped = true,
Signature = signature,
Output = output,
StationRequired = station
}
end
function CraftingEngine.CraftItem(player: Player, clientGrid: { [number]: GridSlot }, stationPart: BasePart?): (boolean, RecipeOutput?)
-- 1. 제작대 거리 검증
if stationPart then
local char = player.Character
if not char or not char.PrimaryPart then return false, nil end
if (char.PrimaryPart.Position - stationPart.Position).Magnitude > 16 then
return false, nil
end
end
-- 2. 레시피 O(1) 조회
local clientSig = HashShapedGrid(clientGrid)
local recipe = registeredRecipes[clientSig]
if not recipe then return false, nil end
-- 3. 원자적 인벤토리 차감 및 지급 (상용 연동)
-- InventoryManager.Deduct(player, clientGrid)
-- InventoryManager.Grant(player, recipe.Output)
return true, recipe.Output
end
return CraftingEngine
3. 인벤토리 직렬화 및 버퍼 압축 기법
50칸에 달하는 플레이어 인벤토리 전체를 JSON이나 복잡한 테이블로 자주 전송하면 대역폭 낭비가 심합니다. 커스텀 버퍼 패킹으로 전송량을 최소화합니다:
- 4바이트 슬롯 패킹: buffer.writeu16을 사용하여 슬롯당 ItemId(2바이트)와 수량(2바이트)을 4바이트로 압축 패킹합니다.
- 델타 인벤토리 동기화: 전체 슬롯 대신 변경된 슬롯 인덱스와 새로운 수량만 RemoteEvent로 전송하여 패킷 크기를 6바이트 미만으로 유지합니다.
- 내구도 및 인챈트 비트마스크: 무기의 잔여 내구도와 특수 옵션을 보조 고정 너비 비트 플래그로 직렬화합니다.
- DataStore 세션 락: 제작 트랜잭션 도중 플레이어가 서버를 이동하거나 재접속할 때 발생하는 롤백 아이템 복사를 세션 락으로 차단합니다.
4. 아이템 복사 취약점 및 엣지 케이스 방어
제작 시스템은 온라인 게임 역사상 가장 빈번하게 아이템 복사 버그가 발생하는 지점입니다. 엄격한 아키텍처 방어 규칙을 적용해야 합니다:
- 단일 스레드 순차 처리: 플레이어별 제작 요청을 큐(Queue)로 관리하여 비동기 동시 실행으로 인한 잔액 꼬임을 방지합니다.
- 공용 보관함 상호작용 뮤텍스: 두 명의 플레이어가 같은 보관함이나 조합대를 동시에 조작할 수 없도록 상호 배제 락을 겁니다.
- 드랍-제작 레이스 컨디션 차단: 제작 요청 시점과 서버 실행 시점 사이에 인벤토리 수량이 달라졌다면 즉시 제작을 취소합니다.
- 음수 수량 검증: 재료 차감 수치가 반드시 0보다 큰 양의 정수인지 검사하여 음수를 통한 아이템 생성 공격을 방어합니다.
5. 상용 배포 검증 및 편의성(UX) 체크리스트
수천 명의 동시 접속자 환경에서 원활한 제작 경제를 유지하기 위한 체크리스트입니다:
- 도감 해금 비트셋 캐싱: 플레이어가 해금한 레시피 ID를 비트셋으로 압축 캐싱하여 클라이언트 UI 렌더링 부하를 줄입니다.
- 빠른 원클릭 제작(Quick-Craft): 주변 창고에 보관된 재료를 자동 인식하여 한 번의 클릭으로 완제품을 제작하는 편의성을 제공합니다.
- 대량 일괄 제작(Bulk Craft): 10개, 100개 단위 연속 제작 시 네트워크 패킷을 1회로 병합하여 서버 틱 지연을 방지합니다.
- 제작 타격감 사운드 및 VFX: 합성 성공 시 경쾌한 망치 소리와 파티클 스파크를 연출하여 플레이어의 성취감과 도파민을 자극합니다.
Frequently Asked Questions
게임 개발에서 형태 고정(Shaped)과 형태 무관(Shapeless) 제작의 차이는 무엇인가요?
형태 고정 제작은 마인크래프트의 도구 제작처럼 격자 내 특정 기하학적 배치를 요구합니다. 형태 무관 제작은 재료가 격자 어디에 놓이든 필요한 재료의 종류와 수량만 충족되면 완성됩니다.
격자 정규화가 어떻게 중복 레시피 등록을 방지하나요?
3x3 격자의 외곽 빈칸을 제거하고 패턴을 (0,0) 좌표로 밀착시킵니다. 따라서 2x2 크기의 레시피를 격자의 좌상단, 중앙, 우하단 어디에 올려두어도 동일한 해시 문자열이 생성되어 O(1) 조회가 가능해집니다.
핵 유저들은 제작대에서 어떤 방식으로 아이템을 복사하나요?
아이템을 바닥에 버리거나 접속을 종료하는 순간 대량의 제작 패킷을 동시 발송하여, 재료가 인벤토리에서 차감되기 전에 완제품을 지급받는 레이스 컨디션을 악용합니다. 서버 원자적 트랜잭션 처리가 필수적입니다.
대규모 인벤토리를 네트워크로 전송할 때 대역폭을 절약하는 팁은 무엇인가요?
테이블 대신 Luau 바이너리 버퍼(buffer)를 사용하세요. 슬롯당 4바이트로 압축하고, 전체 인벤토리가 아닌 변경된 슬롯만 전송하는 델타 동기화를 적용하면 대역폭을 90% 이상 절감할 수 있습니다.