https://github.com/gloomn/blogMCPluginProjects
GitHub - gloomn/blogMCPluginProjects
Contribute to gloomn/blogMCPluginProjects development by creating an account on GitHub.
github.com
프로젝트 폴더들은 모두 여기 있으니까 코드가 필요하시면 다운로드 받으시면 됩니다!
이전 강의
https://syntaxack.tistory.com/entry/minecraftplugin13
마인크래프트 플러그인 강좌 13강 - [config.yml] getStringList로 금지어 목록 만들기 실습
이전 강의https://syntaxack.tistory.com/entry/minecraftplugin12 마인크래프트 플러그인 강좌 12강 - [config.yml] getString, getInt, getBoolean 값 읽기 실습이전 강의https://syntaxack.tistory.com/entry/minecraftplugin11 마인크래프
syntaxack.tistory.com
이번 강의 목표
- NPC(Entity)와의 상호작용을 처리하는 방법 배우기
- 우클릭 이벤트를 사용하여 플레이어가 NPC와 상호작용할 때 발생하는 이벤트 정의하기
- NPC랑 대화하기
1. 프로젝트 생성하기
Intellij를 실행해서 새로운 마인크래프트 프로젝트를 만들어준다.
프로젝트 생성 방법을 모르면 아래 링크를 눌러 한 번만 보면 된다.
https://syntaxack.tistory.com/entry/minecraftplugin1
마인크래프트 플러그인 강좌 1강 - 첫 플러그인 만들기
이전 강의https://syntaxack.tistory.com/entry/minecraftplugin0 마인크래프트 플러그인 강좌 0강 - 준비하기마인크래프트 플러그인이란?마인크래프트 플러그인은 마인크래프트 내에서 기능을 확장할 수 있도
syntaxack.tistory.com
1. paper/spigot/sponge 템플릿에서 여러분이 사용하는 버킷 플러그인을 선택한다.
2. 빌드 시스템은 Gradle를 선택한다.
3. 언어는 자바를 선택한다.
4. 마인크래프트 버전은 여러분이 플러그인을 적용항 버전과 paper 버전이 일치하도록 선택한다.
예를 들어서 마인크래프트 버전 1.21.4, paper 버전 1.21.4 이면 1.21.4를 선택한다.
5. 플러그인 이름과 클래스 이름을 적는다.
생성을 눌러준다.
https://syntaxack.tistory.com/entry/minecraftpluginproblem1
마인크래프트 플러그인 강좌 - 잘못된 Gradle JVM 구성을 발견했습니다.
IntelliJ를 사용해서 마인크래프트 플러그인 프로젝트를 만들었을 때 잘못된 Gradle JVM 설정이라는 알람이 뜰 때가 있다.이는 JDK와 Gradle이 호환되지 않아서 발생하는 문제이다.이때는 인터넷 검색
syntaxack.tistory.com
만약 Gradle과 JVM 버전이 안 맞는다는 오류가 뜨면 위의 포스트를 보면 된다.
2. Gradle build 설정
우리는 jar 파일로 빌드할 때 빌드 위치를 바로 플러그인 폴더로 빌드되게 설정할 것이다.
build.gradle 파일을 열어준다.
tasks.jar{
archiveFileName = 'npcTest1.jar'
destinationDirectory = file('C:\\Users\\kijoon\\Desktop\\Server\\plugins')
}
3.NpcTest1.java 작성하기
package org.blog.npcTest1;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.bukkit.util.BlockIterator;
public class NpcTest1 extends JavaPlugin implements Listener {
private boolean isTalkingToNPC = false;
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this); // 이벤트 리스너 등록
}
@Override
public void onDisable() {
// Nothing to do
}
// 플레이어의 커서 위치에 NPC를 생성하는 메서드
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event)
{
Player player = event.getPlayer();
// 플레이어가 우클릭한 경우
if (event.getAction().name().contains("RIGHT_CLICK")) {
Location targetLocation = getTargetBlockLocation(player);
// NPC가 이미 그 위치에 있는지 확인
if (!isNPCAtLocation(targetLocation)) {
spawnNPC(targetLocation);
}
}
}
// 플레이어가 바라보는 위치를 얻는 메서드
private Location getTargetBlockLocation(Player player) {
// 플레이어가 바라보는 방향에 있는 블록을 찾기 위한 아이템으로 BlockIterator 사용
BlockIterator blockIterator = new BlockIterator(player, 100); // 100 블록 내의 위치 탐지
// 블록이 있는 위치를 찾을 때까지 반복
while (blockIterator.hasNext()) {
Location location = blockIterator.next().getLocation();
// 블록을 만났을 때 그 위치를 반환
if (location.getBlock().getType().isSolid()) {
return location.add(0, 1, 0); // 블록의 상단 위치를 반환 (npc가 부딪히지 않게)
}
}
// 만약 100블록 내에서 발견되지 않으면 기본적으로 플레이어가 서 있는 위치 반환
return player.getLocation().add(player.getEyeLocation().getDirection().multiply(2)); // 2블록 앞에 NPC 생성
}
// NPC를 생성하는 메서드
private void spawnNPC(Location location) {
Villager npc = (Villager) location.getWorld().spawnEntity(location, org.bukkit.entity.EntityType.VILLAGER); // Villager 엔티티 생성
npc.setCustomName("§6퀘스트 NPC"); // NPC 이름 설정
npc.setCustomNameVisible(true); // NPC 이름을 플레이어에게 보이도록 설정
}
// 지정된 위치에 NPC가 이미 존재하는지 확인하는 메서드
private boolean isNPCAtLocation(Location location) {
for (Entity entity : location.getWorld().getEntities()) {
// 해당 위치와 가까운 엔티티가 Villager라면 NPC가 이미 존재하는 것
if (entity instanceof Villager && entity.getLocation().distance(location) < 1.5) {
return true; // 가까운 위치에 NPC가 이미 존재하면 true 반환
}
}
return false; // NPC가 존재하지 않으면 false 반환
}
// NPC와 대화 시작
@EventHandler
public void onNPCInteract(PlayerInteractEntityEvent event) {
Player player = event.getPlayer();
Entity entity = event.getRightClicked();
if (entity instanceof Villager) {
Villager npc = (Villager) entity;
String npcName = npc.getCustomName() != null ? npc.getCustomName() : "NPC";
if ("§6퀘스트 NPC".equals(npcName)) {
player.sendMessage(Component.text("🎉 NPC와 대화가 시작되었습니다!").color(NamedTextColor.GREEN));
player.sendMessage(Component.text("NPC: 안녕하세요! 퀘스트를 시작하고 싶으세요?").color(NamedTextColor.YELLOW));
isTalkingToNPC = true; // 대화 중임을 표시
}
}
}
// 플레이어가 대화 중에 메시지를 입력하면 처리
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
if (isTalkingToNPC) {
String message = event.getMessage().toLowerCase();
if (message.contains("네")) {
player.sendMessage(Component.text("NPC: 퀘스트를 시작합니다!").color(NamedTextColor.YELLOW));
isTalkingToNPC = false; // 대화 종료
} else if (message.contains("아니오")) {
player.sendMessage(Component.text("NPC: 퀘스트를 취소하였습니다. 나중에 다시 오세요.").color(NamedTextColor.RED));
isTalkingToNPC = false; // 대화 종료
} else {
player.sendMessage(Component.text("NPC: 답변을 '네' 또는 '아니오'로 해주세요.").color(NamedTextColor.RED));
}
event.setCancelled(true); // 메시지를 서버에 보내지 않도록 차단
}
}
}
4. 코드 설명
코드 설명은 코드에 주석을 다 달아두었다.
궁금한 것이 있으면 댓글로 질문하길 바란다.
getCustomName이나 setCustomName이 deprecated로 나올 것이다. 이는 구문법이지만 사용이 가능하다.
5. plugin.yml 작성하기
name: npcTest1
version: '1.0-SNAPSHOT'
main: org.blog.npcTest1.NpcTest1
api-version: '1.21'
6. jar 파일로 빌드하기
7. 테스트하기
먼저 바라보는 곳 바닥에 우클릭을 하면 주민이 생성된다.
그 다음 주민을 우클릭하면 메세지가 뜬다.
텍스트에 네 또는 아니오를 적어본다.
정상적으로 작동한다.
아니오를 쓰면
이렇게 작동한다.
8. 다음 강의
다음 강의에서는 NPC의 외모를 변경하는 방법을 다룰 예정이다.