스티브코딩

스크립트 API PvP·몹

체력 표시·킬 기록·묘비 부활·넉백·몹 보상·몹끼리 전투 등 전투 스크립트 8가지

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

한눈에 보기

  • 무엇: PvP(플레이어끼리 대결)와 몹 전투에 쓰는 기능을 스크립트 API(JavaScript)로 만든 예제 모음
  • 누구: 스크립트 API를 한 번 적용해 본 교사, 중·고등학생 코딩 동아리(중급~고급)
  • 몇 분: 기능 하나당 코드 붙여 넣기 10분, 코드 읽기·값 바꾸기 1차시
  • 결과물: 체력 표시, 킬 스코어보드, 묘비 부활, 넉백 망치, 몹 처치 보상, 크기 조절, 몹 전투 시뮬레이터, 우리 편 좀비
  • 대부분 GitHub 코드를 main.js에 붙여 넣고 코드 맨 위 설정 값(아이템·확률·범위)만 바꿔 씁니다. 몹끼리 싸우게 하려면 엔티티 JSON도 함께 넣습니다.
묘비와 관전자(영혼)로 표시되는 PvP 묘비 부활 시스템
묘비와 관전자(영혼)로 표시되는 PvP 묘비 부활 시스템

기능 한눈에 고르기

기능 쓰는 이벤트·파일 영상
이름표 아래 체력 표시 runInterval, health 컴포넌트 [생명치 표시](https://youtu.be/0sNQ9avt85Y)
킬 횟수 기록 chatSend, entityDie, 스코어보드 [킬 기록](https://youtu.be/CePqHZ83YBc)
묘비 부활 entityDie, runInterval, Map, playerLeave [묘비 부활](https://youtu.be/BIV2JrK1GbE)
넉백 망치 itemUse, applyKnockback [넉백](https://youtu.be/h9ShwgxNX_g)
특정 몹 처치 보상 entityDie, 확률(Math.random) [몹 보상](https://youtu.be/8Ge3a5CLcYM)
플레이어 크기 바꾸기 player.json scale, UI 폼 [크기](https://youtu.be/gjaNtZiYW2U)
몹 전투 시뮬레이터 · 우리 편 좀비 엔티티 JSON 공격 대상, spawnEntity [전투](https://youtu.be/dQxmfNDid0U) · [좀비](https://youtu.be/hKpJd5YLDAI)
- 수업 순서 추천: 체력 표시(짧은 반복) → 킬 기록(이벤트) → 묘비 부활(Map·인터벌) → 넉백·보상(설정 값 바꾸기) → 몹 JSON(행동 팩)

시작 전 준비

  1. Bridge에서 행동 팩 프로젝트를 만들고 scripts/main.js 에 GitHub 코드를 붙여 넣기
  2. manifest.json에 @minecraft/server 종속성, UI 창을 쓰는 기능(크기 바꾸기)은 @minecraft/server-ui 도 추가
  3. 채팅 명령(카운트, !부활, !전투, !사이즈)을 쓰는 기능은 월드 실험 기능에서 ‘베타 API’를 켜기
  4. 몹 전투·우리 편 좀비·크기 바꾸기는 엔티티 JSON(행동 팩 entities 폴더)도 넣어야 동작
  • 사용법은 대부분 코드 맨 위 주석에 적혀 있습니다. 먼저 읽고 시작하세요.
  • 받은 애드온(.mcaddon)은 업데이트로 manifest 종속성이 달라지면 안 될 수 있어, Bridge에서 직접 만들고 코드를 붙여 넣기를 권합니다(설명란).
교사용 메모 · 준비 메모: 버전과 베타 API
  • world.beforeEvents.chatSend·afterEvents.chatSend 는 실험(베타) 전용 이벤트라 ‘베타 API’를 켜야 합니다. itemUse, entityDie, playerLeave, runInterval 은 정식(stable) 기능입니다.
  • 교육용 에디션은 ‘베타 API’ 토글이 없고 베타 버전 종속성도 불러오지 않아(2026-07 실측), 채팅 명령이 있는 기능은 교육용에서 그대로는 동작하지 않습니다. 이런 기능은 베드락 에디션 월드에서 쓰고, 교육용 수업에는 아이템 사용·사망 이벤트처럼 정식 기능만 쓰는 예제를 고르세요.
  • 2024~2025년 영상 코드라 runCommandAsync 를 씁니다. @minecraft/server 2.0.0부터 runCommandAsync 는 없어졌으므로 manifest를 2.x로 쓰면 runCommand 로 바꿉니다. 넉백도 2.0.0부터 applyKnockback(x, z, 세기, 높이) 네 값이 applyKnockback({ x: x * 세기, z: z * 세기 }, 높이) 두 값으로 바뀌었고, 묘비의 player.isOp() 는 지금 공식 문서에 없습니다(2026-10 공식 문서 기준). GitHub 최신 코드(knockbackItem.js, PvPtomb.js)는 이미 새 형식과 op·admin 태그 확인으로 고쳐져 있으니 GitHub 코드를 쓰세요.
  • 오류는 설정의 제작자 로그(콘텐츠 로그)로 확인합니다. 코드 맨 위 console.warn('불러옴') 이 로그에 보이면 스크립트가 불러와진 것입니다.

이름표 아래 체력 표시

  • 다른 플레이어 이름 아래에 현재 체력/최대 체력이 나오고, 맞거나 회복하면 바로 바뀝니다(예: 16.5/20).
  • 내 이름이 내 화면에 안 보이듯 내 체력도 내 화면에는 안 보입니다.
  • 코드는 이것뿐입니다. 2틱마다 모든 플레이어의 이름표를 다시 씁니다.
import { world, system } from '@minecraft/server';

system.runInterval(() => { // runInterval을 사용해 반복
    for (const player of world.getAllPlayers()) {
        const playerHp = player.getComponent("minecraft:health").currentValue    // 현재 HP
        const playerMaxHp = player.getComponent("minecraft:health").effectiveMax // 최대 HP
    }
}, 2) // 워치독 방지를 위해 2틱마다 실행
이름 아래에 16.5/20으로 줄어든 체력 (00:13)
이름 아래에 16.5/20으로 줄어든 체력 (00:13)

킬 횟수 기록: 채팅 명령과 스코어보드

  • 채팅에 카운트 → “카운트를 시작합니다.” + 사이드바에 kill 스코어보드, 초기화 → 모두 0
  • 다른 플레이어를 잡으면 kill이 1 올라갑니다. 몹을 잡을 때는 오르지 않습니다.
  • 구독자 플럼(Plumr)님이 제공한 코드를 재구성한 것입니다(영상 자막).
world.afterEvents.chatSend.subscribe(e => {
    const msg = e.message
    if (msg == "카운트") {
        e.sender.runCommand("say 카운트를 시작합니다.")
        e.sender.runCommand("scoreboard objectives add kill dummy kill")
        e.sender.runCommand("scoreboard objectives setdisplay sidebar kill")
        e.sender.runCommand("scoreboard players add @a kill 0") // 새로 온 플레이어도 kill 값 만들기
    }
    if (msg == "초기화") {
        e.sender.runCommand("say 초기화되었습니다.")
        e.sender.runCommand("scoreboard players set @a kill 0")
    }
})
① 채팅에 ‘카운트’ 입력 (00:14)
① 채팅에 ‘카운트’ 입력 (00:14)
② 상대를 잡아 kill 1 (00:27)
② 상대를 잡아 kill 1 (00:27)

킬 횟수 기록: 죽음 이벤트로 세기

  • entityDie 에서 죽인 엔티티(damageSource.damagingEntity)와 죽은 엔티티(deadEntity)를 받습니다.
  • 둘 다 플레이어이면 죽인 플레이어가 명령을 실행하므로 @s 가 곧 죽인 사람입니다.
  • 아래쪽 예시처럼 nameTag 로 “이름이 ‘스티브’인 플레이어를 잡았을 때”만 동작하게 할 수도 있습니다.
world.afterEvents.entityDie.subscribe(e => {
    const kill = e.damageSource.damagingEntity // 죽인 엔티티
    const die = e.deadEntity                   // 죽은 엔티티
    if (kill.typeId == "minecraft:player") {
        if (die.typeId == "minecraft:player") {
            world.sendMessage(`플레이어가 사망했습니다.`)
            kill.runCommand('scoreboard players add @s kill 1')
        }
    }
    // 추가 예시: if (die.nameTag == "스티브") { kill.runCommand('say 스티브가 죽었습니다.') }
})
  • 낙사·용암처럼 죽인 엔티티가 없으면 kill 이 비어 오류가 날 수 있습니다. kill?.typeId 로 쓰면 안전합니다(몹 보상 코드가 이 방식).

묘비 부활: 동작과 명령어

  • 플레이어가 죽으면 그 자리에 ‘○○의 묘비’ 아머 스탠드가 생기고, 죽은 플레이어는 관전자 모드가 되어 파티클로 표시됩니다.
  • 다른 플레이어가 묘비를 부수면 그 자리에서 부활 → “○○님이 다시 전투에 참여합니다!”
  • 관리자(OP)는 채팅에 !부활 플레이어이름 으로 강제 부활(예: !부활 플레이어) → “관리자가 ○○님을 부활시켰습니다!”
  • 여러 명이 동시에 죽어도 각자의 묘비가 생기고, 죽은 플레이어끼리는 서로를 볼 수 있습니다.
① 죽은 자리에 생긴 묘비 (00:10)
① 죽은 자리에 생긴 묘비 (00:10)
② 묘비를 부숴 부활 (00:41)
② 묘비를 부숴 부활 (00:41)
③ 관리자 !부활 명령 (00:56)
③ 관리자 !부활 명령 (00:56)

묘비 부활: 코드 구조

  • 사망: 관전자 모드 → 이름표에 표시 → 묘비 이름을 단 아머 스탠드 소환 → 플레이어마다 runInterval 을 Map 에 저장
  • 인터벌: 그 이름의 아머 스탠드가 없으면(부서졌으면) 묘비 위치로 이동 → 서바이벌 → 이름 되돌리기 → 알림
  • 퇴장(playerLeave): 인터벌과 파티클을 지워야 합니다. 없으면 나간 플레이어 위치에 파티클을 만들려다 오류가 계속 납니다.
world.afterEvents.entityDie.subscribe((event) => {
    const entity = event.deadEntity;
    if (entity.typeId === "minecraft:player") {
        entity.runCommandAsync("gamemode spectator @s");   // 관전자 모드로 변경
        const location = entity.location;
        const graveName = `§8† §7${entity.name}의 묘비 §8†`;
        entity.runCommandAsync(`summon armor_stand "${graveName}" ${location.x} ${location.y} ${location.z}`);
        const armorStandCheck = system.runInterval(() => {
            const armorStands = [...world.getDimension("overworld").getEntities({ type: "armor_stand", name: graveName })];
            if (armorStands.length === 0 && tombMap.has(entity.name)) {  // 묘비가 부서졌다면
                entity.teleport(tombMap.get(entity.name).location);
                entity.runCommandAsync("gamemode survival @s");
                entity.nameTag = entity.name;
            }
        });
    }
});
  • 위 코드는 화면에서 핵심 줄만 옮긴 것입니다. 전체(Map 저장, !부활 명령, 파티클)는 PvPtomb.js(자료 링크)를 보세요.

넉백 망치: 주변 몹 밀어내기

  • 철퇴(mace)를 모루에서 ‘넉백의 망치’ 로 이름을 바꿔 들고 우클릭 → 반경 10칸 안의 엔티티가 뒤로 밀려납니다.
  • 맨 위 KNOCKBACK_CONFIG 에서 아이템·이름·범위·세기·위로 뜨는 힘·파티클 반지름을 바꿉니다. 조금 위로 떠야 뒤로 잘 밀립니다.
  • 원리: 엔티티가 플레이어를 바라보게(lookAt) 한 뒤 그 방향의 반대(-x, -z)로 넉백을 줍니다.
const KNOCKBACK_CONFIG = {
    ITEM: "minecraft:mace",
    ITEM_NAME: "넉백의 망치",
    RANGE: 10,           // 범위 (블록)
    POWER: 3,            // 넉백 세기
    HEIGHT: 0.7,         // 위로 뜨는 힘
    PARTICLE_RADIUS: 1.5 // 파티클 원형 효과의 반지름
};
// itemUse 안에서 (아이템 타입과 이름이 모두 맞을 때)
for (const entity of dimension.getEntities({ location: player.location, maxDistance: KNOCKBACK_CONFIG.RANGE, excludeNames: [player.name] })) {
    try {
        entity.lookAt(player.location)              // 먼저 엔티티가 플레이어를 바라보게 함
        const { x, z } = entity.getViewDirection()  // 엔티티의 새로운 방향을 가져옴
        entity.applyKnockback(-x, -z, KNOCKBACK_CONFIG.POWER, KNOCKBACK_CONFIG.HEIGHT)
    } catch (error) {
        continue; // 넉백이 지원되지 않는 엔티티는 무시
    }
}
① 넉백 망치로 좀비 무리를 밀어낸 장면 (00:00)
① 넉백 망치로 좀비 무리를 밀어낸 장면 (00:00)
② 설정 값과 아이템 사용 이벤트 (00:16)
② 설정 값과 아이템 사용 이벤트 (00:16)

특정 몹을 잡으면 특별한 보상

  • 좀비 → 썩은 고기 2개(100%) + 가끔 철괴, 변명자 → 에메랄드 1개(100%) + 다이아몬드(30%)
  • MOB_REWARDS 에 몹 ID와 itemId·amount(개수)·chance(1~100 확률)를 적어 보상을 추가합니다.
  • 채팅에 한글로 나오게 하려면 getKoreanItemName 의 nameMap 에 아이템 이름을 넣습니다(예: "minecraft:gunpowder": "화약").
const MOB_REWARDS = {
    "minecraft:vindicator": [
        { itemId: "minecraft:emerald", amount: 1, chance: 100 },     // 100% 확률로 에메랄드 1개
        { itemId: "minecraft:diamond", amount: 1, chance: 30 }       // 30% 확률로 다이아몬드 1개
    ],
    "minecraft:zombie": [
        { itemId: "minecraft:rotten_flesh", amount: 2, chance: 100 }, // 100% 확률로 썩은 고기 2개
        { itemId: "minecraft:iron_ingot", amount: 1, chance: 10 }     // 10% 확률로 철괴 1개
    ],
    "minecraft:skeleton": [
        { itemId: "minecraft:bone", amount: 2, chance: 100 },         // 100% 확률로 뼈 2개
        { itemId: "minecraft:arrow", amount: 5, chance: 50 }          // 50% 확률로 화살 5개
    ]
    // 여기에 더 많은 몹과 보상을 추가할 수 있습니다
};
① 좀비 처치 → 썩은 고기 2개 (00:08)
① 좀비 처치 → 썩은 고기 2개 (00:08)
② 변명자 처치 → 에메랄드·다이아몬드 (00:22)
② 변명자 처치 → 에메랄드·다이아몬드 (00:22)

몹 보상: 확률 계산과 지급

  • entityDie 에서 죽인 것이 플레이어인지 확인(killer?.typeId) → 죽은 몹의 보상 목록을 찾아 giveRewards 로 지급
  • Math.random() * 100 이 chance 이하일 때만 아이템을 줍니다 → 같은 몹이어도 보상이 매번 다릅니다.
world.afterEvents.entityDie.subscribe((event) => {
    const deadEntity = event.deadEntity;
    const killer = event.damageSource.damagingEntity;
    if (killer?.typeId === "minecraft:player") {            // 킬러가 플레이어인지 확인
        const mobRewards = MOB_REWARDS[deadEntity.typeId];  // 죽은 몹의 보상 정보
        if (mobRewards) giveRewards(killer, mobRewards);
    }
});

function giveRewards(player, rewards) {
    rewards.forEach(reward => {
        if (Math.random() * 100 <= reward.chance) {         // 확률 계산
            const item = new ItemStack(reward.itemId, reward.amount);
            player.getComponent("inventory").container.addItem(item);
            player.sendMessage(`§a${getKoreanItemName(reward.itemId)} ${reward.amount}개를 획득했습니다!`);
        }
    });
}
  • 수학 확률 단원과 이어 보기: 같은 몹을 20번 잡아 실제로 나온 비율을 기록하고 chance 와 비교합니다(제안).

플레이어 크기 바꾸기

  • 종이(paper)를 들고 사용하거나 채팅에 !사이즈 → ‘사이즈 설정 메뉴’(사이즈 설정 / 사이즈 초기화)
  • 크기는 1/10~10배, 기본 1.0배(슬라이더 값 10). 바꾸면 “[ Scale ] 사이즈가 ○으로 변경되었습니다.”
  • 바닐라 player.json에 없는 minecraft:scale 컴포넌트를 넣은 player.json(scaleplayer.json)이 꼭 필요합니다.
// player.json "components" 안에 추가:  "minecraft:scale": { "value": 0.1 }
system.runInterval(() => {
    for (const player of world.getAllPlayers()) {
        const value = player.getDynamicProperty(`scaleValue`)    // 저장된 크기 값 불러오기
        const ScaleComponent = player.getComponent("minecraft:scale")
        if (value) {
            ScaleComponent.value = value / 10   // 실제 크기는 설정값의 1/10
        } else {
            ScaleComponent.value = 1.0          // 기본 크기
        }
    }
}, 5) // 5틱마다 실행
① 거인이 된 플레이어와 보통 크기 비교 (00:30)
① 거인이 된 플레이어와 보통 크기 비교 (00:30)
② 아주 작아진 플레이어 (00:45)
② 아주 작아진 플레이어 (00:45)

몹 전투 시뮬레이터: 워든 2 VS 좀비 100

채팅 입력 하는 일
`!전투설정 [첫번째몹번호] [두번째몹번호]` 대결할 몹 고르기 (1 워든, 2 좀비, 3 스켈레톤, 4 철골렘, 5 변명자)
`!전투 [첫번째몹수] [두번째몹수]` 마릿수를 정해 소환하고 카운트다운 후 전투 시작 (예: 워든 2, 좀비 100)
- 화면 아래에 남은 수(`워든: 2 좀비: 96`)가 실시간으로 나옵니다. 삽을 들고 우클릭하면 시점을 바꿉니다. - 너무 많이 소환하면 “경고: 너무 많은 몬스터를 소환하면 게임 성능이 저하될 수 있습니다!” — 교실 PC 사양에 맞춰 마릿수를 정하세요. - 몹을 늘리려면 main.js의 `availableMobs` 에 `{ id, name, displayName }` 한 줄을 더합니다.
① 몹 목록과 !전투 입력 (00:12)
① 몹 목록과 !전투 입력 (00:12)
② 워든 2마리와 좀비 100마리 소환 (00:24)
② 워든 2마리와 좀비 100마리 소환 (00:24)
③ 결과: 워든 승리 (00:53)
③ 결과: 워든 승리 (00:53)

몹끼리 싸우게 하기: 엔티티 JSON의 공격 대상

  • 몹이 누구를 공격할지는 스크립트가 아니라 행동 팩 엔티티 JSON의 minecraft:behavior.nearest_attackable_target 이 정합니다.
  • 전투 시뮬레이터: warden.json 은 “워든이 아닌 모든 몹”을 공격, zombie.json·skeleton.json 도 같은 방식으로 고침(철골렘은 원래 몬스터를 공격)
  • 우리 편 좀비(custom:friendly_zombie): 좀비·스켈레톤·언데드만 공격하고, 플레이어와 같은 편은 공격하지 않음
// warden.json (전투 시뮬레이터)
"minecraft:behavior.nearest_attackable_target": {
    "priority": 2, "reselect_targets": true, "must_see": true, "within_radius": 100.0,
    "entity_types": [{ "filters": { "all_of": [
        { "test": "is_family", "subject": "other", "operator": "!=", "value": "warden" },
        { "test": "is_family", "subject": "other", "operator": "!=", "value": "inanimate" }
    ] }, "max_dist": 100 }]
}
// friendly_zombie.json (우리 편 좀비) — filters 부분
"any_of": [
    { "test": "is_family", "subject": "other", "value": "zombie" },
    { "test": "is_family", "subject": "other", "value": "skeleton" },
    { "test": "is_family", "subject": "other", "value": "undead" }
]
① warden.json 공격 대상 필터 (01:10)
① warden.json 공격 대상 필터 (01:10)
② friendly_zombie.json 언데드 대상 (01:57)
② friendly_zombie.json 언데드 대상 (01:57)

좀비를 공격하는 좀비 소환하기

  • 모루에서 뼈 이름을 ‘강령술사의 뼈’ 로 바꿔 들고 우클릭 → 앞에 초록 이름표 ‘아군 좀비’ 2마리 소환
  • 밤이 되면 플레이어 대신 좀비·스켈레톤을 공격합니다(팔·걸음은 조금 어색함).
  • 필요한 파일 3개: 행동 팩 entities/friendly_zombie.json, 리소스 팩 entity/friendly_zombie.entity.json, animations/friendly_zombie.animation.json (모습은 바닐라 좀비와 같음)
const SUMMON_ITEM = "minecraft:bone";
const SUMMON_ITEM_NAME = "강령술사의 뼈";

world.afterEvents.itemUse.subscribe((event) => {
    const player = event.source;
    const item = event.itemStack;
    if (item.typeId === SUMMON_ITEM && item.nameTag === SUMMON_ITEM_NAME) {
        const viewDirection = player.getViewDirection();      // 플레이어가 바라보는 방향
        for (let i = 0; i < 2; i++) {                          // 플레이어 앞에 좀비 두 마리
            const offsetX = (i === 0) ? -1 : 1;
            const spawnLocation = {
                x: player.location.x + (viewDirection.x * 2) + offsetX,
                y: player.location.y,
                z: player.location.z + (viewDirection.z * 2)
            };
            const zombie = player.dimension.spawnEntity("custom:friendly_zombie", spawnLocation);
            zombie.nameTag = "§a아군 좀비";
        }
    }
});
① 모루에서 뼈 이름 바꾸기 (00:08)
① 모루에서 뼈 이름 바꾸기 (00:08)
② 아군 좀비 2마리 소환 (00:17)
② 아군 좀비 2마리 소환 (00:17)
③ 밤에 언데드를 공격 (00:38)
③ 밤에 언데드를 공격 (00:38)

수업 팁

  • 값 바꾸기부터: 넉백 범위·세기, 보상 확률, 크기 값처럼 코드 맨 위 설정만 바꿔 보게 하면 처음 하는 학생도 결과를 바로 봅니다.
  • 스크립트 vs JSON: “언제 무엇을 할지”는 스크립트(이벤트), “몹이 누구를 공격할지”는 엔티티 JSON이 정한다는 차이를 우리 편 좀비로 비교합니다.
  • 나만의 소환수: any_of 목록을 바꿔 ‘거미만 공격하는 골렘’ 같은 소환수를 설계해 봅니다(제안).
  • 실험 수업: 전투 시뮬레이터로 몹 조합·마릿수를 바꿔 결과를 예측하고 기록합니다(제안). 100마리 이상은 교실 PC가 느려질 수 있습니다.
  • 협동 규칙: 묘비 부활은 쓰러진 팀원을 구하는 팀 게임 규칙으로 쓰기 좋습니다(제안).

관련 영상

관련 공식 자료

함께 보기

출처

  • 유튜브 「PvP에서 킬 횟수 기록하기」(2024-05-23)