한눈에 보기
- 무엇: 스크립트 API(JavaScript) 예제 12가지로 게임 속 경제 만들기 — 상점, 송금, 은행, 쿠폰, 가챠, 잠금 상자, 블록·플레이어 상호작용 창
- 누구: 스크립트 API를 처음 써 보는 교사, 중·고등 코딩 동아리 (영상 난이도 중급~고급)
- 방법: 설명란의 GitHub 코드를 main.js에 붙여 넣고 가격·아이템·확률 같은 값만 바꿔 씀 (영상 1~5분)
- 결과물: 학급 화폐로 사고팔고 저축하는 서버 월드 → 경제 교육과 연결(제안)
- 화폐는 두 가지: 스코어보드
money(송금·상태창) / 에메랄드 아이템(상점·은행·셜커 상자 상점)

준비: 코드를 애드온으로 적용하기
- Bridge 새 프로젝트(실험 기능 베타 API 켜기) → scripts/main.js에 GitHub 코드 붙여 넣기
- 월드 설정: 행동 팩 적용 + 실험 기능 ‘베타 API’ 켜기 → 코드를 고친 뒤에는
/reload - 관리자 메뉴가 있는 예제(상점 관리·쿠폰 관리):
/op [플레이어이름]+/tag [플레이어이름] add admin
- 모듈:
@minecraft/server(이벤트·명령) /@minecraft/server-ui(ActionFormData = 버튼 창, ModalFormData = 입력 창) - 상점·은행·쿠폰·잠금 데이터는 동적 속성(dynamic property)에 저장 → 월드를 다시 열어도 그대로
- 예전 버전: 2024년 영상 코드의
runCommandAsync는 최신 @minecraft/server에서runCommand로 바꿔야 함
화폐 만들기: 플레이어 상태창
- 채팅
!스코어보드→ money(소지금)·level(레벨) 스코어보드를 만들고 모두 0으로 - 다른 플레이어를 바라보고 마우스 오른쪽 클릭 → 그 사람의 이름·소지금·레벨 창
!내정보→ 내 상태창 (채팅창을 닫아야 열림)- 채팅창이 열려 있으면 창이
UserBusy로 취소됨 → 다시 띄우도록 처리
// 다른 플레이어와 상호작용할 때
world.beforeEvents.playerInteractWithEntity.subscribe((ev) => {
const player = ev.player; // 상호작용을 시작한 플레이어
const target = ev.target; // 상호작용을 당한 대상
if (target.typeId == "minecraft:player") {
Info(player, target); // 상대의 이름·소지금·레벨 창
ev.cancel = true; // 상호작용을 취소
}
});
// 내 정보 창: 다른 UI를 쓰는 중이면 다시 표시
form.show(player).then(response => {
if (response.cancelationReason == "UserBusy") {
myInfo(player);
}
});

송금: 다른 플레이어에게 돈 보내기
- 나침반 우클릭 → ‘은행’ 창: 송금하기 / 잔액확인하기
- 송금 창: 드롭다운 = 접속한 모든 플레이어, 입력 칸 = 금액(1~10000)
- 잘못된 입력은 메시지로 막음: 나에게 보내기 · 빈칸 · 범위 밖 · 숫자 아닌 문자 · 잔액 부족
- 돈은 스코어보드
money값 → 사이드바에 띄워 돈이 옮겨 가는 것을 확인 - 사이드바 표시:
/scoreboard objectives setdisplay sidebar money


송금 코드: 입력 검사 순서
const formData = new ModalFormData();
const players = world.getAllPlayers().map(player => player.name);
formData.title('송금 하기');
formData.dropdown("송금할 플레이어를 선택하세요.", players);
formData.textField("송금 금액을 입력하세요.", "1~10000");
formData.show(player).then(({ formValues }) => {
const received_player = players[formValues[0]]; // 드롭다운에서 고른 플레이어
const money = formValues[1]; // 입력한 송금 금액
if (received_player == player.name) {
player.sendMessage(`자신에게는 송금을 할수없습니다.`);
} else if (money.length == 0) {
player.sendMessage(`송금금액 입력칸이 비어있습니다.`);
} else if (money < 1 || money > 10000) {
player.sendMessage(`${money}는 유효하지않은 송금금액입니다.`);
} else if (!/[0-9]/g.test(money)) {
player.sendMessage(`송금금액에 숫자가 아닌 문자가 들어가있습니다.`);
} else if (money > getScore(player)) {
player.sendMessage(`보낼수있는 돈이 없습니다.`);
} else {
player.runCommandAsync(`scoreboard players remove @s ${score_id} ${money}`);
player.runCommandAsync(`scoreboard players add "${received_player}" ${score_id} ${money}`);
player.sendMessage(`${received_player}님에게 ${money}원을 보냈습니다.`);
}
});
// 스코어보드 값 가져오기 (score_id = "money")
function getScore(player) {
return world.scoreboard.getObjective(score_id).getScore(player)
}
- 순서도로 먼저 그려 보고 코드와 비교하면 조건문 연습이 됨(제안)
에메랄드 은행: 1분마다 1% 이자
- 에메랄드를 들고 우클릭 또는 채팅
!은행→ 처음엔 계좌 생성(플레이어당 1개, 삭제 불가) - 다시 열면 입금·출금·계좌 정보 / 입금하면 인벤토리의 에메랄드가 빠짐
- 1분마다 잔고의 1% 이자: 100 → 101
- 마이너스 통장: -1000 에메랄드까지 출금 가능, 마이너스면 이자가 차감됨
const BANK_DATA_KEY = "bank_accounts";
const INTEREST_RATE = 0.01; // 1% 이자율
const INTEREST_INTERVAL = 1200; // 1분마다 이자 계산 (20틱 * 60초)
const MINIMUM_BALANCE = -1000; // 최소 잔액
// 이자 계산 및 적용 (applyInterest)
const interest = Math.floor(account.balance * INTEREST_RATE);
account.balance += interest;


UI 상점 ①: 나침반으로 여는 상점
- 나침반 우클릭 → ‘상점’ 창: 황금사과 3, 감자 2, 당근 1 에메랄드
- 상품을 누르면 슬라이더로 1~10개 → 에메랄드가 빠지고 아이템이 들어옴
- 상품 추가는 세 곳을 같은 순서로: ① 상품 목록 ② 가격 ③ 영문 아이템 ID(getItemEn) →
/reload - 구매는 명령어 3줄: 에메랄드가 충분하면 give → 액션바 메시지 → clear로 에메랄드 회수
const itemList = ["황금사과(가격: 3)", "감자(가격: 2)", "당근(가격: 1)"];
formData.slider(`구매하실려는 ${item}의 수량을 설정하세요.`, 1, 10, 1);
const totalPrice = quantity * price;
player.runCommandAsync(`give @s[hasitem={item=emerald, quantity=${totalPrice}..}] ${item_en} ${quantity}`);
player.runCommandAsync(`title @s[hasitem={item=emerald, quantity=${totalPrice}..}] actionbar ${item}을(를) 구매했습니다`);
player.runCommandAsync(`clear @s[hasitem={item=emerald, quantity=${totalPrice}..}] minecraft:emerald 0 ${totalPrice}`);

UI 상점 ②: 재고·구매 한도가 있는 상점
- 손님: 에메랄드 우클릭 또는
!상점→ 구매(수량 입력) / 판매(구매가의 70%) - 관리자: 블레이즈 막대 우클릭 또는
!관리자창(admin 태그 필요) → 상품 추가·제거 - 추가 입력: 아이템 ID · 표시될 이름 · 가격(에메랄드) · 초기 재고 · 1인 구매 한도 (예: 사과, 1에메랄드, 재고 10, 한도 5)
- 한도보다 많이 사면 거절, 다시 열면 남은 구매 가능 수량이 줄어 있음
const SHOP_CONFIG = {
SHOP_ITEM: "minecraft:emerald", // 상점 열기 아이템
ADMIN_ITEM: "minecraft:blaze_rod", // 관리자 상점 열기 아이템
DEFAULT_STOCK: 64, // 기본 재고량
MAX_PURCHASE_PER_PLAYER: 64, // 플레이어당 최대 구매 가능 수량
SELL_PRICE_RATIO: 0.7 // 판매가 비율 (구매가 대비 %)
};
const DEFAULT_ITEMS = [
{ id: "minecraft:diamond", price: 10, stock: 64, maxPerPlayer: 64, name: "다이아몬드" },
{ id: "minecraft:iron_ingot", price: 5, stock: 128, maxPerPlayer: 64, name: "철괴" },
{ id: "minecraft:gold_ingot", price: 8, stock: 128, maxPerPlayer: 64, name: "금괴" }
];



셜커 상자 세트 판매
- 셜커 상자에 팔 아이템을 넣어 그 자리에 둠 (설정 → 제작자 설정을 켜면 바라보는 블록의 ‘블록 위치’ 좌표가 보임)
- 채팅
!상자등록 <이름> <가격> <x> <y> <z> <설명>— 예:!상자등록 테스트세트 1 206 64 -338 삼지창, 방패, 메이스 !상점→ 상품 → 설명·가격 확인 → 구매하기 → 내 위치에 같은 셜커 상자가 생김
- 원리: 에메랄드를
clear @s emerald 0 가격으로 빼고,clone명령으로 원본 상자를 내 위치에 복사 - 원본 상자를 치우면 판매 안 됨 (“원본 셜커 상자를 찾을 수 없습니다”)
- 등록 삭제:
!상자삭제 <이름>(admin 태그 필요, GitHub shulkerBoxShop.js 기준)


보상 쿠폰
- 학생: 나침반 우클릭 → 쿠폰 코드 입력 → 보상 (예: ‘스티브코딩’ → 다이아몬드 64개), 같은 쿠폰은 한 번만
- 관리자(
/op+ admin 태그): 채팅!쿠폰관리→ 쿠폰 삭제 · 사용 현황 · 미사용 쿠폰 · 쿠폰 생성 · 모든 쿠폰 초기화 - 쿠폰 생성: 코드(비워 두면 자동 생성) · 설명 · 보상 명령어 — 대상은 꼭
@s(쿠폰을 쓰는 사람) - 예: 1234 ‘랜덤선물’
give @s …(영상에서는 사과 지급) / 4321 ‘꽝’say 꽝입니다 - 이미 있는 코드는 다시 만들 수 없음 / 쿠폰 삭제는 한 번 누르면 바로 지워짐



가챠 블록 ①: 부수면 랜덤 보상이 나오는 블록
- Bridge 새 프로젝트(베타 API) → 새 파일 → 빈 블록: 식별자
stevecoding:gacha_normal, 이름 ‘보통뽑기’ - 블록 컴포넌트에 루트 테이블로 빈 파일
loot_tables/empty.json(내용{}) 지정 → 부숴도 블록이 안 떨어짐 - main.js에 gachablock.js 붙여 넣기 → 블록 식별자·이름을 코드와 똑같이
- 블록 그림: 리소스 팩 textures/blocks/gacha 의 16×16 이미지(바꿔도 됨)
- 월드에 행동·리소스 팩 + 베타 API →
/give로 블록 받기 → 서바이벌에서 부수기

가챠 블록 ②: 보상과 확률 바꾸기
GACHA_TYPES에 블록 식별자마다 이름·보상 목록(아이템, 최소·최대 개수, chance)- chance는 비율: 합(30+40+10=80)으로 나눠 뽑음 → 다이아몬드는 40/80 = 50%
- 묶음을 복사해 두 번째 가챠(‘희귀한뽑기’)를 만들 수 있음 — 블록도 새로 만들고 식별자·이름을 맞춰야 함
playerBreakBlock이벤트: 부서진 블록이 가챠이면give @s 아이템 개수+ 획득 메시지
const GACHA_TYPES = {
"stevecoding:gacha_normal": {
name: "보통뽑기",
rewards: [
{ item: "emerald_block", minCount: 1, maxCount: 3, chance: 30 },
{ item: "diamond", minCount: 1, maxCount: 5, chance: 40 },
{ item: "netherite_ingot", minCount: 1, maxCount: 1, chance: 10 }
]
}
};
// 확률에 따른 아이템 선택 (selectRandomReward)
const totalChance = rewards.reduce((sum, reward) => sum + reward.chance, 0);
let random = Math.random() * totalChance;
for (const reward of rewards) {
if (random < reward.chance) return reward;
random -= reward.chance;
}

설치하면 아이템이 채워지는 상자
- 빈 상자를 설치하면 필수 아이템 + 랜덤 아이템 1~3종이 자동으로 들어감
- 필수: 다이아몬드 검 1, 다이아몬드 64 / 랜덤 풀: 황금사과 5, 철괴 32, 에메랄드 16, 엔더 진주 4, 화살 64, 익힌 소고기 32
- 뽑은 아이템은 풀에서 빼서(splice) 중복 없음
- 이벤트:
playerPlaceBlock— 놓은 블록이minecraft:chest이면system.runTimeout으로 잠시 뒤 채움
const MIN_RANDOM_ITEMS = 1; // 최소 랜덤 아이템 개수
const MAX_RANDOM_ITEMS = 3; // 최대 랜덤 아이템 개수
function getRandomItems() {
const numItems = Math.floor(Math.random() * (MAX_RANDOM_ITEMS - MIN_RANDOM_ITEMS + 1)) + MIN_RANDOM_ITEMS;
const selectedItems = [];
const availableItems = [...RANDOM_ITEMS];
for (let i = 0; i < numItems && availableItems.length > 0; i++) {
const randomIndex = Math.floor(Math.random() * availableItems.length);
selectedItems.push(availableItems[randomIndex]);
availableItems.splice(randomIndex, 1); // 선택된 아이템 제거하여 중복 방지
}
return selectedItems;
}


황금열쇠로 잠그는 보안 상자
- 모루에서 금 괭이 이름을 ‘황금열쇠’로 바꿈
- 열쇠를 들고 통(배럴) 우클릭 → 채팅에 비밀번호(예: 1234) → 잠김
- 열 때: 통 우클릭 → 채팅에 비밀번호 → 한 번 입력에 한 번 열림 / 틀리면 “잘못된 비밀번호입니다!”
- 해제: 열쇠로 다시 우클릭 → 비밀번호 → “컨테이너의 잠금이 해제되었습니다!”
- 통마다 다른 비밀번호 가능, 잠긴 통은 크리에이티브에서도 못 부숨(
playerBreakBlock에서 막음) - 코드:
SECURITY_KEY = { id: "minecraft:golden_hoe", name: "황금열쇠" }, 대상 블록은minecraft:barrel


블록 상호작용 ①: 마법 지팡이로 효과 받기
- 모루에서 막대기 이름을 ‘마법 지팡이’로 → 블록을 캐지 않고 우클릭
- 다이아몬드 블록 → 점프 강화 / 에메랄드 블록 → 신속 / 금 블록 → 힘 (+ 파티클·효과음·메시지)
- 코드에는 레드스톤·옵시디언·석탄 블록 등도 있고, 묶음을 복사해 블록·명령을 늘림
- 이벤트:
beforeEvents.playerInteractWithBlock+e.isFirstEvent(한 번만 실행) + 아이템 이름(nameTag) 확인
const BLOCK_COMMANDS = {
"minecraft:diamond_block": {
commands: [
'effect @p jump_boost 200 1',
'particle minecraft:totem_particle ~ ~1 ~',
'playsound random.levelup @p'
],
message: "§b점프 부스트를 획득했습니다!"
},
// emerald_block: 'effect @p speed 200 2' / gold_block: 'effect @p strength 300 1' …
};
const MAGIC_WAND = {
typeId: "minecraft:stick",
name: "마법 지팡이"
};

블록 상호작용 ②: 블록을 누르면 메뉴 창
- 빈손으로 다이아몬드·에메랄드 블록 우클릭 → 블록별 메뉴 창 (손에 블록을 들면 그냥 설치됨)
- 다이아몬드 블록 메뉴: 점프 부스트 · 체력 회복 · 발광 효과 (버튼 아이콘은 넣지 않아도 됨)
- 새 블록(예: 금 블록) 추가는 두 곳: ① showBlockUI의 case 묶음 복사 ② 상호작용 이벤트의 블록 조건
case "minecraft:diamond_block":
form.body("다이아몬드 블록 메뉴")
.button("점프 부스트", "textures/items/diamond")
.button("체력 회복", "textures/items/apple")
.button("발광 효과", "textures/items/glowstone_dust");
form.show(player).then((response) => {
if (response.canceled) return;
switch (response.selection) {
case 0: player.runCommandAsync("effect @p jump_boost 200 1"); break;
case 1: player.runCommandAsync("effect @p instant_health 1 1"); break;
case 2: player.runCommandAsync("effect @p glowing 200 1"); break;
}
});
// 이벤트: 첫 이벤트 + 맨손 + 블록 종류
if (e.isFirstEvent) {
if (!item && (block.typeId === "minecraft:diamond_block" || block.typeId === "minecraft:emerald_block")) {
e.cancel = true; // 기본 상호작용 취소
system.run(() => { showBlockUI(player, block.typeId, block.location); });
}
}

경제 교육으로 엮기 (제안)
| 차시 | 활동 | 쓰는 예제 |
| 1 화폐 | 학급 화폐 정하기, 소지금 확인, 친구에게 송금 | 상태창, 송금 |
| 2 시장 | 학생이 관리자가 되어 가격·재고·구매 한도 정하기, 판매가(70%) 계산 | UI 상점 ②, 셜커 상자 세트 |
| 3 금융 | 저축과 이자, 마이너스 통장(부채) 잔고 기록·비교, 이자율 바꿔 보기 | 에메랄드 은행 |
| 4 보상·확률 | 쿠폰 보상 설계, 가챠 확률 계산과 실제 뽑기 결과 비교 | 보상 쿠폰, 가챠 블록, 채워지는 상자 |
교사용 메모 · 수업 메모
- 한 월드에 여러 예제를 함께 넣으면 여는 아이템·채팅 명령이 겹칩니다: 나침반(UI 상점 ①·송금·쿠폰), 에메랄드(UI 상점 ②·은행),
!상점(UI 상점 ②·셜커 상자). 코드의 아이템 ID와 명령 문자열을 서로 다르게 바꾸세요(제안). !은행·!상점·!쿠폰관리같은 채팅 명령은beforeEvents.chatSend를 쓰므로 베타 API 실험 기능이 필요합니다. 교육용 에디션은 월드 설정에 실험 기능 메뉴가 없고 베타 API도 불러오지 않으므로(2026-07 실측) 이 채팅 명령들은 교육용에서 그대로 동작하지 않습니다(‘웹소켓·스크립트 API’ 페이지 참고).- manifest.json 종속성 예(가챠 블록 영상):
@minecraft/server1.17.0-beta,@minecraft/server-gametest1.0.0-beta,@minecraft/server-ui1.4.0-beta. 마인크래프트가 업데이트되면 바뀌므로 제작자 로그에 나온 버전으로 맞춥니다. 설명란도 내려받은 애드온보다 Bridge에서 직접 만들고 코드를 붙여 넣기를 권합니다. - 상태창 예제가 만드는 스코어보드:
scoreboard objectives add money dummy "소지금",scoreboard objectives add level dummy "레벨",scoreboard players set @a money 0,scoreboard players set @a level 0. 송금 예제(sendMoney.js)도 같은 이름money를 쓰므로 두 예제를 함께 쓸 수 있습니다(GitHub 코드 기준). 단,!스코어보드는 모든 플레이어의 money·level을 0으로 되돌리므로 수업 처음에 한 번만 씁니다. - 송금 코드의 숫자 검사
!/[0-9]/g.test(money)는 숫자가 하나라도 있으면 통과합니다(예: 12a). 학생과 함께 고쳐 보는 활동으로 씁니다(제안). - 내림 계산: 상점 판매가는 구매가×0.7을 내려서 1에메랄드 상품 1개는 0, 2개면 1. 은행 이자도
Math.floor라 잔고 99 이하는 이자가 0입니다. 결과를 먼저 예측해 보게 합니다(제안). - 가챠 기본 코드의 두 번째 묶음은
stevecoding:rare_gachabox‘레어 가차박스’(네더라이트 블록 12개 20, 다이아몬드 블록 13개 40, 마법이 부여된 황금사과 1~5개 30)입니다. 영상에서는 이것을 ‘희귀한뽑기’ 블록으로 바꿔 만들었습니다. - 코드로 확인한 세부: 셜커 상자 등록 삭제는
!상자삭제 <이름>. 잠금 상자는 비밀번호를 입력받는 동안 채팅 이벤트를 취소하므로 비밀번호가 채팅창에 보이지 않습니다(secureChest.js 주석 “비밀번호는 채팅창에 표시되지 않습니다”). 가챠 블록의 루트 테이블은 블록 컴포넌트"minecraft:loot": "loot_tables/empty.json"으로 지정합니다(2026-10 공식 문서 기준).