본문으로 건너뛰기
버전: Minecraft 26.2

자라는 블록

크리스털 새싹은 0단계에서 3단계까지 5초마다 한 단계씩 자랍니다. 다 자란 새싹을 사용하면 크리스털 3개를 얻고 0단계로 돌아갑니다.

이를 위해 다음 세 가지를 사용합니다.

무엇하는 일
블록 상태 속성 stage현재 몇 단계인지 나타냅니다. 단계마다 모양, 밝기, 크기가 다릅니다
블록 엔티티블록마다 저장되는 데이터(growth)와 매 틱 실행되는 코드를 담습니다
블록 동작플레이어가 블록을 사용할 때 실행할 일을 정합니다

새싹 클래스​

새 파일 CrystalSprout.java를 만듭니다.

src/main/java/com/example/crystalgarden/CrystalSprout.java
final class CrystalSprout {

static final BlockProperty.IntegerProperty STAGE = BlockProperty.integer("stage", 0, 3);
static final int RIPE = 3;
/** Ticks per stage: 100 ticks are 5 seconds. */
static final int TICKS_PER_STAGE = 100;

static final int CRYSTALS_PER_HARVEST = 3;

private CrystalSprout() {}

static void declare(ContentRegistrar content) {
content.block(
"crystal_sprout",
BlockDefinition.builder()
.strength(0.5f, 0.5f)
.sound(BlockSound.AMETHYST)
.noOcclusion()
.shape(state -> BlockShape.box(3, 0, 3, 13, 4 + 3 * state.get(STAGE), 13))
.lightLevel(state -> 2 + 3 * state.get(STAGE))
.properties(STAGE)
.behavior(new SproutBehavior())
.blockEntity(BlockEntityDefinition.builder()
.ticker(CrystalSprout::grow)
// The client draws the growth bar from this data (CrystalGardenClient).
.syncToClients()
.build())
.item(ItemDefinition.builder()
.creativeTab(CreativeTab.NATURAL_BLOCKS)
.build())
.build());
}
  • .properties(STAGE): 블록 상태에 stage=0..3을 추가합니다.
  • .shape(state -> …), .lightLevel(state -> …): 단계가 오를수록 블록이 커지고 밝아집니다. 이 값은 게임이 블록을 만들 때 상태마다 한 번씩 계산합니다.
  • .noOcclusion(): 꽉 찬 정육면체가 아니므로 옆 블록의 면을 가리지 않도록 합니다.
  • .blockEntity(...): 이 블록마다 블록 엔티티를 붙입니다. syncToClients()는 데이터를 클라이언트로 보내며, 클라이언트 코드에서 성장 표시를 그릴 때 이 데이터를 사용합니다.

매 틱 자라기​

CrystalSprout.java (이어서)
/** Runs every server tick for every sprout. */
static void grow(BlockEntityView sprout) {
WorldView world = sprout.world();
int stage = world.stateAt(sprout.pos()).get(STAGE);
if (stage >= RIPE) {
return;
}
DataTree data = sprout.data();
int growth = data.getInt("growth", 0) + 1;
if (growth >= TICKS_PER_STAGE) {
growth = 0;
world.setProperty(sprout.pos(), STAGE, stage + 1);
}
data.putInt("growth", growth);
// markChanged() saves the data and, with syncToClients(), sends it to clients. Once a second is
// enough for a growth bar, and sending every tick would cost network traffic for nothing.
if (growth % 20 == 0) {
sprout.markChanged();
}
}

ticker는 서버에서 매 틱(1초에 20번) 실행됩니다. 이미 다 자란 새싹은 아무 일도 하지 않고 곧바로 반환합니다.

매 틱 코드는 가볍게

새싹이 수백 개라면 이 메서드는 1초에 수천 번 호출됩니다. markChanged()는 저장 표시와 함께 클라이언트로 데이터를 보내므로 1초에 한 번(growth % 20 == 0)만 호출합니다. 단계가 바뀌는 것은 setProperty가 따로 알리기 때문에 화면 갱신이 늦어지지 않습니다.

쓰면 수확하기​

CrystalSprout.java (이어서)
/** Using a ripe sprout harvests it; using a growing one tells how far along it is. */
static final class SproutBehavior implements BlockBehavior {
@Override
public InteractionResult onUse(BlockUse use) {
WorldView world = use.world();
if (world.isClientSide()) {
return InteractionResult.SUCCESS;
}
int stage = world.stateAt(use.pos()).get(STAGE);
if (stage >= RIPE) {
harvest(world, use.pos(), use.player());
} else {
int growth = world.blockEntityAt(use.pos())
.map(entity -> entity.data().getInt("growth", 0))
.orElse(0);
use.player()
.sendMessage("Crystal sprout: stage " + stage + "/" + RIPE + ", "
+ growth * 100 / TICKS_PER_STAGE + "% to the next stage");
}
return InteractionResult.SUCCESS;
}
}
}

onUse는 클라이언트와 서버 양쪽에서 호출됩니다. 게임 상태는 서버에서만 바꾸므로 isClientSide()이면 바로 반환합니다. harvest는 다음 페이지에서 수확 기록과 함께 완성합니다. 지금은 아래 코드만 두고 넘어가도 됩니다.

CrystalSprout.java (임시)
static void harvest(WorldView world, BlockPos pos, PlayerView player) {
world.setProperty(pos, STAGE, 0);
player.give(CrystalGardenMod.CRYSTAL, CRYSTALS_PER_HARVEST);
}

엔트리포인트에 연결​

CrystalGardenMod.java
static final ResourceId CRYSTAL_SPROUT = ResourceId.of(MOD_ID, "crystal_sprout");

// onInitializeCommon 안, 크리스털 블록 선언 다음에
CrystalSprout.declare(mod.content());

단계별 모양​

단계마다 다른 모델을 쓰도록 블록 상태 파일을 직접 작성합니다. 모델은 바닐라 자수정 싹의 것을 빌려 씁니다.

src/main/resources/assets/crystal_garden/blockstates/crystal_sprout.json
{
"variants": {
"stage=0": {"model": "minecraft:block/small_amethyst_bud"},
"stage=1": {"model": "minecraft:block/medium_amethyst_bud"},
"stage=2": {"model": "minecraft:block/large_amethyst_bud"},
"stage=3": {"model": "minecraft:block/amethyst_cluster"}
}
}
src/main/resources/assets/crystal_garden/items/crystal_sprout.json
{"model": {"type": "minecraft:model", "model": "minecraft:item/small_amethyst_bud"}}

나머지(드롭, 영어 이름, 곡괭이 태그)는 polyResources에 맡깁니다. 직접 쓴 블록 상태와 아이템 파일은 그대로 사용됩니다.

build.gradle.kts
polyResources {
// …crystal, crystal_block…
block("crystal_sprout") {
texture = "minecraft:block/budding_amethyst"
minedWith = "pickaxe"
}
}

ko_kr.json에는 "block.crystal_garden.crystal_sprout": "크리스털 새싹"을 추가합니다.

확인​

runClient로 게임을 실행하고 자연 블록 탭에서 크리스털 새싹을 꺼내 설치합니다. 새싹은 5초마다 커지고 밝아지며, 다 자란 뒤 손으로 사용하면 크리스털 3개를 얻습니다. 자라는 중에 사용하면 현재 몇 퍼센트까지 자랐는지 알려 줍니다.

블록 상태와 블록 엔티티의 다른 기능(인벤토리, 메뉴, 데이터 버전)은 블록과 블록 엔티티와 메뉴에서 설명합니다.

다음 단계​

저장 데이터와 명령어에서 수확을 기록합니다.

Buy me a coffee