개인 자료란 (JE)

  서버 커뮤니티

Profile Kobins 대표칭호 없음

Kobins 7e61baba5cec465d89e9b4c5615ae13e

Profile

강좌 자바 에디션(JE) 플러그인 개발

[마시자] 마인크래프트로 시작하는 Java 3강 - 명령어(Command) 등록

2020.03.15 조회 수 2469 추천 수 6
분야 플러그인 
장르 개발자 툴 
게임버전 모든버전 
API 스피곳, 페이퍼 

1776ced7247c675b457e79b884389828.png

마인크래프트로 시작하는 Java 3강 목차

명령어(Command) 등록

명령어를 만들자

사실 이전에 소개한 이벤트로 충분히 대체 가능한 분야입니다. 하지만 웬만하면 대부분의 경우 플러그인이 인게임 플레이어 또는 콘솔에서 텍스트 형식의 정보를 입력받아 가공하려 할 때에는 일반적으로 명령어(Command)를 만들어 사용합니다.

그리고 최근에는 명령어 자동완성 기능이 생각보다 강력해지면서 완성도 있는, 편리한 플러그인은 명령어 또한 굉장히 사용하기 편하게 구성되어 있습니다.

하지만 그 명령어를 만드는 과정은 또한 복잡합니다. plugin.yml에 명령어 정보를 입력하고, getCommand(String name)으로 명령어를 가져오고, 그 명령어에 대한 구현을 담는 CommandExecutor 인터페이스를 implement한 클래스명령어에 등록해주면 됩니다.

- plugin.yml에 굳이 등록하지 않고 코드 내부에서 직접 넣어도 됩니다.

정말 간단하지 않나요?

아니라구요?

다시 돌아온 plugin.yml

d2786f4bb973c4b26bd34567025ccc3f.png

정말 오랜만에 보는 친구입니다. 0강에서 resources 폴더에 집어넣었던 plugin.yml을 다시 한번 봅시다.

a18f954b854a1f64eead0db3a655aba0.png

그리고 이렇게 적어줍시다. 알아서 띄어쓰기까지 자동완성 해 줄거에요.

이러면 이제 msj 라는 이름의 명령어를 Paper가 플러그인에 달아줍니다.

또한 /help, /? 등의 명령어를 사용하여 도움말을 볼 때 뜨는 설명description에 해당합니다.

aliases는 해당 명령어를 단축하여 사용할 수 있게 만드는 별명들입니다. 밑에서 자세하게 다룰 예정.

84c65c52d03eb1e6e2ebfe0e96ea0128.png

클래스를 만들어 줍시다.

aece0732effb56f0f493170841972f0d.png

CommandManager 로 하겠습니다.

7e4d7a59f8b491215c06b5b8d2a07dd7.png

생성된 클래스 이름 뒤에 implements CommandExecutor 를 달아줍시다. (import 해주면서)

어.. 근데 왜 빨간 줄이 뜨죠?

고것은 바로 CommandExecutor 구현(implement)해야 하는 내용(메소드)을 CommandManager 가 구현하고 있지 않기때문이죠!

상속(Inheritance, extends)과 인터페이스(Interface)의 구현(implement)

ee715d3c819d94607fc9b8501c6929e6.png

CommandExecutor는 이런 형태의 인터페이스(interface)입니다.

인터페이스는 클래스와 비슷하지만 실체는 없고, 껍데기만 있는 클래스입니다.

우리가 Main 클래스에 extends JavaPlugin 하여 상속하던 JavaPlugin 클래스와는 다르게, 인터페이스implements CommandExecutor처럼 implements를 사용하여 구현(implement)합니다.

저번 강의에서 Listener 또한 인터페이스였습니다. 하지만 Listener는 진짜로 구현해야 할 내용도 없이 이벤트를 받는 클래스이다 라는 것을 명시하기 위해 만들어 놓은 껍데기 중의 껍데기에 불과했습니다.

실제로는 인터페이스하위 클래스(인터페이스를 implement하는 클래스)에게 기능의 구현을 강제하기 위해 사용됩니다.

따라서 아까와 같이 오류를 띄며 사진의 boolean onCommand(...) { ... } 메소드의 구현을 강제한 것이죠.

하지만 일반 클래스 간의 상속은 다릅니다. 하위 클래스에게 상위 클래스의 기능들을 물려준다는 느낌이 강합니다. 그리고 물려준 기능들을 하위 클래스는 수정할 수 있습니다.

그렇게 물려받은 기능들을 수정한 것이 0강에서 JavaPlugin 클래스상속Main 클래스에서 @Override public void onEnable(){ ... } 를 통해 메소드를 작성한 것이었습니다.

98b40435479f9cbdd87dd7b70a6dfdfe.png

따라서 implement methods를 눌러 가져와 줍시다.

2b26f94389f37b87369e6ad29e75e98e.png

8762da717f4f6e34037eceba3ba1bc29.png

짜잔.

711af007d8b2b379d3311550da028a19.png

이 방법으로 안 해도 되고, 0강에서 했던 것 처럼 oncommand만 써도 나옵니다. (얘도 상속 비슷한 거기 때문에!)

7b66465a0ea14ab8bfe36e8e0ea7b70e.png

그럼 대충 요런 친구가 나오는데...

ce9eadb165f4662e9879447115c5eb80.png

잠시 이름을 이렇게 바꾸도록 하겠습니다.

우리는 명령어의 기능을 구현하는 과정에서 총 4개의 변수를 사용할 수 있습니다.

  • CommandSender sender
    • 명령어를 보낸 플레이어 또는 콘솔, 엔티티, 커맨드 블록입니다.
    • 즉, sender가 Player이거나 ConsoleCommandSender인 경우 등을 구분할 수 있게 해 줍니다.
    • sender.sendMessage(...) 를 통해 메세지를 보낼 수 있습니다.
  • Command command
    • 명령어 객체입니다.
    • 어떤 명령어가 실행되었는지를 알게 해 주는 기본적인 인자입니다.
    • command.getName() 을 통해 위에서 등록한 plugin.yml에서의 msj 부분을 가져올 수 있습니다.
  • String label
    • 명령어의 이름으로 사용된 문자열입니다.
    • command.getName()과 같을 수 있으나 위에서 본 aliases: [mj, ms ...] 부분에서 플레이어가 사용한 alias를 알아내는 데에도 사용됩니다.
  • String[] args
    • 명령어 이후로 입력된 인자들입니다.
    • /msj Kobins 1 2 라는 명령어를 쳤을 때, {"Kobins", "1", "2"} 와 같이 띄어쓰기 단위로 문자열 배열로 들어갑니다.

무슨 말인가 싶은데, 만약 Kobins라는 플레이어가 /tp Kobins 0 64 0 이라는 명령어를 치게 되면...

c42ca09c580f05a0999e53e35db922df.png

이런식으로 들어온다는 이야기!!!

이 4개의 변수를 통해, 필요한 입력값을 받아 명령어를 구현하게 됩니다.

우리는 아까 plugin.yml에 등록한 msj라는 명령어를 받을 예정이니, 명령어의 이름이 msj일 때 라는 조건을 걸어봅시다.

33fa5c4da0be8bd4c1b130571ac766ff.png

바로 이렇게!

command는 위에서 설명했듯이, 명령어를 받았을 때의 명령어 객체라고 했는데, 명령어 객체에는 명령어의 이름을 받을 수 있습니다.

어? 근데 if가 뭐죠?

if( 조건 ) {
구문
}

과 같이 작성하는데, 조건 부분에는 참(true)거짓(false)을 알 수 있는 구문을 작성하시면 됩니다.

따라서 위 구문에서는 command.getName()"msj"가 같은지 같지 않은지를 구분할 때 사용하는 equals(...)를 통해 조건을 판단하는 것입니다!

이제 /msj 명령어를 입력하면 저 if문 안의 구문이 실행되겠지요.

그럼 무슨 명령어를 만들어 볼까요... 간단하게 메세지를 하나 보내볼까요?

f97aae1abdf6e8b895f1aefc559a4dd9.png

이렇게 써 봅시다!

이러면 명령어를 입력한 플레이어의 이름이 Kobins일 때, 안녕? Kobins 라는 메세지가 전달되겠지요?

명령어 등록

좋아요, 이제 대충(제대로 구현하지 않았지만) 명령어를 치면 인사를 해 주는 명령어가 만들어졌으니, 이것도 이벤트처럼 등록을 해야합니다.

단, 이벤트 때에는 Listener 클래스를 등록했던 것과 달리, 이번에는 plugin.yml을 통해 인식된 명령어를 아까 만든 CommandManager와 연결을 시켜주게 됩니다.

c4bce8f68c0a0a4abaa65c25fdf146ba.png

잠시 Main 클래스로 돌아와, getCommand를 입력 해 줍시다.

getCommand(String name)JavaPlugin에서 상속받은 메소드인데,  괄호 안의 문자열을 기준으로 명령어 객체를 가져옵니다.

b411105fe04be0d278212025b399de25.png

제가 가져오려는 명령어의 이름은 아까 msj 라고 했으니, 큰따옴표 안에 msj를 넣어줍시다.

5ac4dd2c51b489dc9139887425ada9b1.png

즉, 큰 따옴표 안에 들어갈 문자열은 바로 우리가 plugin.yml에서 작성한 저 부분을 의미합니다.

1b9961ab6e12bdbed6ac2fff7d2d1b36.png

그리고, getCommand를 통해 가져온 명령어 객체에 .을 붙이면 해당 명령어에 대해 여러가지 일을 할 수 있는데...

우리가 해야 할 일은 명령어에 CommandExecutor를 구현한 클래스를 넣어야 합니다.

c178fccdb90d1ab23992bd85925346d3.png

바로 이 친구요!

e4ef0029ee255a8598b1ea9c015c3541.png

우리가 EventManager를 등록할 때 new를 사용했던 것 처럼, CommandManager도 똑같이 해 줍시다.

5a93c04c2a513eea0bf87703b1720d79.png

짜잔.

그럼 이제, /msj 라는 명령어를 입력했을 때, 그 명령어에 등록된 CommandExecutorCommandManager에서 onCommand(sender, command, label, args)를 실행시킬 겁니다.

6498d9e489bacc2d7513b016867d379a.png

그럼 아까 우리가 작성한 안녕? <닉네임> 과 같은 문장을 명령어를 친 대상에게 전달하겠지요.

이 상태의 플러그인을 이제 빌드하고 서버를 열면...?

59be2c5756a42cbac2523ae41acfa0be.png 를 입력하면...

f3f22fec5be85d1b41504b0731eb7ebd.png

와!

근데, 명령어는 플레이어 뿐만 아니라, 콘솔과 커맨드 블록, 다른 엔티티도 칠 수 있지요!

만약 콘솔에서 친다면, 

50e0eb5ee8b5e36feb6f619b8a353ea2.png

이렇게!

만약 커맨드 블록에서 친다면,

564dfe5a87339eacb0014df25ef5a02b.png

이전 출력에서 저렇게 뜨게 되지요!

이렇게, 누가 보냈는지를 구분하여 명령어를 구성할 수도 있습니다!


목차




18개의 댓글

히리지
2020.03.16

와 동영상으로 해보는것도 나쁘지...안..

Redips
2020.03.17

1.15.2로 만들고 있는데 1.13인가? 부터 위에 쓸수있는 커맨드가 뜨잖아요? 근데 거기에 제가 만든 커맨드를 넣고 싶은데 어떻게 해야할까요

리미트
2020.03.31
@Redips

그냥 위에 강좌대로 만드니까 뜨던데요?

Redips
2020.04.15
@리미트

라벨 부분은 잘 뜨는데 arg 부분이 안떠서요. 제가 이상한건가요?

Kobins
2020.04.15
@Redips

args는 TabExecutor를 implements해서 List onTabComplete(....)을 이용해서 반환할 자동완성 문자열 리스트를 판단하고 리턴해줘야합니다...

Redips
2020.06.30
@Kobins

혹시 저처럼 args 부분 나오게 하고 싶으면 https://www.youtube.com/watch?v=rQce_yEXSOE 여기보고 따라해보세요

영어라서 듣기 힘들지 몰라도 대충 알아듣기는 할거에요

리미트
2020.03.31

다음 강좌는 언제 나오나유?

4.JPG

여기 나오는 S 가 어떻게 만들어지는 지 모르겠습니다. 빌드 해봤더니 오류가 나서요. 그리고

de2922c7b278ce4420ba5073c7217275.JPG

여기서 NAME과 SETEXACUTER도 오류가 뜨는데 왜 그런지를... ㅠㅠㅠㅠㅠ

Kobins
2020.04.18
@스피드웨건도와줘요

강좌에 나오는 모든 name:과 s:같은 것들은 인텔리제이에서 자동으로 매개변수 가이드 해주는거에요!!!!!!!!!! 따라쓰는 게 아님!!!!

아글
2020.06.17

equalsIgnoreCase 와 equals의 차이는 무엇인가요?

Kobins
2020.06.17
@아글

equalsIgnoreCase는 String 객체에서만 사용 가능합니다.

말 그대로 Case를 무시하고, 즉 대소문자 구분 없이 일치 여부를 검사한다는 뜻입니다.

AABB와 aabb는 equals로 비교하면 다르지만, equalsIgnoreCase로 검사하면 같은 결과입니다.

두 문자열에 각각 toLowerCase() (모든 문자 소문자로)나 toUpperCase() (모든 문자 대문자로)를 사용하여 검사하는 것과 같습니다.

한글에는 대소문자가 없기때문에 사용할 일은 없고, 영어의 경우 굳이 대소문자의 구별이 필요하지 않는 경우 사용됩니다.

명령어같은 경우 가끔 실수로 캡스락이 켜져있는 상태로 치는 경우도 있고, 여러가지 경우가 있어서 플러그인 강좌에서 명령어 인자 일치를 검사할 때 equalsIgnoreCase를 사용합니다.

사실 써도되고 안써도돼요 ㅎㅎ;

 

equals는 String의 비교뿐만 아니라 다른 모든 객체에 대한 일치 여부를 검사합니다.

단, 객체는 ==로 비교하면 내용물이 아니라 객체 번지수를 검사하므로 별도로 객체별로 equals를 구현하여 내용 일치를 구현합니다.

아글
2020.06.17
@Kobins

답변 감사합니다.

아글
2020.06.17
@Kobins

좋은 강좌 기다리겠습니다 ^^

59f27fe82d7dbfe951ff3784861db09d.PNG

위 사진과 같이 코드를 썼는데, 마지막 }에서 에러가 나네요.  왜그런지 아시나요?

Kobins
2020.10.24
@moon_glasses

이미지가 날아간 것 같습니다... 아마 중괄호를 위에서 하나 빼먹었거나 하신 것 같습니다.

S1RO__
2020.12.13

[18:59:07 ERROR]: Error occurred while enabling MSJ v1.0 (Is it up to date?)

java.lang.NullPointerException: Cannot invoke "org.bukkit.command.PluginCommand.setExecutor(org.bukkit.command.CommandExecutor)" because the return value of "me.siro.Main.getCommand(String)" is null

at me.siro.Main.onEnable(Main.java:18) ~[?:?]

at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:263) ~[patched_1.16.4.jar:git-Paper-324]

at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:380) ~[patched_1.16.4.jar:git-Paper-324]

at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:483) ~[patched_1.16.4.jar:git-Paper-324]

at org.bukkit.craftbukkit.v1_16_R3.CraftServer.enablePlugin(CraftServer.java:501) ~[patched_1.16.4.jar:git-Paper-324]

at org.bukkit.craftbukkit.v1_16_R3.CraftServer.enablePlugins(CraftServer.java:415) ~[patched_1.16.4.jar:git-Paper-324]

at net.minecraft.server.v1_16_R3.MinecraftServer.loadWorld(MinecraftServer.java:469) ~[patched_1.16.4.jar:git-Paper-324]

at net.minecraft.server.v1_16_R3.DedicatedServer.init(DedicatedServer.java:239) ~[patched_1.16.4.jar:git-Paper-324]

at net.minecraft.server.v1_16_R3.MinecraftServer.w(MinecraftServer.java:936) ~[patched_1.16.4.jar:git-Paper-324] at net.minecraft.server.v1_16_R3.MinecraftServer.lambda$a$0(MinecraftServer.java:178) ~[patched_1.16.4.jar:git-Paper-324]

at java.lang.Thread.run(Thread.java:832) [?:?]

이런 오류가 뜨는데 어떻게 해결하쥬?

SharedScreenshot.jpg SharedScreenshot2.jpg SharedScreenshot3.jpg

이런식으로 했는데 라벨도 안뜨고 커맨드도 없는데 뭐가 문제인가요?

sino
2021.11.02
getCommand("sino").setExecutor(new CommandManager(), this);

이게 안되서 아래처럼 했는데 됬거든요?? 뭐가 문제일까요...

getCommand("sino").setExecutor((CommandExecutor) new CommandManager());
뉴스 및 창작물
/files/thumbnails/762/770/003/262x150.crop.jpg?20240418073724

레드스톤

T.B.H (고민중독) | 노트블럭 버전 | NoteBlock Cover [한국어 영어 중국어 가사 추가]

노트블럭전문가

2024-04-18

0

/files/thumbnails/218/767/003/262x150.crop.jpg?20240412130213

레드스톤

우리의 꿈 - 원피스 오프닝

노트블럭전문가

2024-04-12

0

/files/thumbnails/505/766/003/262x150.crop.jpg?20240411122306

레드스톤

기동전사 건담 수성의 마녀 | 노트블럭 커버 1

노트블럭전문가

2024-04-11

1

/files/thumbnails/932/765/003/262x150.crop.jpg?20240410124459

레드스톤

마인크래프트 노트블록으로 만든 『 밤양갱 (Bam Yang Gang) 』

노트블럭전문가

2024-04-10

0

/files/thumbnails/403/765/003/262x150.crop.jpg?20240409190538

레드스톤

마인크래프트 노트블록으로 만든 『 밤양갱 (Bam Yang Gang) 』

Sonttukk

2024-04-09

4

/files/thumbnails/161/758/003/262x150.crop.jpg?20240331105743

레드스톤

라마 침 분수대 1

GlassesFilm

2024-03-31

0

/files/thumbnails/520/751/003/262x150.crop.jpg?20240328020349

레드스톤

마인크래프트 노트블록으로 만든 『 Bling‐Bang‐Bang‐Born 』 4

Sonttukk

2024-03-23

3