ENFRKOantony langlois
프로젝트

Minecraft mod - The Creepy Walker

바로가기기타JavaMinecraftForge
The Creepy Walker attack showcase
The Creepy Walker animation showcase

Minecraft 공포 몹 · 서기, 웅크리기, 기어가기를 전환하며 어떤 공간이든 플레이어를 쫓아오는 지형 인식형 괴물 · 세이브 데이터를 통해 월드당 하나만 존재 · Java · Forge

아키텍처

Forge client
클라이언트 · 렌더링
엔티티 렌더러
entity/client · MobRenderer
매 프레임 walker 렌더링 · 사망 시 기본 몸 회전을 막아 쓰러진 자리에 그대로 유지
Blockbench
클라이언트 · 지오메트리
계층형 모델
entity/client · 18 bones
Blockbench에서 내보낸 스켈레톤 · 제한된 머리 yaw와 pitch로 플레이어를 계속 응시
Java keyframes
클라이언트 · 애니메이션
키프레임 클립
entity/animations
클립 14개: 걷기, 웅크리기, 기어가기, 대기, 공격, 생성, 사망, 자세 전환
server tick
핵심 · 서버 AI
TheCreepyWalkerEntity
entity/custom · Monster subclass
매 틱 위와 앞의 블록을 탐지하고 자세를 선택합니다: stand, sneak 또는 crawl
자세 상태 기계히트박스 크기 조정근접 공격 목표피해 규칙스폰 검사
Minecraft
외부 · 게임 런타임
Forge + 기본 엔진
모드가 연결되는 이벤트 버스, AI 목표 스케줄러, 자연 생성 엔진
Forge events
서버 · 생성 검사
생성 검사기
event · SpawnPlacements
canSpawnHere: 지하의 단단한 땅 위이며 하늘이 보이지 않고 다른 walker가 없을 때만 허용
SavedData
저장소 · 월드 세이브
싱글턴 월드 데이터
worlddata · NBT
월드 세이브에 불리언 exists 저장 · 재시작과 청크 언로드 후에도 유지
JSON
저장소 · 데이터팩
Biome modifier
data/forge/biome_modifier
모든 오버월드 바이옴의 생성 목록에 가중치 100으로 walker 추가

기본 스폰 엔진biome modifier의 스폰 목록에서 walker를 선택한 뒤 스폰 검사기에 생성 가능 여부를 묻습니다. canSpawnHere싱글턴 월드 데이터를 읽어 세이브 안에 walker가 이미 있으면 생성을 막습니다. 생성된 TheCreepyWalkerEntity는 서버 틱마다 주변 지형을 확인하고 서기, 웅크리기, 기어가기 자세를 전환합니다. 자세에 맞춰 히트박스 크기를 바꾸고 SynchedEntityData로 상태를 클라이언트에 보냅니다. 엔티티 렌더러는 매 프레임 이 상태를 읽고 계층형 모델은 알맞은 키프레임 클립을 재생합니다. 기어가기에서 서기로 바뀔 때는 두 단계 전환 애니메이션을 사용합니다.

배포: Mod jar → Forge 클라이언트 + 서버 · 생성 규칙 → 데이터팩 JSON, 코드 다시 불러오기 불필요

성과

  • 자세 3종 · 히트박스 높이가 2.7, 1.7, 0.7블록으로 변화
  • 월드당 1개 · 세이브 파일을 통해 싱글턴 강제
  • 클립 14개 · 손으로 키프레임을 잡은 Blockbench 애니메이션
  • 사용자 정의 패킷 0개 · 모든 상태를 기본 엔티티 데이터로 동기화

보여준 역량: 서버-클라이언트 상태 동기화 · 유한 상태 기계 · 히트박스 + 충돌 처리 · 이벤트 기반 등록 · NBT 월드 영속성 · 게임 AI 목표 설계

문제와 해결책

핵심 목표는 하나뿐인 괴물이 살아 있는 것처럼 느껴지고, 어떤 공간에서도 플레이어를 끝까지 따라오게 만드는 것이었습니다.

█ 지형을 인식하는 자세 상태 머신

문제: 키가 2.7블록인 괴물은 1블록짜리 좁은 통로로 플레이어를 쫓아갈 수 없습니다. 바닐라 몹은 낮은 입구를 그냥 우회하거나 입구에서 막혀버려서, 어디든 따라오는 존재라는 공포 전제를 무너뜨립니다.

서버 틱마다 엔티티가 위쪽과 앞쪽 블록을 확인하고, 벽을 만나면 한 칸 위까지 살펴 자세를 고릅니다. 서기, 웅크리기, 기어가기에는 각각 다른 히트박스를 적용합니다. 높이는 2.7, 1.7, 0.7블록이며 getDimensionsrefreshDimensions로 갱신합니다.

  • 기본 Pose의 수영과 웅크리기는 플레이어 형태의 히트박스만 지원하고 전환 시점을 제어할 수 없어 사용하지 않았습니다
  • 틱마다 블록 상태를 최대 8번 읽는 비용을 감수하고, 자세를 바꾼 뒤 20틱 동안 다시 바꾸지 않아 모서리에서 상태가 반복되는 현상을 막았습니다
  • 경로 탐색만 조정해서는 만들 수 없는, 플레이어가 가는 공간에 실제로 들어맞는 몹을 구현했습니다

▚ 커스텀 패킷 없는 자세 동기화

문제: 자세는 서버에서 결정되지만 애니메이션은 클라이언트에만 존재합니다. 평범한 Java 필드는 멀티플레이에서 조용히 어긋납니다: 각 클라이언트가 자기 나름의 자세를 추측해 잘못된 클립을 재생하게 됩니다.

현재 자세, 이전 자세, 숫자로 된 자세 변경 코드를 Minecraft의 엔티티 상태 동기화 기능인 SynchedEntityData accessor에 저장합니다. 클라이언트는 변경 코드를 읽어 알맞은 전환 클립을 시작한 뒤 코드를 0으로 초기화합니다.

  • 불리언과 정수 하나로 표현할 상태 때문에 코드, 버전 관리, 순서 문제가 늘어나는 사용자 정의 네트워크 채널은 제외했습니다
  • 엔티티 하나에 동기화 accessor 10개를 쓰는 비용은 있지만 별도 패킷 없이 복제되고 경쟁 조건도 생기지 않습니다
  • 전환 도중 렌더링 범위에 들어온 플레이어에게도 올바른 애니메이션을 보여줍니다

▞ 2단계 전환 안무

문제: 기어가기에서 서기로 바로 바꾸면 순간이동처럼 보입니다. 전환 클립은 서로 인접한 자세끼리만 만들어 기어가기에서 서기로 곧장 이어지는 애니메이션이 없었습니다.

중간 자세를 건너뛸 때는 클립 두 개를 대기열에 넣습니다. 기어가기에서 웅크리기 클립을 먼저 재생하고 대기 플래그로 다음 동작을 준비합니다. 첫 클립의 타임아웃이 0이 되는 틱에 웅크리기에서 서기 클립을 시작합니다. 전체 순서는 클라이언트의 틱 카운터로 제어합니다.

  • 드물게 쓰는 전환을 위해 Blockbench 애니메이션 두 개를 더 만들고 관리할 필요가 없어 전용 클립은 만들지 않았습니다
  • 새 자세로 최고 속도에 도달하기까지 약 16틱이 더 걸리는 점은 감수했습니다
  • 전환 클립 네 개만으로 모든 자세 변경을 자연스럽게 연결했습니다

▓ 월드당 walker 하나, 두 번 강제

문제: 스폰 엔진이 수십 개의 복제본을 만들어내면 공포 몹은 모든 위협감을 잃습니다. 바닐라 스폰 상한은 카테고리별, 청크별로 작동하므로 '월드 전체에서 영원히 정확히 하나'를 표현할 수 없습니다.

월드 세이브의 SavedData 불리언 값을 생성 여부의 기준으로 사용합니다. 값이 설정돼 있으면 canSpawnHere가 모든 생성을 막고 Forge의 join/leave 이벤트가 값을 갱신합니다. 모든 오버월드 바이옴에서 생성 가중치를 100으로 설정해 허용된 단 한 마리가 빠르게 나타나게 했습니다.

  • 로드된 엔티티만 세면 언로드된 청크의 walker를 놓쳐 중복 생성을 허용하므로 제외했습니다
  • 차원별로 기록하지 않고 월드 전체 플래그 하나를 사용합니다. walker가 총 한 마리만 존재하는 것이 의도한 설계입니다
  • 플래그를 NBT로 저장해 서버를 재시작해도 싱글턴 상태가 유지됩니다

▒ 틱이 아니라 애니메이션에 맞춘 근접 공격

문제: 바닐라 MeleeAttackGoal은 대상이 사거리에 들어오는 순간 피해를 적용하지만, 돌진 애니메이션은 대상에 닿기까지 여러 틱이 걸립니다. 플레이어는 아직 팔도 움직이지 않은 괴물에게 맞습니다.

사용자 정의 공격 목표는 준비 카운터를 사용합니다. 공격 플래그를 먼저 켜 클라이언트에서 휘두르기 애니메이션을 시작하고, 팔이 내려오는 프레임에 카운터가 도달했을 때만 doHurtTarget를 실행합니다.

  • 기본 공격 목표에 클라이언트 측 추정을 더하면 지연 때문에 중요한 순간에 타이밍이 어긋나므로 제외했습니다
  • 동작을 보고 피할 수 있게 만드는 대신 실제 공격 속도가 느려지는 12틱 쿨다운을 적용했습니다
  • 각 자세의 전용 공격 클립과 정확히 맞는 프레임에 피해가 들어갑니다

▧ 직접 연출한 생성과 사망, 래그돌 없음

문제: 기본 사망 연출은 빨간색으로 번쩍이고 90도로 쓰러진 뒤 바로 사라져 생물의 죽음보다 게임 시스템처럼 보입니다. 갑자기 나타나는 기본 생성 방식도 첫 조우의 긴장감을 살리지 못합니다.

죽을 때는 렌더러가 기본 시체 회전을 막고 키프레임 붕괴 클립을 재생합니다. 몸은 60틱 동안 남아 있지만 상호작용할 수 없습니다. 생성될 때는 108틱 길이의 등장 클립을 재생하고 그동안 서버 AI를 멈춥니다.

  • 원하는 자세를 유지하려면 tickDeathsetupRotations를 재정의해야 해서 LivingEntity의 기본 사망 처리를 쓰지 않았습니다
  • 등장 연출이 끊기지 않도록 생성 후 5초 동안 AI가 멈추는 취약 구간을 감수했습니다
  • 괴물의 분위기를 끝까지 유지하는 등장과 퇴장을 구현했습니다