Nyannyacha d5524fc4eb3a4bdc95d86cfab6afb308
분야 | 스크립트 |
---|---|
게임버전 | 모든버전 |
폭발은 예술냥
하이냥 밤이다냥
오늘은 가볍게 누군가 죽으면 아이템을 흩뿌리면서 폭발하는 스크립트를 한번 구현해볼까 한다냥
예전에 이미 해놓은거긴 하지만 쓸모 없어졌다냥.
이 글에서 나오는 스크립트는 흔히 아는 Skript 가 아니다냥
내가 쓰는 스크립트란 Lua 스크립트를 말한다냥.
Lua 스크립트는 이미 여러 메이저 게임 엔진 내부에서 많이 쓰여서 게임용으로도
확실히 의미가 있음을 보여준 언어다냥, 물론 게임 목적으로 만들어 졌다는건 아니고냥
탁월한 이식성과 간결한 문법이 게임 스크립터한테 인기를 끌어서 많이 사용됐다는 얘기다냥
물론 지금은 C# 이나 자체 스크립트 많이 쓰는것 같지만 내가 알던 때는 그랬다냥.
물론 Lua 를 쓴다고 해서 완전 못알아 먹을 정도의 코드는 아닐꺼다냥
자바에 올라가 있으니깐 자바 API 를 호출해야되서 대강 알아들을 수 있을거다냥
그러니깐 어느의미에서는, 의사 코드라고(Pseudo code) 생각하고 봐줬으면 좋겠다냥.
서론이 길었다냥
코드다냥
알려주기 전에 몇 가지 알려줄게 있다냥.
1. import "$..." 라고 나와있는 줄은 "org.bukkit.<패키지명 혹은 클래스, 열거형 등>" 패키지를 불러오는 것이라고 보면 된다냥.
2. self 는 자바 플러그인의 인스턴스와 비슷하다냥 플러그인의 인스턴스 말이다냥, 그것하고 똑같다고 보면 된다냥.
3. self.runDelayed 는 BukkitRunnable.runTaskTimer 와 거의 비슷하다냥
4. self.onEnable 은 JavaPlugin.onEnable 과 비슷하다냥 단지 로드되었을 때 할 동작을 클로저 형식으로 전달하는 것일 뿐이다냥.
5. self.registerEvent(event, callback) 은 버킷 이벤트를 등록하는 메소드다냥
위 코드에서는 플레이어가 죽었을 때 사용하는 PlayerDeathEvent 를 진입점으로 폭발을 만들어낸다냥
참고로 PlayerDeathEvent는 버킷 API 의 그 PlayerDeathEvent 맞다냥 참고냥.
local function handle(ev) local d = ev:getEntity() local l = d:getLocation() local w = d:getWorld() local inv = Bukkit:createInventory(nil, InventoryType.PLAYER)
위 내부 함수부터 설명냥
저기 인자로는 ev 는 PlayerDeathEvent 의 인스턴스다냥 참고냥
일단 폭발 이팩트랑 비산될 아이템들을 알아내야 되기 때문에 이벤트로부터 사용자를 가져온다냥
getEntity 를 쓴 이유는 몬가 버킷 버그인지는 몰라도 EntityDeathEvent 로 된 인스턴스가 오더라고냥
그래서 getEntity 를 썼다냥, 하지만 너네들이 쓸때는 타입 체크를 해줘야 안전할 것 같다냥.
이렇게 엔티티 객체랑 위치 객체, 월드 객체 세가지를 준비해둔다냥
마지막으로 인벤토리 정보를 복사하기 위해서 플레이어 타입의 인벤토리를 하나 만들어 준다냥
Bukkit.createInventory 정적 메소드를 호출하는 거다냥
ev:setKeepInventory(true) inv:setContents(d:getInventory():getContents()) d:getInventory():clear()
다음 세줄 이다냥.
이 당시에 이벤트에다가 setKeepInventory 를 호출해서 인벤토리가 날라가지 않게한 것은
아마 서버 환경이 저장되지 않는 환경이라서 호출했던 것 같다냥.
위에서 만들어놓은 신상 인벤토리 객체에 죽은 플레이어의 인벤토리 데이터를 담는다냥.
그리고 지우지냥, 지워야 다시 리스폰 했을때 아이템이 남아있질 않을테니깐냥
self.runDelayed(function() d:setHealth(0.0) w:spawnParticle(Particle.EXPLOSION_HUGE, d:getLocation(), 1, nil) ... end, time.seconds(1))
다음 줄이다냥 위에서 내가 참고 사항으로 말한 메소드를 이제 호출할 때가 되었다냥.
self.runDelayed(function() ... end, time) 은 인자 안의 함수가 버킷 스레드에 time 만큼 시간이 흘렀을때
호출되도록 하는 간단한 비동기 함수다냥. 지금 같은 경우에는 현실 시간으로 1초(20틱) 뒤에 함수가 호출된다냥.
변수 d 에 저장한 것은 플레이어였지냥, setHealth 를 이용해서 완전히 숨통을 끊어놓는다냥.
이것도 아마 버킷 버그때문에 그런것 같은데 너네들 환경을 봐서 넣을지 말지 고려해봐라냥.
변수 w 에 저장한 것은 월드 객체 였지냥, 알다시피 월드 객체에는 spawnParticle 이라는 메소드가 있지냥
해당 월드에 있는 모든 플레이어가 해당 볼 수 있는 파티클 패킷을 만들어내는 메소드냥, 물론 일정 범위
안에 없으면 패킷이 발신될 일은 없다냥 기껏 해야 몇 청크 내일꺼다냥.
나는 그 파티클 소환 메소드로 Particle 열거형에 등록되있는 EXPLOSION_HUGE 를 사용하기로 했다냥
엄청 화려한 이펙트니깐냥 아마 TNT 이펙트일꺼다냥.
두번째 인자로 이 파티클이 어디에 소환되어야 하는지 넣어야 하는데, 당연히 플레이어가 죽은
위치에서 이 파티클이 보여야 위화감이 없겠지냥, 위치 객체는 이미 위에서 받아놨다냥 근데 왠지 모르게
다시 받아다 쓰는 것 같다냥 l 이라는 변수에 넣어놨는데 그거 쓰는게 더 좋았을 것 같다냥.
다음 인자는 파티클의 갯수다냥, 이 파티클은 한개라도 화려하니깐 1을 써줬다냥.
다음 인자는 데이터인데냥, 이것은 파티클에 따라서 받는 데이터가 각각 틀려서 제네릭으로 선언되어 있다냥
하지만 루아에는 제네릭 따윈 없지냥, 애초에 이 파티클 아무것도 안받아도 제대로 이펙트가 나온다냥
nil(null) 을 넣어준다냥.
self.runDelayed(function() each(inv, function(stack) if stack == nil then return end local item = w:dropItem( l:setDirection( Vector:getRandom() ), stack ) local velo = Vector:getRandom() velo:setX(velo:getX() - 0.5) velo:setY(0.6) velo:setZ(velo:getZ() - 0.5) item:setVelocity(velo) item:setCanMobPickup(false) item:setPickupDelay(time.seconds(5)) end) end, time.seconds(0.5))
또 다시 버킷 쓰레드에 함수를 맡긴다냥 이번에는 0.5초(10틱) 뒤에 실행시키는 함수다냥.
each 라는 이상한 키워드가 있다냥, 이것은 첫번째 인자로 받은 변수가 순환 가능한 클래스면 돌면서
그 변수가 가진 요소들을 하나씩 두번째 인자에 있는 함수에 꼿아주는 키워드다냥. 근데 루아에서 지원하는건 아니다냥
내가 그냥 만든 함수같은거다냥 for 문이랑 비슷하다냥
인벤토리는 기본적으로 순환 가능한 객체다냥 Iterable 이라는 인터페이스를 상속받았지냥
버킷에서 PlayerInventory 클래스가 무엇을 구현하고 있는지 따라가보면 나올꺼다냥
그치냥?
아까 플레이어 인벤토리에서 복사받은 신상 인벤토리에서 안전하게 가지고 있던 아이템들을
하나하나 처리한다냥.
each(inv, function(stack) if stack == nil then return end local item = w:dropItem( l:setDirection( Vector:getRandom() ), stack )
하나 주의해야될 점 인벤토리를 순환하던 도중에는 null 이 나올 수 있다냥
그러니깐 널 체크는 꼭 해줘야 된다냥 안그러면 널포 나올꺼다냥
새 변수를 하나 만들자냥 item 변수에다가 월드 객체를 이용해서 dropItem 이라는 메소드를 호출냥
이 메소드는 아이템이 엔티티화(떨어져 있는 아이템 그것은 엔티티다냥)된 객체를 반환한다냥.
여기서 중요한건데냥, 버킷이 제공하고 있는 벡터 유틸리티는 랜덤 백터를 손쉽게 만들어낼 수 있다냥.
어차피 방향만 가리킬 용도로 쓰기때문에 굳이 정규화(normalize) 안해줘도 된다냥.
그 랜덤 백터로 떨궈진 아이템 엔티티의 방향을 일단 랜덤하게 설정해준다냥 기준은 죽은 위치가 되야되기 때문에
당연히 위에서 받았던 위치 객체의 setDirection 을 호출한다냥,
두번째는 떨궈야 될 아이템 스택 객체를 주면 되고냥
이러면 아이템 엔티티 객체가 생성냥.
local velo = Vector:getRandom() velo:setX(velo:getX() - 0.5) velo:setY(0.6) velo:setZ(velo:getZ() - 0.5) item:setVelocity(velo) item:setCanMobPickup(false) item:setPickupDelay(time.seconds(5)) end)
또 하나 벡터 클래스로부터 getRandom 을 호출해서 벡터를 받자냥 이번엔 가속도를 위해서다냥
가속도가 0 에서 1 범위 사이에 놀아도 마크에서는 크기 때문에 적당히 스케일 다운 해줘야 된다냥
난 그냥 몇번씩 테스트 하면서 상수값을 넣어서 빼주는 식으로 가속도를 적당히 조정해줬다냥.
velo 는 Vector 객체다냥 getX() 등등은 버킷 문서에 나와있으니깐 알아서 참조냥
이제 마무리를 해줘야 할 때다냥
아이템 엔티티에 가속도를 설정해줘야 된다냥
setVelocity 로 가속도를 설정해준다냥
setCanMobPickup 으로 몹은 이 아이템을 주울 수 없게끔 플래그를 설정한다냥.
setPickupDelay 로 약 5초간은 아이템을 그 누구도 주울 수 없는 상태로 만든다냥.
이건 내가 여러 플레이어가 자기들끼리 싸우면서 아이템을 주우느라 정신없는 아비규환을 만들어 내기 위한
꼼수 였다냥. 필요 없으면 안넣어도 된다냥 그대신 아이템이 가속도에 의해 날라가는 위치에 플레이어가 있으면
바로 먹어버릴거라서 아마 넣어야 될껄냥?
이렇게 설정해주면~ 위에 동영상에서 나온 폭산 장면을 연출해 낼 수 있을꺼다냥.
손가락이 아프다냥 이만 마칠께냥
바바냥
'^'/
엘풍
2020.07.14예술이다냥
저거 어트케 만든거냥?
참 대단하다냥
냥냐챠
2020.07.14그냥 폭산하는거 만들고 싶어서 만들었었던 것 뿐이다냥
엘풍
2020.07.14폭사냥?
mchang
2020.07.14우왕
저걸 어디다 입력 해야 하는거냥??
그리고 저런 걸 어떻게 만드는 거냥??
냥냐챠
2020.07.14바로 어디다 입력하지 못한다냥.
자바나 아니면 너네들이 자주쓰는 Skript 로
번역해서 만들어야 된다냥.
어디까지나 개념설명으로 가지고 온 코드지냥
저 코드를 그대로 쓰려면 루아를 자바에 올려놔야되는데, 오픈소스로는 Lukkit 이라는 루아 코드를 올릴 수 있는 플러그인있다냥.
나는 그 Lukkit 이라는 플러그인의 마인크래프트 에서의 루아 코드 실행 방법론만 가져다가 내 입맛에 맞게 다시 만든 사제 엔진을 쓴다냥 '^'b
마크러버
2020.07.14위에 떡하니 써있는디...냥
mchang
2020.07.14그 코드를 어디에 입력해야 하는지요
냥냐챠
2020.07.14저걸 그대로 어디로 적을 곳이 없다고 분명히 윗댓글에도 본문에도 써놨는데냥 '3'
저걸 올린건 그대로 쓰라고 올린게 아니라 각자
즐겨쓰는 언어로 번역해서 쓰라는 취지로 올린거다냥 그래서 의사코드로 봐달라고 했고 말이다냥.
mchang
2020.07.15그러니까 제가 한 말은, 글을 보고 어디다 쓰는지 궁금해서 댓을 달았더니 밤님이 알려주셔서 아, 어디로 적을 곳이 없구나, 하고 알아들었고, 마크러버님이 위에 떡하니 있다고 한게 글에 있다고 하시는 줄 알고 위의 글을 읽었는데 어디에 있는지 모르겠어서 다시 질문드린거예요
냥냐챠
2020.07.15어디다 바로 적을 수 없는게 맞다냥
Lukkit 같은거 쓰지 않는 이상 말이다냥
마크러버
2020.07.15스크립트/lua 스크립트
마크러버
2020.07.14감사하다냥
서재형
2020.07.17PlayerDeathEvent에서는 플레이어를 반환하는데도 getEntity()메소드를 쓴다냥. 이유는 모른다냥. 아무튼 여기 자바버전 코드다냥.
import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.Vector; public class Main extends JavaPlugin implements Listener { @Override public void onEnable() { // 플러그인 활성화 System.out.println(ChatColor.GREEN + "ExplosionItem 플러그인 활성화"); PluginManager pm = Bukkit.getPluginManager(); // 플러그인매니저 불러오기 pm.registerEvents(this, this); // 이벤트 등록 } @Override public void onDisable() { // 플러그인 비활성화 System.out.println(ChatColor.RED + "ExplosionItem 플러그인 비활성화"); } @EventHandler public void onDeath(PlayerDeathEvent e) { // 플레이어가 죽었을 때 Player p = e.getEntity(); // 플레이어 Location loc = p.getLocation(); // 플레이어 위치 World w = p.getWorld(); // 플레이어 월드 ItemStack[] inv = p.getInventory().getContents(); // 플레이어 인벤토리를 배열로 저장 p.getInventory().clear(); // 인벤토리 클리어 Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() { // 1초 후에 실행하는 메소드 @Override public void run() { w.createExplosion(loc, 3, false, false); // 가짜 폭발 생성 } }, 20); Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() { // 1.5초 후에 실행하는 메소드 @Override public void run() { for (ItemStack item : inv) { // 아이템 배열 순환 if (item != null) { // 아이템이 널값이 아니라면 Item di = w.dropItem(loc, item); // 아이템 소환 Vector vec = Vector.getRandom(); // 랜덤 벡터 vec.setX(vec.getX() - 0.5); // x좌표 설정 vec.setY(0.6); // y좌표 설정 vec.setZ(vec.getZ() - 0.5); // z좌표 설정 di.setVelocity(vec); // 아이템의 벨로시티를 랜덤 벡터로 설정 di.setPickupDelay(100); // 5초 후에 주울 수 있게 설정 } } } }, 30); } }
냥냐챠
2020.07.18`^`b