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

기능 한눈에 고르기
| 기능 | 쓰는 이벤트·파일 | 영상 |
| 이름표 아래 체력 표시 | 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) |
시작 전 준비
- Bridge에서 행동 팩 프로젝트를 만들고
scripts/main.js에 GitHub 코드를 붙여 넣기 - manifest.json에
@minecraft/server종속성, UI 창을 쓰는 기능(크기 바꾸기)은@minecraft/server-ui도 추가 - 채팅 명령(
카운트,!부활,!전투,!사이즈)을 쓰는 기능은 월드 실험 기능에서 ‘베타 API’를 켜기 - 몹 전투·우리 편 좀비·크기 바꾸기는 엔티티 JSON(행동 팩
entities폴더)도 넣어야 동작
- 사용법은 대부분 코드 맨 위 주석에 적혀 있습니다. 먼저 읽고 시작하세요.
- 받은 애드온(.mcaddon)은 업데이트로 manifest 종속성이 달라지면 안 될 수 있어, Bridge에서 직접 만들고 코드를 붙여 넣기를 권합니다(설명란).
교사용 메모 · 준비 메모: 버전과 베타 API
world.beforeEvents.chatSend·afterEvents.chatSend는 실험(베타) 전용 이벤트라 ‘베타 API’를 켜야 합니다.itemUse,entityDie,playerLeave,runInterval은 정식(stable) 기능입니다.- 교육용 에디션은 ‘베타 API’ 토글이 없고 베타 버전 종속성도 불러오지 않아(2026-07 실측), 채팅 명령이 있는 기능은 교육용에서 그대로는 동작하지 않습니다. 이런 기능은 베드락 에디션 월드에서 쓰고, 교육용 수업에는 아이템 사용·사망 이벤트처럼 정식 기능만 쓰는 예제를 고르세요.
- 2024~2025년 영상 코드라
runCommandAsync를 씁니다.@minecraft/server2.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틱마다 실행

킬 횟수 기록: 채팅 명령과 스코어보드
- 채팅에
카운트→ “카운트를 시작합니다.” + 사이드바에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")
}
})


킬 횟수 기록: 죽음 이벤트로 세기
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)는 채팅에
!부활 플레이어이름으로 강제 부활(예:!부활 플레이어) → “관리자가 ○○님을 부활시켰습니다!” - 여러 명이 동시에 죽어도 각자의 묘비가 생기고, 죽은 플레이어끼리는 서로를 볼 수 있습니다.



묘비 부활: 코드 구조
- 사망: 관전자 모드 → 이름표에 표시 → 묘비 이름을 단 아머 스탠드 소환 → 플레이어마다
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; // 넉백이 지원되지 않는 엔티티는 무시
}
}


특정 몹을 잡으면 특별한 보상
- 좀비 → 썩은 고기 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개
]
// 여기에 더 많은 몹과 보상을 추가할 수 있습니다
};


몹 보상: 확률 계산과 지급
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틱마다 실행


몹 전투 시뮬레이터: 워든 2 VS 좀비 100
| 채팅 입력 | 하는 일 |
| `!전투설정 [첫번째몹번호] [두번째몹번호]` | 대결할 몹 고르기 (1 워든, 2 좀비, 3 스켈레톤, 4 철골렘, 5 변명자) |
| `!전투 [첫번째몹수] [두번째몹수]` | 마릿수를 정해 소환하고 카운트다운 후 전투 시작 (예: 워든 2, 좀비 100) |



몹끼리 싸우게 하기: 엔티티 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" }
]


좀비를 공격하는 좀비 소환하기
- 모루에서 뼈 이름을 ‘강령술사의 뼈’ 로 바꿔 들고 우클릭 → 앞에 초록 이름표 ‘아군 좀비’ 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아군 좀비";
}
}
});



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