본문으로 건너뛰기

Lua 스크립트

Connect Base 게임 서버는 Lua 스크립트로 서버 권위(server-authoritative) 게임 로직을 정의할 수 있습니다. 샌드박스 안에서 실행되며, 룸의 category_id 에 매핑된 스크립트가 자동으로 로드됩니다.

⚠️ 이 페이지는 backend/cmd/game-server/app/scripting/engine.go 가 노출하는 실제 전역과 1:1 일치합니다. 다른 글에서 본 getState, broadcastEvent, setTimer 같은 함수는 존재하지 않으니 사용하지 마세요.

이벤트 핸들러

스크립트 최상위에 다음 함수를 정의하면 게임 서버가 자동으로 호출합니다.

lua
-- 플레이어 입장: clientID 와 userID 가 인자로 전달됩니다
function onJoin(clientID, userID)
    log("player joined: " .. clientID)

    -- room.state 는 룸의 상태 테이블 그대로 — 직접 읽기 가능
    local players = room.state.players or {}
    players[clientID] = {
        x = 0,
        y = 0,
        health = 100,
        score = 0,
    }
    room.setState("players", players)
end

-- 플레이어 퇴장
function onLeave(clientID)
    local players = room.state.players
    if players then
        players[clientID] = nil
        room.setState("players", players)
    end
end

-- 매 틱마다 호출 (deltaTime 단위: 초)
function onTick(deltaTime)
    local players = room.state.players or {}
    for id, p in pairs(players) do
        -- 게임 로직...
    end
end

-- 클라이언트가 보낸 액션 처리
-- action 테이블 필드: type, client_id, client_timestamp, sequence, data
function onAction(action)
    if action.type == "move" then
        handleMove(action.client_id, action.data)
    elseif action.type == "shoot" then
        handleShoot(action.client_id, action.data)
    end
end

📌 onJoin, onLeave, onTick, onAction — 이 4개가 게임 서버가 호출하는 유일한 라이프사이클 훅입니다. onRoomCreated, onPlayerJoined, onPlayerLeft 같은 이름은 호출되지 않습니다.

room 전역

룸 자체에 대한 접근은 모두 room 테이블을 통해 이루어집니다.

lua
room.id                          -- 룸 ID (string, read-only)
room.state                       -- 현재 상태 테이블 (직접 읽기 가능)
room.setState(path, value)       -- 상태 필드 수정 (path 는 dot notation, 예: "players.abc.health")
room.getPlayers()                -- 현재 플레이어 배열 반환
                                 -- 각 entry: { client_id, user_id, metadata }
room.getPlayer(clientID)         -- 단일 플레이어 조회 (없으면 nil)
room.broadcast(data)             -- data 를 JSON 직렬화하여 모든 클라이언트에 전송
lua
function handleMove(clientID, data)
    local player = room.state.players[clientID]
    if not player then return end

    -- 속도 검증
    local dx = data.x - player.x
    local dy = data.y - player.y
    local distance = math.sqrt(dx * dx + dy * dy)
    local maxSpeed = 5

    if distance > maxSpeed then
        local ratio = maxSpeed / distance
        data.x = player.x + dx * ratio
        data.y = player.y + dy * ratio
    end

    player.x = data.x
    player.y = data.y
    room.setState("players." .. clientID, player)

    -- 모든 클라이언트에 알림
    room.broadcast({ type = "moved", client_id = clientID, x = player.x, y = player.y })
end

유틸리티 전역

lua
-- 로깅
log("hello")                     -- 1KB 초과 시 잘림

-- JSON
local s = json.encode({ a = 1, b = "x" })
local v, err = json.decode(s)

-- 시간
local nowSec = time.now()        -- Unix epoch (초)

-- Lua 표준 라이브러리
math.random(1, 10)               -- 표준 math 모듈 사용

⚠️ setTimer, setInterval, cancelTimer, broadcastEvent, sendEvent, kickPlayer, getState, setState, deleteState, random, getServerTime 같은 전역은 등록되어 있지 않습니다. 주기적인 작업이 필요하면 onTick 안에서 직접 카운터를 누적하세요.

서비스 바인딩

scripting/services.go 에서 등록하는 추가 전역들 — Connect Base 의 다른 기능을 Lua 안에서 호출할 수 있습니다.

전역설명
db같은 앱의 데이터베이스 read/write
kvNATS JetStream KV 스토어
http외부 HTTP 호출 (timeout/허용 도메인 정책 적용)
member앱 멤버 조회
storage파일 스토리지 read/write
function다른 Connect Base 함수 호출
websocket외부 WebSocket 푸시
spectator관전자 시스템 (spectator_api.go)
triggers트리거 볼륨 (trigger_api.go)
console디버그 로그 (debug.go)
physics물리 엔진 (다음 섹션 참고)

스크립트 등록

스크립트는 콘솔 → 게임 → 카테고리 메뉴에서 카테고리에 첨부합니다. 같은 category_id 의 룸이 생성되면 해당 스크립트가 자동으로 로드되고 핫 리로드도 지원됩니다.

⚠️ 외부에서 호출 가능한 POST /v1/game/scripts HTTP 엔드포인트는 존재하지 않습니다. 콘솔 UI 또는 카테고리 관리 API 를 사용하세요.

보안 고려사항

  • Lua 스크립트는 샌드박스 환경에서 실행됩니다
  • 파일 시스템, 네트워크 접근이 제한됩니다
  • CPU 시간과 메모리 사용량이 제한됩니다
  • 무한 루프 감지 및 중단됩니다