스티브코딩

스크립트 API 상점·경제

스크립트 API로 상점·송금·은행·쿠폰·가챠·잠금 상자 만들기

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

한눈에 보기

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

준비: 코드를 애드온으로 적용하기

  1. Bridge 새 프로젝트(실험 기능 베타 API 켜기) → scripts/main.js에 GitHub 코드 붙여 넣기
  2. 월드 설정: 행동 팩 적용 + 실험 기능 ‘베타 API’ 켜기 → 코드를 고친 뒤에는 /reload
  3. 관리자 메뉴가 있는 예제(상점 관리·쿠폰 관리): /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);
    }
});
!내정보로 연 상태창 — 이름·소지금·레벨 (01:12)
!내정보로 연 상태창 — 이름·소지금·레벨 (01:12)

송금: 다른 플레이어에게 돈 보내기

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

송금 코드: 입력 검사 순서

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;
① 입금할 에메랄드 수 입력 (00:28)
① 입금할 에메랄드 수 입력 (00:28)
② 1분 뒤 101 에메랄드 — 이자율·마이너스 한도 표시 (00:42)
② 1분 뒤 101 에메랄드 — 이자율·마이너스 한도 표시 (00:42)

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 상점 (00:06)
나침반으로 연 UI 상점 (00:06)

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: "금괴" }
];
① 관리자: 아이템 관리 (00:24)
① 관리자: 아이템 관리 (00:24)
② 손님: 아이템 구매 — 가격·재고 (01:15)
② 손님: 아이템 구매 — 가격·재고 (01:15)
③ 판매가는 구매가의 70% — 사과는 0 (01:48)
③ 판매가는 구매가의 70% — 사과는 0 (01:48)

셜커 상자 세트 판매

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

보상 쿠폰

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

가챠 블록 ①: 부수면 랜덤 보상이 나오는 블록

  1. Bridge 새 프로젝트(베타 API) → 새 파일 → 빈 블록: 식별자 stevecoding:gacha_normal, 이름 ‘보통뽑기’
  2. 블록 컴포넌트에 루트 테이블로 빈 파일 loot_tables/empty.json(내용 {}) 지정 → 부숴도 블록이 안 떨어짐
  3. main.js에 gachablock.js 붙여 넣기 → 블록 식별자·이름을 코드와 똑같이
  4. 블록 그림: 리소스 팩 textures/blocks/gacha 의 16×16 이미지(바꿔도 됨)
  5. 월드에 행동·리소스 팩 + 베타 API → /give로 블록 받기 → 서바이벌에서 부수기
보통뽑기를 부숴 다이아몬드 2개 획득 (03:15)
보통뽑기를 부숴 다이아몬드 2개 획득 (03:15)

가챠 블록 ②: 보상과 확률 바꾸기

  • 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;
}
main.js의 가챠 종류별 보상 설정 (01:31)
main.js의 가챠 종류별 보상 설정 (01:31)

설치하면 아이템이 채워지는 상자

  • 빈 상자를 설치하면 필수 아이템 + 랜덤 아이템 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;
}
① 설치하자 채워진 상자 (00:08)
① 설치하자 채워진 상자 (00:08)
② 필수 아이템·랜덤 풀·개수 설정 (00:45)
② 필수 아이템·랜덤 풀·개수 설정 (00:45)

황금열쇠로 잠그는 보안 상자

  1. 모루에서 금 괭이 이름을 ‘황금열쇠’로 바꿈
  2. 열쇠를 들고 통(배럴) 우클릭 → 채팅에 비밀번호(예: 1234) → 잠김
  3. 열 때: 통 우클릭 → 채팅에 비밀번호 → 한 번 입력에 한 번 열림 / 틀리면 “잘못된 비밀번호입니다!”
  4. 해제: 열쇠로 다시 우클릭 → 비밀번호 → “컨테이너의 잠금이 해제되었습니다!”
  • 통마다 다른 비밀번호 가능, 잠긴 통은 크리에이티브에서도 못 부숨(playerBreakBlock에서 막음)
  • 코드: SECURITY_KEY = { id: "minecraft:golden_hoe", name: "황금열쇠" }, 대상 블록은 minecraft:barrel
① 황금열쇠(금 괭이)와 통 (00:11)
① 황금열쇠(금 괭이)와 통 (00:11)
② 비밀번호 설정·해제·오류 메시지 (00:54)
② 비밀번호 설정·해제·오류 메시지 (00:54)

블록 상호작용 ①: 마법 지팡이로 효과 받기

  • 모루에서 막대기 이름을 ‘마법 지팡이’로 → 블록을 캐지 않고 우클릭
  • 다이아몬드 블록 → 점프 강화 / 에메랄드 블록 → 신속 / 금 블록 → 힘 (+ 파티클·효과음·메시지)
  • 코드에는 레드스톤·옵시디언·석탄 블록 등도 있고, 묶음을 복사해 블록·명령을 늘림
  • 이벤트: 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: "마법 지팡이"
};
마법 지팡이로 다이아몬드 블록 우클릭 → 점프 부스트 (00:12)
마법 지팡이로 다이아몬드 블록 우클릭 → 점프 부스트 (00:12)

블록 상호작용 ②: 블록을 누르면 메뉴 창

  • 빈손으로 다이아몬드·에메랄드 블록 우클릭 → 블록별 메뉴 창 (손에 블록을 들면 그냥 설치됨)
  • 다이아몬드 블록 메뉴: 점프 부스트 · 체력 회복 · 발광 효과 (버튼 아이콘은 넣지 않아도 됨)
  • 새 블록(예: 금 블록) 추가는 두 곳: ① 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); });
    }
}
다이아몬드 블록 메뉴 창 (00:06)
다이아몬드 블록 메뉴 창 (00:06)

경제 교육으로 엮기 (제안)

차시 활동 쓰는 예제
1 화폐 학급 화폐 정하기, 소지금 확인, 친구에게 송금 상태창, 송금
2 시장 학생이 관리자가 되어 가격·재고·구매 한도 정하기, 판매가(70%) 계산 UI 상점 ②, 셜커 상자 세트
3 금융 저축과 이자, 마이너스 통장(부채) 잔고 기록·비교, 이자율 바꿔 보기 에메랄드 은행
4 보상·확률 쿠폰 보상 설계, 가챠 확률 계산과 실제 뽑기 결과 비교 보상 쿠폰, 가챠 블록, 채워지는 상자
- 영상에는 수업 사례가 없어 모두 제안입니다. 교사가 애드온을 미리 적용한 월드에서 시작합니다(제안). - 코딩 수업이면 값 바꾸기 → 묶음 복사해 늘리기 → 조건 고치기 순으로 난이도를 올립니다(제안).
교사용 메모 · 수업 메모
  • 한 월드에 여러 예제를 함께 넣으면 여는 아이템·채팅 명령이 겹칩니다: 나침반(UI 상점 ①·송금·쿠폰), 에메랄드(UI 상점 ②·은행), !상점(UI 상점 ②·셜커 상자). 코드의 아이템 ID와 명령 문자열을 서로 다르게 바꾸세요(제안).
  • !은행·!상점·!쿠폰관리 같은 채팅 명령은 beforeEvents.chatSend를 쓰므로 베타 API 실험 기능이 필요합니다. 교육용 에디션은 월드 설정에 실험 기능 메뉴가 없고 베타 API도 불러오지 않으므로(2026-07 실측) 이 채팅 명령들은 교육용에서 그대로 동작하지 않습니다(‘웹소켓·스크립트 API’ 페이지 참고).
  • manifest.json 종속성 예(가챠 블록 영상): @minecraft/server 1.17.0-beta, @minecraft/server-gametest 1.0.0-beta, @minecraft/server-ui 1.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 공식 문서 기준).

관련 영상

관련 공식 자료

함께 보기

출처

  • 유튜브 「언제 어디든 아이템을 살 수 있는 UI 상점」(2024-04-14)