스티브코딩

스크립트 API 서버 운영

입장 메시지·팀 채팅·보호 구역·길드·퀘스트 등 학급 서버 운영용 스크립트 13가지

도구·명령어2026-10-20 갱신

한눈에 보기

  • 무엇: 여러 명이 함께 쓰는 서버·학급 월드를 운영할 때 필요한 기능을 스크립트 API(JavaScript)로 만든 예제 모음
  • 누구: 스크립트 API를 한 번 적용해 본 교사, 고등학생·코딩 동아리(중급~고급)
  • 몇 분: 기능 하나당 코드 붙여 넣기 10분, 코드 읽기·고치기 1~2차시
  • 결과물: 채팅 명령(!길드, !저장, !워프 등)이나 나침반 UI로 쓰는 행동 팩
  • 대부분 GitHub의 코드를 main.js에 그대로 붙여 넣어 쓰고, 문구·값만 바꿔 우리 반 서버로 만듭니다.
블록을 놓거나 부술 수 없는 보호 구역 설정하기
블록을 놓거나 부술 수 없는 보호 구역 설정하기

기능 한눈에 고르기

기능 쓰임 영상 링크
입장 메시지 · 기기 표시 처음 온 학생에게 규칙 안내, 모바일·PC 확인 [입장](https://youtu.be/RN9-LahL4tI) · [기기](https://youtu.be/GaN4A8IXUok)
팀 채팅 · 도배 금지 모둠끼리만 대화, 채팅 예절 [팀 채팅](https://youtu.be/KZuXZUz02q8) · [도배 금지](https://youtu.be/D5VeF_cAK2M)
순간이동 요청 · 위치 저장 허락받고 모이기, 탐사 지점 저장 [워프](https://youtu.be/XGtmmp0kiKU) · [위치 저장](https://youtu.be/T4pG6pn2swU)
허용·거부 구역 · 보호 구역 작업 구역 나누기, 완성 작품 지키기 [허용·거부](https://youtu.be/H9DoQttE5_I) · [보호 구역](https://youtu.be/fqjSODEEPXc)
칭호 · 길드 · 길드 전쟁 역할 표시, 모둠(길드) 운영, 팀 대항전 [칭호](https://youtu.be/ZPUvfg-jIkc) · [길드](https://youtu.be/ptWIJbiqj9w) · [길드 전쟁](https://youtu.be/dyfLch_5gD4)
퀘스트 · 퀴즈 학습 과제를 임무로, 교과 퀴즈 [퀘스트](https://youtu.be/8KyXFZekb_Q) · [퀴즈](https://youtu.be/hdPMNhcT43c)
서버에서 서버로 이동 로비 서버 → 활동별 서버 [서버 이동](https://youtu.be/lvg7qufYDoA)

시작 전 준비: 코드 넣기와 관리자 태그

  1. Bridge에서 행동 팩 프로젝트를 만들고 scripts/main.js 에 GitHub 코드를 붙여 넣기
  2. manifest.json에 @minecraft/server 와 @minecraft/server-ui(UI가 있는 기능) 종속성 추가
  3. 월드 실험 기능에서 ‘베타 API’ 켜고 행동 팩 적용 → 코드를 고친 뒤에는 /reload
  4. 관리자 기능은 admin 태그로 구분: /tag @s add admin (지우기: /tag @s remove admin)
  • 사용법은 대부분 코드 맨 위 주석에 적혀 있습니다. 먼저 읽고 시작하세요.
  • 처음이라면 「스크립트 API 시작하기」「복붙한 코드를 애드온으로 바꾸기」 영상을 먼저 봅니다(자료 링크).
교사용 메모 · 준비 메모: 베타 API와 교육용 에디션
  • 이 페이지의 기능 대부분은 채팅 명령을 world.beforeEvents.chatSend(퀴즈는 afterEvents.chatSend)로 받습니다. chatSend는 공식 문서에서 실험(베타) 전용으로 표시된 이벤트라, 월드에서 ‘베타 API’ 실험 기능을 켜야 동작합니다.
  • 허용·거부 구역의 블록 설치 감지(beforeEvents.playerPlaceBlock)도 베타 전용입니다. 블록 파괴 감지(playerBreakBlock)는 정식(stable) 기능입니다.
  • 서버 이동에 쓰는 @minecraft/server-admin 모듈은 manifest에 1.0.0-beta 버전으로 넣습니다(영상 화면).
  • 교육용 에디션은 월드 설정에 ‘베타 API’ 실험 토글이 없고, 베타 버전 종속성을 넣어도 로드되지 않았습니다(2026-07 실측). 그래서 chatSend·playerPlaceBlock(before)·server-admin을 쓰는 예제는 교육용 에디션에서 그대로는 동작하지 않습니다. 이 예제들은 베드락 에디션 월드·전용 서버에서 쓰는 것을 기본으로 하고, 교육용 에디션이 업데이트되면 다시 시험해 보세요.
  • 2023~2025년 영상 코드라 runCommandAsync 를 씁니다. @minecraft/server 2.0.0부터 runCommandAsync 는 없어졌으므로, manifest를 2.x로 쓰면 runCommand 로 바꿉니다. player.isOp() 는 지금 공식 문서에 없어 쓸 수 없고(GitHub 최신 코드도 op·admin 태그로 확인하도록 바뀜), 권한 확인이 필요하면 정식 기능인 player.commandPermissionLevel(2.1.0부터)을 씁니다. 기기 표시에 쓰는 player.clientSystemInfo.platformType 은 정식 기능이라 그대로 쓸 수 있습니다(2026-10 공식 문서 기준).
  • 오류는 설정의 제작자 로그(콘텐츠 로그)로 확인하고, manifest의 @minecraft/server 버전을 로그에 나온 버전으로 맞춥니다.

입장 메시지와 접속 기기 표시

  • 입장 메시지: 처음 온 플레이어 → “새로운 플레이어 ○○님이 서버에 처음 오셨습니다!” + 개인 안내, 다시 온 플레이어 → “○○님이 서버에 다시 접속하셨습니다.”
  • 접속 기록은 월드 동적 속성 joinedPlayers 에 저장되어 월드를 껐다 켜도 남습니다. 관리자(OP)가 채팅에 !resetplayer 를 치면 기록 초기화
  • 기기 표시: 이름표 아래에 Mobile·Desktop 같은 플랫폼이 빨간 글씨로 나옵니다.
world.afterEvents.playerSpawn.subscribe((eventData) => {
    if (!eventData.initialSpawn) return; // 리스폰은 무시
    const player = eventData.player;
    let joinedPlayers = world.getDynamicProperty("joinedPlayers") || "";
    let playerList = joinedPlayers ? joinedPlayers.split(",") : [];
    if (!playerList.includes(player.name)) {
        playerList.push(player.name);
        world.setDynamicProperty("joinedPlayers", playerList.join(","));
        world.sendMessage(`§e새로운 플레이어 ${player.name}님이 서버에 처음 오셨습니다!`);
        player.sendMessage("§a환영합니다! 서버의 규칙을 확인해주세요!");
    } else {
        world.sendMessage(`§b${player.name}님이 서버에 다시 접속하셨습니다.`);
    }
});

// 기기 표시: 주기적으로 모든 플레이어의 이름표 수정
system.runInterval(() => {
    for (const player of world.getAllPlayers()) {
        const os = player.clientSystemInfo.platformType;
    }
});
① 다시 접속한 플레이어에게 나온 메시지 (00:21)
① 다시 접속한 플레이어에게 나온 메시지 (00:21)
② 이름표 아래에 표시된 기기 Mobile (00:13)
② 이름표 아래에 표시된 기기 Mobile (00:13)

팀 채팅: 우리 팀끼리만 대화하기

채팅 입력 하는 일
`!setteam A` 내 팀을 A로 정하기
`!adminsetteam 플레이어이름 팀이름` 관리자(admin 태그)가 다른 학생의 팀 정하기
`!teamlist` 팀별 소속 플레이어 목록
`!t 메시지` / `!team 메시지` 같은 팀에게만 `[팀 채팅]`으로 보내기
- 팀은 플레이어 동적 속성 `team` 에 저장되어 다시 들어와도 남습니다. ``` function sendTeamMessage(sender, message, team) { for (const player of world.getAllPlayers()) { if (getPlayerTeam(player) === team) { player.sendMessage(`§6[팀 채팅] §f${sender.name}: ${message}`); } } } ```
팀 설정 → 권한 없음 → admin 태그 → 팀 목록 → 팀 채팅 (01:26)
팀 설정 → 권한 없음 → admin 태그 → 팀 목록 → 팀 채팅 (01:26)

도배 금지: 같은 말·긴 말·빠른 채팅 막기

  • 같은 채팅 반복 → “이전 채팅이랑 똑같습니다.” / 10글자 넘음 → “메시지가 너무 깁니다. 최대 (10글자)” / 너무 빠름 → “채팅 속도가 너무 빠릅니다. (0.3초 남음)”
  • 조건에 걸리면 data.cancel = true 로 채팅을 아예 보내지 않습니다.
  • chat_speed 스코어보드를 정상 채팅 때 10틱(0.5초)으로 두고, runInterval 로 1틱마다 1씩 줄입니다. 20틱 = 1초
const chat_length = 10 // 채팅 글자 제한
const chat_speed = 10; // 채팅 속도 (1틱 = 0.05초)

if (player.getDynamicProperty("lastChat") == msg) {
    data.cancel = true
    player.sendMessage(`§c이전 채팅이랑 똑같습니다.`)
} else if (msg.length > chat_length) {
    data.cancel = true
    player.sendMessage(`§c메시지가 너무 깁니다. 최대 (${chat_length}글자)`)
} else if (chatSpeed > 0) {
    data.cancel = true // 채팅 속도가 너무 빠름
} else {
    player.runCommandAsync(`scoreboard players set @s chat_speed ${chat_speed}`)
    player.setDynamicProperty("lastChat", msg)
}
같은 채팅과 빠른 채팅이 막힌 채팅창 (00:38)
같은 채팅과 빠른 채팅이 막힌 채팅창 (00:38)

순간이동 요청: 상대가 허락해야 이동

  1. 채팅에 !워프 → 채팅창을 닫으면 지금 접속한 플레이어 목록 창이 열림
  2. 갈 플레이어를 누르면 “○○님에게 워프 요청을 보냈습니다.”
  3. 상대에게 ‘워프 요청’ 수락·거절 창 → 수락하면 3초 뒤 이동
  • 대답이 없으면 요청 만료, 같은 상대에게 이미 요청이 진행 중이면 “이미 다른 워프 요청이 진행 중입니다.”
  • 값은 WARP_CONFIG 에서 바꿉니다(단위: 초).
const WARP_CONFIG = {
    COOLDOWN: 120,        // 쿨타임 (초)
    REQUEST_TIMEOUT: 60,  // 요청 만료 시간 (초)
    TELEPORT_DELAY: 3     // 텔레포트 지연 시간 (초)
};
  • 영상 설명은 쿨타임 3분이지만 화면 코드와 GitHub 최신 코드(2026-10) 값은 모두 120(2분)입니다. 3분으로 쓰려면 180으로 바꿉니다.
WARP_CONFIG 설정값과 사용법 주석 (00:41)
WARP_CONFIG 설정값과 사용법 주석 (00:41)

위치 저장: 자주 가는 곳을 나침반으로 이동

  • 저장: 그 자리에서 채팅에 !저장 해안마을 → “위치 '해안마을'이(가) 성공적으로 저장되었습니다.”
  • 이동: 나침반을 들고 우클릭 → ‘저장된 위치’ 창의 버튼(이름과 X·Y·Z 좌표)을 누르면 순간이동
  • 삭제: 창의 ‘저장된 위치 삭제’ → 드롭다운에서 골라 보내기
  • 위치는 월드 동적 속성 savedLocations 에 플레이어별로 저장(JSON) → 월드를 나갔다 와도 유지
if (message.startsWith("!저장")) {
    chatEvent.cancel = true;
    const locationName = message.slice(3).trim(); // '!저장' 뒤 글자
    const pos = player.location;
    savedLocations[player.name] = savedLocations[player.name] || {};
    savedLocations[player.name][locationName] = {
        x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z)
    };
}
// 버튼을 고르면
player.teleport({ x: coords.x, y: coords.y, z: coords.z });
① !저장 해안마을 로 저장 (00:15)
① !저장 해안마을 로 저장 (00:15)
② 나침반 우클릭 → 저장된 위치 창 (00:37)
② 나침반 우클릭 → 저장된 위치 창 (00:37)

허용·거부 구역: 좌표 범위로 블록 설치·파괴 제한

  • 허용 구역: 정한 범위 안에서만 블록을 놓고 부술 수 있고, 밖에서는 “이 영역에서는 블록을 설치할 수 없습니다.”
  • 거부 구역: 변수 isAllowed 를 isRestricted 로 바꾸고 !(아니다)만 빼면 반대로 그 구역 안만 금지
  • 운영자(OP)는 막지 않습니다(!player.isOp()일 때만 취소). 교사는 언제든 고칠 수 있음
  • 교육용 에디션의 허용·거부 블록은 그 위쪽 전체에 적용 → 좌표 상자로 정하고 싶으면 코드가 편리
const allowRegion = {
    min: { x: -10, y: -60, z: -10 },  // 최소 좌표
    max: { x: 10, y: -50, z: 10 }     // 최대 좌표
};
world.beforeEvents.playerBreakBlock.subscribe((ev) => {
    const player = ev.player;
    const { x, y, z } = ev.block.location;
    const isAllowed = (
        x >= allowRegion.min.x && x <= allowRegion.max.x &&
        y >= allowRegion.min.y && y <= allowRegion.max.y &&
        z >= allowRegion.min.z && z <= allowRegion.max.z
    );
    if (!isAllowed && !player.isOp()) {
        ev.cancel = true;
        player.sendMessage(`§c이 영역에서는 블록을 파괴할 수 없습니다.`);
    }
});
// 설치는 world.beforeEvents.playerPlaceBlock 으로 똑같이
허용 구역 시작·끝 좌표와 파괴 이벤트 코드 (00:28)
허용 구역 시작·끝 좌표와 파괴 이벤트 코드 (00:28)

보호 구역: 나무 도끼로 두 꼭짓점 찍기

  1. 교사에게 admin 태그를 주고 나무 도끼를 듦
  2. 보호할 곳의 첫 꼭짓점 블록 우클릭 → “첫번째 좌표가 저장되었습니다.”
  3. 반대쪽 꼭짓점 우클릭 → “새로운 보호 영역이 저장되었습니다.” → admin 태그가 없는 학생은 설치·파괴 불가
  • 채팅 명령: !보호목록(번호와 좌표) · !보호삭제 2(2번 삭제) · !보호초기화(모두 삭제)
  • 코드 위쪽 값으로 도구·태그·문구를 바꿉니다. 영역은 동적 속성 protectedAreas 에 저장
const ADMIN_ITEM_NAME = `minecraft:wooden_axe`
const ADMIN_TAG = `admin`
const CANT_DESTROY_MESSAGE = `§c[ system ] 해당 블록은 파괴할 수 없습니다.`
const CANT_PLACE_MESSAGE = `§c[ system ] 해당 블록은 설치할 수 없습니다.`
const DB_KEY = "protectedAreas"
  • 구독자 플럼(Plumr)님이 제공한 코드(BlockProtector.js)입니다.
① 보호 영역 저장과 !보호목록 결과 (01:37)
① 보호 영역 저장과 !보호목록 결과 (01:37)
② 사용법 주석과 도구·태그 설정 (01:56)
② 사용법 주석과 도구·태그 설정 (01:56)

칭호: 채팅과 이름표에 붙는 나만의 이름

  • 처음에는 모두 <뉴비> 칭호 → 나침반 우클릭 → ‘칭호 설정’에 입력(예: 서버장) → <서버장> <ssakspirit> 서버장입니다.
  • 삭제에서 ‘예’를 누르면 “칭호를 삭제했습니다”와 함께 뉴비로 돌아감
  • 칭호는 1~10글자만 저장(0글자·10글자 초과는 안내 메시지), 플레이어 동적 속성 rank 에 저장
  • 이름표에도 [ 서버장 ] 이름 으로 2틱마다 표시
world.beforeEvents.chatSend.subscribe((data) => {
    const rank = data.sender.getDynamicProperty(`rank`)
    const name = data.sender.name
    const msg = data.message
    data.cancel = true;
    if (typeof rank == "undefined") {
        world.sendMessage(`<뉴비> <${name}> ${msg}`)
    } else {
        world.sendMessage(`<${rank}> <${name}> ${msg}`)
    }
})
① 나침반으로 연 칭호 설정 창 (00:26)
① 나침반으로 연 칭호 설정 창 (00:26)
② 뉴비 → 서버장으로 바뀐 채팅 (00:39)
② 뉴비 → 서버장으로 바뀐 채팅 (00:39)

길드: 모둠 만들기·가입·길드 채팅

채팅 입력 누가 하는 일
`!길드` 모두 길드 생성(이름·설명) · 가입 요청 · 탈퇴 · 정보
`!길드장` 길드장 길드원 관리(추방) · 정보 수정 · 가입 요청 수락 · 해체
`!관리자` admin 태그 모든 길드 삭제, 길드 채팅 모니터링
`ㅁ메시지` 길드원 같은 길드원만 보는 길드 채팅
- 채팅창을 닫으면 창이 열립니다. 가입이 수락되면 이름 위에 `[운영자]`처럼 길드 이름이 붙음 - 한 길드에만 가입 가능, 길드장이 탈퇴하면 길드 해체. 길드 정보는 동적 속성 `guilds`(JSON)에 저장
① !길드 → 길드 생성 창 (00:25)
① !길드 → 길드 생성 창 (00:25)
② 길드 정보: 길드·설명·길드원·길드장 (00:38)
② 길드 정보: 길드·설명·길드원·길드장 (00:38)
③ 가입 수락 후 이름 위 ‘운영자’ 표시 (02:16)
③ 가입 수락 후 이름 위 ‘운영자’ 표시 (02:16)

길드 전쟁: 목표 킬 수와 보상 정하기

  • 같은 길드끼리는 공격 불가, 다른 길드끼리는 공격 가능(최대 20개 길드). 이 기능은 GitHub의 player.json 을 행동 팩 entities 폴더에 넣어야 함
  • !길드전쟁설정: 목표 킬 수·승리 보상 정하기(영상: 3킬, 에메랄드 5개) / !길드전쟁: 시작 / !길드전쟁종료: 결과 발표 — 모두 admin 태그 필요
  • 시작하면 스코어보드 guildWar 에 길드별 킬 수 표시, 목표를 먼저 채운 길드의 모든 길드원이 보상
  • 목표 전에 종료하면 보상 없이 최종 순위만. 길드가 2개 이상이어야 시작
function startGuildWar(player) {
    if (!player.hasTag('admin')) {
        player.sendMessage('§c관리자만 길드 전쟁을 시작할 수 있습니다.');
        return;
    }
    const guilds = getGuilds();
    if (Object.keys(guilds).length < 2) {
        player.sendMessage('§c길드 전쟁을 시작하려면 최소 2개 이상의 길드가 필요합니다.');
        return;
    }
    // ...
}
① 코드 맨 위 길드 시스템 사용법(명령어 목록) (01:39)
① 코드 맨 위 길드 시스템 사용법(명령어 목록) (01:39)
② 3킬 달성 → 승리 길드 발표와 에메랄드 보상 (01:12)
② 3킬 달성 → 승리 길드 발표와 에메랄드 보상 (01:12)

퀘스트: 태그로 임무 완료·보상 관리

  • 나침반 우클릭 → ‘퀘스트’ 창: 채팅치기 / 플레이어 때리기(미완료·완료 표시)
  • 임무를 하면 “퀘스트 "채팅 치기" 완료!” → 창에서 ‘보상받기’ → 에메랄드 10개(채팅 퀘스트)
  • 태그 4개로 상태 관리: chat·hit(수행 완료), chat_compensation·hit_compensation(보상 받음) — /tag @s list 로 확인
  • 같은 모양으로 블록 캐기 같은 새 퀘스트를 추가해 보는 활동으로 확장
if (player.hasTag("chat")) {
    if (player.hasTag(`chat_compensation`)) {
        player.sendMessage(`이미 보상을 받았습니다.`)
    } else {
        player.runCommandAsync(`give @s emerald 10`)
        player.runCommandAsync(`tag @s add chat_compensation`)
    }
}
// 때리기 퀘스트: world.afterEvents.entityHitEntity 에서 damagingEntity 에 hit 태그
① 나침반으로 연 퀘스트 목록 (00:08)
① 나침반으로 연 퀘스트 목록 (00:08)
② 채팅 퀘스트 창과 보상 지급 코드 (02:44)
② 채팅 퀘스트 창과 보상 지급 코드 (02:44)

멀티플레이 퀴즈: 채팅으로 문제 풀기

  • 채팅에 시작 → 화면에 “1번 문제”(제목)와 문제(부제목) → 채팅으로 정답 → “정답입니다! 다음 문제로 진행합니다.” → 마지막 문제 뒤 “게임이 종료되었습니다. 수고하셨습니다!”
  • 종료 를 치면 중간에 끝냄. 여러 명이 함께 풀 수 있음(먼저 맞힌 사람 기준으로 다음 문제)
  • 문제 목록만 교과 내용으로 바꾸면 우리 반 퀴즈가 됩니다. 정답은 trim 으로 앞뒤 공백을 지우고 대소문자 무시
const questions = [
    { question: "마인크래프트 개발사 이름은?", answer: "모장" },
    { question: "마인크래프트 주인공 이름은?", answer: "스티브" },
    { question: "지금 보는 유튜브 채널 이름은?", answer: "스티브코딩" },
    // 추가 문제들을 필요에 따라 이어서 나열
];
// 문제 보여 주기 (인덱스는 0부터라 +1)
world.getDimension("overworld").runCommandAsync(`title @a subtitle ${currentQuestion.question}`)
world.getDimension("overworld").runCommandAsync(`title @a title ${currentQuestionIndex + 1}번 문제`)
① 시작 → 1번 문제가 화면에 (00:05)
① 시작 → 1번 문제가 화면에 (00:05)
② 정답 비교와 다음 문제로 넘어가는 코드 (05:12)
② 정답 비교와 다음 문제로 넘어가는 코드 (05:12)

서버에서 서버로 이동하기

  • 나침반 우클릭 → ‘서버 이동’ 창에서 서버를 고르면 그 서버로 접속(로비 서버 → 활동 서버 구조에 활용)
  • manifest.json dependencies 에 @minecraft/server-admin(1.0.0-beta) 추가 필요
  • 내 서버는 servers 목록에 이름·주소·포트를 같은 모양으로 추가
import { transferPlayer } from '@minecraft/server-admin';

const servers = {
    "GALAXITE": { ip: `play.galaxite.net`, port: 19132 },
    // "우리반 서버": { ip: `주소`, port: 19132 },
};
// UI에서 고른 서버로 보내기
transferPlayer(player, serverInfo.ip, serverInfo.port)
  • 서버 주소는 바뀔 수 있으니 최신 주소를 씁니다(코드 주석). @minecraft/server-admin 은 공식 문서상 베타(pre-release) 전용이고 “마인크래프트 클라이언트나 Realms에서는 쓸 수 없는” 전용 서버(BDS)용 모듈입니다. 그래서 교육용 에디션·일반 월드에서는 동작하지 않습니다. 지금 문서의 형식은 transferPlayer(player, { hostname, port }) 라서 영상 코드(transferPlayer(player, ip, port))는 고쳐 써야 할 수 있습니다(2026-10 공식 문서 기준).
① 나침반으로 연 서버 이동 창 (00:04)
① 나침반으로 연 서버 이동 창 (00:04)
② manifest에 넣을 server-admin 모듈 주석 (00:52)
② manifest에 넣을 server-admin 모듈 주석 (00:52)

수업 팁

  • 관리자는 교사만: admin 태그·OP는 교사에게만. 길드 삭제·팀 지정·보호 구역 지정을 학생이 하지 않게 합니다.
  • 한 번에 하나씩: 여러 스크립트를 한 main.js에 그대로 이어 붙이면 import 줄과 같은 이름의 변수·함수가 겹쳐 오류가 나거나, 같은 채팅 명령을 두 코드가 함께 처리할 수 있습니다. 기능을 하나씩 넣고 확인합니다.
  • 채팅 예절과 연결: 도배 금지는 학생과 규칙(글자 수·간격)을 함께 정하고 값을 고쳐 보는 활동으로.
  • 모둠 활동: 팀 채팅·길드·칭호로 모둠 이름과 역할(건축가·탐험가)을 표시합니다.
  • 작품 보호: 완성 작품·공용 시설은 보호 구역, 개인 작업 공간은 허용 구역으로 나눕니다.
  • 코드 읽기 수업: 동적 속성(저장), 태그(권한·상태), 이벤트(채팅·아이템 사용) 세 가지가 거의 모든 예제에 반복됩니다.

관련 영상

관련 공식 자료

함께 보기

출처

  • 유튜브 「문제를 내고 푸는 퀴즈 게임 제작(멀티플레이 가능) #애드온제작」(2023-12-08)