"That,good sir,is but a phantasy of yours."
-Arthur Dimmesdale,The Scarlet Letter
在等東方心綺樓的試玩版發布前有些無聊,因此就寫一寫Forge的事件系統吧.
事件(Event)系統是Forge在4.0版本後引入的,用最通俗的話解釋,事件就是一件事發生後,就會觸發與它相關聯的事.比如一個玩家不幸掉進岩漿里受到傷害,就會引發一個事件(之後我們會了解到這個事件是LivingHurtEvent事件),Forge的系統會將這個事件派發給相關聯的方法(Method),那些方法來決定是否處理,如何處理.
很不幸這僅僅只是通俗的解釋,實際實現起來要比這個困難一些,首先Java不原生支持事件,它採用的替代方案(基於監聽者模式,使用監聽器和匿名類)相當令人噁心,因此Forge設計了一套自己的事件系統(基於發布/訂閱模式).Forge的事件總線(EventBus)是事件系統的處理中樞,任何人都可以發布事件,任何人也都可以訂閱事件並在事件被發布時接收到它.如果有複數個訂閱者訂閱了同一事件,那麼系統會將事件按優先級讓訂閱者們依次傳閱,事件在被傳閱完畢後會重新交還給系統並返還給發布者.遊戲中支持的事件類型是被預先編程好的,當然開發者可以自己定製事件.
訂閱一個事件
事件的訂閱是通過Annotation來實現,任何一個被添加了@SubscribeEvent的方法都會被視為事件訂閱者,但要想實際起作用必須滿足這三個條件:
①:方法有且只有一個參數,並且此參數的類型必須是Event類或其派生類.
②:方法的訪問級是public,不能是靜態(static)
③:方法必須屬於一個非靜態的類.
(注:這裡所說的Event是指net.minecraftforge.event包下的Event類,而不是cpw.mods.fml.common.event包下的FMLEvent)
首先先添加一個處理事件的方法(我這裡添加到了Mod主類當中),比如這裡我想在訂閱"生物下落"(一個生物從高處摔到地上時觸發),就讓它的參數是LivingFallEvent.然後為它加上@SubscribeEvent
@SubscribeEvent public void test1(LivingFallEvent event) { if(event.entityLiving instanceof EntityPlayer) { EntityPlayer entityPlayer = (EntityPlayer)event.entityLiving; entityPlayer.addChatMessage(new ChatComponentText("Falling Star! You fell "+event.distance+ " meters.That's cool, man!")); } }
然後我們要通過MinecraftForge.EVENT_BUS.register來註冊方法所在的那個類,通常來說我們是在Mod載入時進行註冊,如果你將處理事件的方法單獨寫在一個類中(假設叫做EventContainer),那麼就在Mod主類的Init方法中添加
MinecraftForge.EVENT_BUS.register(new EventContainer());
如果你直接把處理事件的方法寫在了Mod主類中,那就直接在Init方法中添加
MinecraftForge.EVENT_BUS.register(this);
(順便一提,EVENT_BUS也提供了一個相應的方法來進行反註冊,也就是將已註冊的實例解除掉)
注:這些圖是我在舊版時截的,因此sendChatToPlayer和新版有差別...
之後進入遊戲測試一下(注意要在生存/H模式下,創造模式玩家觸發不了LivingFallEvent事件喲...)
到了遊戲中確實有效...可是這坑爹玩意卻一口氣來了兩個啊!
這是因為新版Minecraft的單人模式採用了本地服務器模擬的方式,雖然你在運行着單人模式,但實質是你在本機運行着一個服務器,然後你的客戶端連接着你自己的本機服務器.然而客戶端和服務器都個會有一個玩家實體(EntityPlayer的派生類),客戶端的是net.minecraft.client.entity.EntityClientPlayerMP,服務器端的是net.minecraft.entity.player.EntityPlayerMP.兩者都繼承自EntityPlayer,因此我們在驗證event.entityLiving instanceof EntityPlayer會返回true.這時我們就需要討論如何取捨了.
執行端的選擇,事件的監聽位置,事件的過濾
我們先依次說起這三件事,首先是執行端的選擇,我們是在服務器端對事件進行響應,還是在客戶端?通常來說,我們認為凡是需要執行實際操作的東西都要放在服務器端,畢竟客戶端和服務器端存在延遲(對於更糟的情況,客戶端還會存在作弊插件),當玩家跳到地上時,他可能會認為自己完成了一次"下落",它的客戶端也是這樣認為,因為EntityClientPlayerMP這個實體確確實實落地了,並在客戶端引發了一個LivingFallEvent事件.然而由於延遲的存在(某些服務器能存在10秒的延遲),服務器端認為EntityPlayerMP依然停留在遠處的一個平地,並沒有進行下落,由此便產生了分歧,究竟玩家是否從高處落下來了?很不幸在這裡服務器是大佬,它認為既然EntityPlayerMP沒有下落,那玩家就是沒有下落,不管客戶端再怎麼鬧騰也沒有作用,如果客戶端是個圖形效果增強Mod之類的不影響實際數據的Mod,也許問題不大,如果是會對遊戲產生實際影響的Mod,只是神知道接下來會發生什麼可怕的事情,有可能是數據不同步造成的斷線.不過假如玩家足夠幸運,在更糟糕的事情發生之前讓自己的移動數據發送到了服務器那,讓服務器做出及時更新的話,或許結果會好一點,但我們禁不起這樣的風險,玩家會認為是我們的Mod有問題,而服務器管理者會考慮卸載掉我們的Mod,畢竟與其花高價租個更好的服務器,管理者更傾向於點點鼠標刪掉一個Mod,然後隨便敷衍玩家幾句了事.在這裡,我選擇在服務器端處理這個事件.對於如何控制執行端,一個是通過事件的過濾,一個是通過@SideOnly,但我不建議使用@SideOnly,除非是必要,否則最好不要考慮它.
對於事件的監聽位置,我建議在一個統一的類內執行,但也並非沒有特例,設想這樣的一個情景,我們做了一把很NB的劍,被它砍中的實體會立刻飛到高度255的地方然後信仰之墜,而且我們為了秀技術刻意不使用onLeftClickEntity方法,而使用一個訂閱LivingHurtEvent事件的方法,那麼我們該怎麼做?我們可以將事件放在Mod主類里,也可以專門建立一個類來監聽,但我們也可以把它直接放在那把劍的物品類里,並在"很NB"的劍的類的構造函數(希望你沒看暈)內註冊它自己.別忘了Minecraft的物品是採用類似於享元模式的方式啊...但注意不要讓事件監聽被註冊的太多,否則會對性能產生巨大的負面影響.
最後我們到了事件的過濾這個問題了,Forge的事件系統的發布方式需要稍微解釋一下,Forge的所有事件(再次強調,不是FML的事件)都繼承自Event類,如果你訂閱了一個事件(類),那麼任何該類及其派生類都會在發布時發送給你.比如LivingFallEvent就是繼承自EntityEvent,那麼如果你訂閱了EntityEvent的話,不光是EntityEvent事件發布時你會收到一份,LivingFallEvent事件發布時也會發給你一份,因為LivingFallEvent是EntityEvent的派生類."既然你連大事都管了,那麼小事你也得管",這或許和現實以及大多數人的認識相反,但這確實是Forge的事件系統的實際寫照.因此事件的過濾就分為兩部分,首先是事件類型的過濾,如果你真的訂閱EntityEvent事件的話我建議你一定要三思,因為LivingUpdateEvent這個事件是每一個實體在每一個Tick(幀)都要執行一次,你最好把它過濾掉,它的過濾判斷表達式是"傳來的事件 instanceof LivingUpdateEvent == false".第二部分的過濾是事件源的過濾,就拿我們實際遇到的情況舉例,EntityClientPlayerMP和EntityPlayerMP都會在同一時間引發相同的事件,既然我選擇在服務器端執行更新,那麼我就過濾掉只存在於客戶端的EntityClientPlayerMP,保留EntityPlayerMP.這個的過濾方法就有很多,對事件的參數進行判斷就行了.
另外再提出一個忠告:訂閱Event事件之前請三思.
小小修改一下代碼,將
event.entityLiving instanceof EntityPlayer
改為
event.entityLiving instanceof EntityPlayerMP
(至於下面的"(EntityPlayer)event.entityLiving"是否修改,在這裡是隨便的,畢竟我只是想過濾一下事件源而已...)
再次測試,這一次我們成功了.(教練我懶得截圖了...自行腦補成功的樣子)
(說的輕巧,事實上作者為了研究這個問題燒了一個晚上的時間...)
事件的優先權
有些時候,會有複數個訂閱者同時監聽着一個事件,有些情況下他們是互相競爭的,因此就有了優先權的設定,優先權分為五級:最高,高,普通(這個是默認的),低,最低.事實上只有前三個才會被開發者考慮,SB才會讓自己的事件排到最低級...對吧= = 所以必要時就有些公德心吧,別把無聊的東西排在高優先級處.
安排優先級的方法是修改@SubscribeEvent的priority屬性.比如:
@SubscribeEvent(priority = EventPriority.HIGH)
這個訂閱的優先級是High,它只會和同優先級的訂閱者競爭,對於比它低的訂閱者,它佔有絕對的優先權.我們這就來試試它的效果,將我們之前的方法改為(即刪掉整個test1然後輸入下面的代碼):
@SubscribeEvent(priority = EventPriority.HIGH) public void test2a(LivingFallEvent event) { if(event.entityLiving instanceof EntityPlayerMP) { EntityPlayer entityPlayer = (EntityPlayer)event.entityLiving; entityPlayer.addChatMessage(new ChatComponentText( "Falling Star! You fell "+event.distance+ " meters. That's cool, man! I will protect you from the falling damage!")); event.distance = 0.0f; } } @SubscribeEvent(priority = EventPriority.LOW) public void test2b(LivingFallEvent event) { if(event.entityLiving instanceof EntityPlayerMP) { EntityPlayer entityPlayer = (EntityPlayer)event.entityLiving; entityPlayer.addChatMessage(new ChatComponentText( "Falling fool! You had fallen... Damn it, WTF "+event.distance+ " meters? Are you fooling me?")); } }
再次進行測試,這次無論我們從多高的地方跳下去,都不會受傷(雖然會發出pia的聲音),因為test2a方法先截獲了跌落事件,並篡改了結果,因此它即糊弄了test2b方法,又欺騙了Minecraft的系統.使得玩家免遭傷筋斷骨之苦.
事件的結果
有時我們我們處理完一個事件後,需要告訴後來人我們已經處理掉它了,還有些情況下,這個事件最終會傳回Minecraft內核,供它就事件的結果進行決策,因此我們就有了Result這個東西.
Result是一個Enum(枚舉)類型.我們通過setResult和getResult來設置或讀取.Result有三個值:ALLOW(允許),DENY(否決)和DEFAULT(默認),然而,這玩意耍起來有風險.
首先,有些事件的Result是供你隨便耍的,有些則會影響到遊戲的運行,前者我稱之為無Result事件,後者我稱其為有Result事件,判斷它的辦法是調用hasResult方法,如果它返回true就代表它有Result,那麼就請仔細閱讀這個事件的JavaDoc.比如玩家拿着水桶對着物體(不一定是實體)按下鼠標右鍵時引發的FillBucketEvent事件就是有Result的事件,如果在最終傳回遊戲內核時它的Result的結果是ALLOW的話,那麼就會強制視為一次成功的操作.對於無Result的事件,它僅僅只是"Result這個參數對它沒有意義",而不代表我們用不了Result.LivingFallEvent就是無Result事件,但我們接下來就用它展示一下Result的使用.
(另外,由於Forge製作組的烏龍,不要將Result和某些事件里的result參數搞混,前者才是我們現在所說的Result,後者只是代表着傳來事件的某一參數.)
在test2a中加入:
event.setResult(Result.DENY);
在test2b中加入:
entityPlayer.addChatMessage(new ChatComponentText( "I understood... She made the event " + event.getResult().toString() + "."));
之後進入遊戲測試一下.
事件的取消
有時在我們執行完一個事件的操作後,希望能取消掉這次事件,讓後面的訂閱者無法處理它,同時也讓遊戲內核認為這個事件沒有發生.對於任何具有Cancelable(可取消)屬性的事件來說,我們都可以實現這一目標.
要判斷一個事件是否是可取消的,就調用它的isCancelable方法,如果返回的是true就代表它是可取消的,此外你也可以通過在IDE中直接觀察那個事件是否具有@Cancelable注釋來判斷它能否被取消.
圖:LivingFallEvent具有@Cancelable注釋,因此它是可取消的.
設置一個事件被取消的辦法是調用它的setCanceled方法,setCanceled的參數是一個bool,true代表此事件取消,被取消的事件在調用isCanceled時會返回true,並且尚未接收它的訂閱者們默認將不再會接收它,Forge的事件系統也將向發布者返回一個true(默認是返回false,具體的返回機制我會在後面解釋),對於已取消的事件,你也可以通過setCanceled(false)來複活它.
(注意,對於不可取消的事件,千萬不要setCanceled(true),遊戲會立刻拋錯的)
現在,將我們的test2a方法中的
event.distance = 0.0f;
替換為
event.setCanceled(true);
然後保存,測試.
這一次你依然是不會受到跌落傷害,甚至都不會收到第二個訂閱者的信息,因為你將這個事件取消掉了.
那麼被取消的事件該如何復活呢?如果要讓別的訂閱者復活它的話,首先我們得讓那個訂閱者能接收它,默認情況下訂閱者不會再接收被取消的事件,但我們可以通過修改@SubscribeEvent的receiveCanceled屬性來讓它能接收被取消的事件.對於任何receiveCanceled = true的訂閱者來說,它都可以接收被取消的事件.
將我們的test2b替換為
@SubscribeEvent(priority = EventPriority.LOW,receiveCanceled = true) public void test2b(LivingFallEvent event) { if(event.entityLiving instanceof EntityPlayerMP) { EntityPlayer entityPlayer = (EntityPlayer)event.entityLiving; entityPlayer.addChatMessage(new ChatComponentText("F@CK U Maribel!I know you are there!")); event.setCanceled(false); } }
事件的發布
事件的發布不比發一顆衛星容易多少,首先你要掌握軌道事件學,應用事件學,量子事件學,理論事件學,高維度事件學,隙間事件學,虛數維度事件學,亞空間事件學,節點空間事件學,遷躍斷層事件學,翹曲事件學,子維度事件學,相對事件學,形而上事件學,混沌事件學,核事件學,高等事件學,代數事件學,幾何事件學,事件工程學,事件哲學,事件心理學.此外你還得了解十事件問題,最小事件問題,事件樹等算法.最後你還得祈禱Yukari在和Cthulhu對轟時不會從你這裡神隱走幾個即將發送的事件,當做彈幕發射出去.
幸運的是,這些事情Forge全幫你包辦了!所以發布一個事件的方法是很容易的!
開發者可以通過調用事件總線(EVENT_BUS)的post方法來發布一個事件,post方法會返回一個bool值,true代表這個事件被取消了,false代表它順利執行到了最後.建議的格式是這樣的.
初始化一個事件的實例 MinecraftForge.EVENT_BUS.post(事件) 判斷事件的結果
看到的時候被雷了一下對吧...我這裡破天荒地使用了偽代碼,因為我自己實在懶得編一個拋出事件的例子,因此我就以Minecraft的ItemBow(弓)的拉弓部分為例:
ArrowNockEvent event = new ArrowNockEvent(par3EntityPlayer, par1ItemStack); MinecraftForge.EVENT_BUS.post(event); if (event.isCanceled()) { return event.result; }
ArrowNockEvent是拉弓事件,它的參數分別是觸發玩家的實體和玩家所拿的弓.
MinecraftForge.EVENT_BUS.post(event)則是將剛剛初始化完畢的事件向訂閱者發送出去,事件總線會完成事件的發布工作(為你省了很多事呢!笑).並最終返還給你返回值.
之後就到了對事件的處理工作了,由面向對象的特性可知,event這個事件在被發布出去後已經被各位訂閱者們糟蹋了一通了.因此我們不必寫一個專門接收處理結果的代碼,直接對event操作就能獲取處理結果.
(小吐槽:其實官方的那段代碼可以被簡化的,post返回的就是isCanceled的結果)
自定義事件
現在開始才是高潮吧,Satori?
自定義事件的發布和固有事件的發布沒有差別,唯一問題在於如何創建自定義事件.
既然所有事件都是繼承自Event類,那我們也創建一個繼承自Event的類好了.
並沒有太多需要注意的東西,一個是在構造函數中要調用基類的構造函數(super();),第二就是如果你不希望訂閱者修改你的事件中的參數的話,就將它們聲明為final.
此外我們可以設定它有無Result,能否被取消.
設定一個事件有Result,是在它的類上方加入@Event.HasResult
設定一個事件可以取消,是在它的類上方加入@Cancelable
之後我寫了一個很無聊的代碼,它的作用是在玩家輸入KABOOM時讓自己周圍的生物全都遭遇一次爆炸.
@SubscribeEvent public void letsrock(ServerChatEvent event) { if(event.message.equals("KABOOM")) { event.setCanceled(true);//截獲玩家的指令並不讓它顯示在屏幕上,用來模擬遊戲指令(Command) EntityPlayer player = event.player; EventHANDRU eventHANDRU = new EventHANDRU(player);//初始化一個事件 MinecraftForge.EVENT_BUS.post(eventHANDRU);//發布它 if(eventHANDRU.getResult() == Result.ALLOW) { //這個長的讓人髮指的東西是獲取玩家附近的生物 List list = player.worldObj.getEntitiesWithinAABB(EntityLiving.class, AxisAlignedBB.getBoundingBox(player.posX-30D, player.posY-20D, player.posZ-30D, player.posX+30D, player.posY+20D, player.posZ+30D)); //值得一提的是我這裡使用的是遍歷器,傳統的下標遍歷因為無法鎖定資源可能導致ConcurrentModificationException... for(Iterator iterator = list.iterator();iterator.hasNext();) { EntityLiving entity = (EntityLiving)iterator.next(); if(entity.equals(player)) //別把自己也給炸了... { continue; } player.worldObj.createExplosion(player, entity.posX, entity.posY, entity.posZ, 4f, true); } } } } @SubscribeEvent public void goodbyeRenko(EventHANDRU event) { event.entityPlayer.addChatMessage(new ChatComponentText("Have a nice day, Renko Usami."));//歡迎來到冥界,宇佐見蓮子. event.setResult(Result.ALLOW); }
進入遊戲後輸入KABOOM,然後看煙花吧...不過有些耐操的動物比如豬可能無法一發炸死.
結尾
"別這樣,紫,我們都是女孩子..."
-Reimu Hakurei,紫妹異聞錄
就這樣結束了嗎...也許是吧,Forge的事件系統就只有這麼點東西可說.然而如果你有興趣,你可以去研究更加高深的東西,例如事件的getListenerList是什麼東西?FML的事件系統怎麼使用?它和Forge的事件系統有什麼關係?學海無涯啊少年...願你在Forge之路上愈行愈遠吧,裝哉我大Forge!F@CK THE BUKKIT!
附錄1.OMG到底有幾條總線!?
事實上,Forge不只有Event_Bus這1個事件總線,Forge總共有3個事件總線,分別是TERRAIN_GEN_BUS(地形生成總線),ORE_GEN_BUS(礦物生成總線)和EVENT_BUS(我們敬愛的,事件總線).3個總線很好區分,分工也很明確,我就不詳細說明了...(群眾:我們去年買了個表)
あの...那個test2b是鬧哪樣啊?就是那個調戲z......嗚......嗚...
-v-~
SZ會寫Java?
為什麼不會...
szszss ,謝謝你在第二章給我的"字符串的演示package", 裡面只有一個檔案, 而且程式碼相當簡潔, 正合我胃口.
程式碼內容看了很多遍, 我是拿著你的程式碼一步一步的KEY進去我的新專案, 在編輯過程中一直遇到兩個Errors, 訊息是
RenderGameOverlayEvent cannot be resolved to a type.
TEXT cannot be resolved or is not a field.
然後我就去查最上面的 import, 我發覺我的Eclipse它一直找不到這兩個
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;
然後, 我又去查Package Explorer顯示所載入 Minecraft 的package, 我找了
net.minecraft.client.renderer
net.minecraft.client.renderer.culling
net.minecraft.client.renderer.entity
net.minecraft.client.renderer.texture
net.minecraft.client.renderer.tileentity
這五個, 它裡面都沒有.
我想請問一下
net.minecraftforge.client.event.RenderGameOverlayEvent;
net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;
這兩個包你是從哪裡得來的, 為什麼我的1.5.1版裡都沒看到?
正常情況下, 如果程式編輯器偵測到我們輸入的程式碼有需要的package , 只要我們選擇 import 那一項後它會自動幫我補進JAVA文件的import區裡面, 現在的問題就是我在Eclipse編輯器裡看Minecraft的所有包裡面沒有這兩個package.
ElementType.TEXT 這個欄位也找不到, 我把這字串刪到剩 ElementType, 重新輸入, 後面輸入 點 就出現小選單一堆可以選的欄位, 裡面就是沒有TEXT 這欄位.
我用的Minecraft 是標準的1.5.1正常版, Forge 也是 minecraftforge-src-1.5.1-7.7.1.611.zip 標準版, MCP是由forge安裝時自動下載自動安裝的, 應該也是標準版, 我用的這些軟體版本跟你的不一樣嗎?
7.7.1.611確實有些舊了...RenderGameOverlayEvent是7.7.1.665新增的哦^ ^ 快去http://files.minecraftforge.net/下新版吧
謝謝你告訴我, 我查看了一下我目前用的Forge版本與 http://www.minecraftforge.net/forum/index.php/board,3.0.html
官網的版本, 真的差很大...
我又得重灌了XD
我下載了 minecraftforge-src-1.5.1-7.7.2.678.zip 這版本來使用.
雖然能成功顯示了, 但有一點我不明白, 不知道是不是Eclipse編輯器的問題, 在
if(event.type == ElementType.TEXT && RenderManager.instance.getFontRenderer() != null)
這一段程式碼裡, ElementType.TEXT 這句一直用不出來, import 方面它也沒有自動幫我載入
import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;
然後我改輸入elementtype.TEXT 後, 滑鼠指過去停留一下, 它就顯示一些可以選的項目, 沒有出現 import 的項目, 後來我就選 change 那一項, 結果它自動幫我變成了這樣子
if(event.type== net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType.TEXT && RenderManager.instance.getFontRenderer()!= null){
然後紅色底線的字都沒有再出現了, 也就是沒有errors的狀況.
我在猜, 是不是Eclipse編輯器的問題, 我目前用的是這個
http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/junosr2
Windows 64-bit 版的.
名字叫 Eclipse IDE for Java Developers
純英文界面.
我想好好的學習, 所以, 我想我該用跟你一樣的編輯器才行, 可以推薦哪個軟體哪個版本的較好上手?
另外, 我們目前在寫程式使用startclient.bat來啟動的, 這樣子的環境是不是叫Debug環境? 在這樣子的環境下, 我要怎麼安裝小地圖模組(ZansMinimap1.5.1)進去這種開發環境?
因為平時用小地圖習慣了, 一下子沒小地圖, 用起來就覺得礙手礙腳的XD
這是由於Java的類重名現象導致,名字叫ElementType的類有兩個,一個是Forge的ElementType,一個是Java的,你import了java的ElementType所在的package...所以就有了那個情況,你的代碼中肯定有一行import java.lang.annotation.ElementType;,將它改爲import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;就好了.
我確實是一直在用Eclipse來啓動Minecraft,具體的方法可以看一下第二篇Extra教程.(至於小地圖的問題,這個似乎是無解的...網上下載的小地圖Mod都是混淆過的,無法用在開發環境下...似乎新版FML自帶反混淆器,但我不太確定...)
至於Eclipse的版本,這個並不重要,我因爲個人需要用的Eclipse for RCP and RAP Developers,Eclipse IDE for Java Developers足以滿足Java開發的需要了.
原來是CLASS名稱重覆的現象, 了解.
看來小地圖MOD是沒希望了...
這張圖是我最近的學習成果: http://imageshack.us/photo/my-images/546/20130504161147.png/
非常謝謝你的指導.
最近研究到 world 裡放置一個自己想要的方塊, 使用 setBlock 這指令.
但是, 有個疑問, 我要如何得知由 setBlock 指令所產生的方塊的ID, 因為我想用這些我所產生的ID來進行後續處理.
例如我在 X=10, Y=15~17, Z= 12 的地方用 setBlock產生3塊石頭, 我想在後續處理時, 只針對這3塊我產生的石頭進行 setBlockToAir 的動作. 請問, 較簡易的作法為何?
另一個問題是音效, 目前版本所使用的音效播放規則跟舊有的版本使用方式一樣嗎? 看了很多遍依然抓不到頭緒, 例如 playAuxSFX, playAuxSFXAtEntity, playSound, playSoundAtEntity, playSoundEffect, playSoundToNearExcept這些指令, 為何單單只想放出一個音效卻要分成那麼多種類的指令? 它們之間異性在哪裡?
今天如果我想要按滑鼠右鍵時放出一個方塊, 然後播放一個簡短的音效, 有比較簡易的作法嗎?
而且, 我到現在還是想不透, 那些音效檔放的目錄以及存取時使用的指令規則是什麼 -.-?
很遺憾Minecraft中沒有磚塊的ID編號這個說法...爲了優化性能和節約存儲空間,Minecraft只記錄下每個坐標所放置的磚塊是什麽,以及它的Metadata是什麽.需要有特殊功能的磚塊通過TileEntity來實現...(正好新版教程還沒寫到TileEntity,你可以去看看MCBBS上的老版教程中TileEntity的部分).如果要記錄某幾個磚塊的位置,我的想法是使用NBT來儲存...(很遺憾似乎新版教程也沒寫到NBT的部分,去看老版吧...離高考只剩一個月,時間不太充裕(其實這只是借口)沒時間碼太多字,如果還不會的話再來問我吧XD)
音效問題我還從來沒研究過,那我就再發揚一次現學現賣的傳統吧...
首先是RenderGlobal類中的playAuxSFX,它是用來播放一個已經設定好的聲音特效,我強烈不推薦使用它,因爲它的所有可播放的音效都是被預先設定好的,而且精度有問題(雖然非常不明顯吧...)
然後是World類,playSoundAtEntity是在一個實體上播放3D音效,它的參數分別是"實體","音效名"(如:"mob.ghast.fireball"是亡魂的火球聲),"音量"(1.0爲標准音量),以及..."音調"(pitch...我也不知道專業術語叫什麽,同樣1.0是標准音調).
playSoundToNearExcept是在某玩家身上播放一個3D音效,神奇的是只有其他玩家能聽見,該玩家本人聽不見,它的參數和上一個類似,只不過"實體"換爲"玩家實體".
playSoundEffect是在某點播放一個3D音效,參數是"x","y","z","音效名","音量","音調".
playSound不推薦使用,建議使用playSoundEffect.
最後是SoundManager類,比較值得一提的是playSoundFX指令,它是播放一個2D音效,比如玩家操作菜單時的音效就是2D音效,沒有音源方位等影響因素.另外它的addSound指令是加載一個聲音文件,參數是"音效名","文件".
NBT部分似乎難度很高, 我先放一旁好了.
音效方面我大概瞭解了, 謝謝.
假設我想要創造一個新的屬性用於玩家的基本能力值, 例如增加一個魔法力(MP), 基礎值 100
我應該把此變數設定在哪個地方較好?
像這種的屬性是不是應該屬於全域變數比較好?
因為我想要此變數在日後進行施法或使用道具後, 能對此變數做增減值的動作.
好吧,我試驗了幾次後才發現原來沒有那麽簡單...我以前只想到了數據的存儲,但沒想到數據的同步,存儲可以通過NBT存儲在EntityPlayerMP(服務器中的玩家Entity)中,但數據沒法從服務器端同步到客戶端,我想用DataWatcher來同步數據,結果試了幾次都失敗了...也許以後我會試試別的辦法,比如用自定義封包(即Packet,好像在台灣管它叫分組)來同步數據啥的...
多人模式的資料傳輸我還沒資格碰, 不懂XD
目前碰到一個難題, 我有2個Entity都使用相同的Render來畫子彈, 日後還會增加更多的Entity, 但也是使用相同的Render來畫子彈而已, 以下我使用目前的程式碼, 會出錯:
MOD主類裡的設定:
EntityRegistry.registerModEntity(EntityMagic01.class, "Magic01", 1, this, 64, 1, true);
RenderingRegistry.registerEntityRenderingHandler(EntityMagic01.class, new RenderMagic());
EntityRegistry.registerModEntity(EntityMagic02.class, "Magic02", 1, this, 64, 1, true);
RenderingRegistry.registerEntityRenderingHandler(EntityMagic02.class, new RenderMagic());
ItemA 裡的設定:
world.spawnEntityInWorld(new EntityMagic01(world, myplayer));
ItemB 裡的設定:
world.spawnEntityInWorld(new EntityMagic02(world, myplayer));
Render裡的設定:
public class RenderMagic extends Render{
public RenderMagic(){ super(); }
public void doRenderMagic(EntityMagic01 entityThrownItem, double d0, double d1, double d2,float f, float f1){
GL11.glPushMatrix();
~畫子彈過程省略~
GL11.glPopMatrix();
}
public void doRender(Entity entity, double d0, double d1, double d2, float f, float f1) {
this.doRenderMagic((EntityMagic01)entity, d0, d1, d2, f, f1);
}
}
請問, 有什麼寫法可以讓 RenderMagic() 給很多個Entity 共用?
我想問題應該是出在doRender裏,this.doRenderMagic((EntityMagic01)entity, d0, d1, d2, f, f1)將entity強制類型轉換爲EntityMagic01,所以導致了EntityMagic02沒法使用這個Render...我的想法是讓EntityMagic01和EntityMagic02都繼承一個類,比如EntityMagic,然後將Render寫成:
public class RenderMagic extends Render{
public RenderMagic(){ super(); }
public void doRenderMagic(EntityMagic entityThrownItem, double d0, double d1, double d2,float f, float f1){
GL11.glPushMatrix();
~畫子彈過程省略~
GL11.glPopMatrix();
}
public void doRender(Entity entity, double d0, double d1, double d2, float f, float f1) {
this.doRenderMagic((EntityMagic)entity, d0, d1, d2, f, f1);
}
}
這種做法類似於RenderLiving,基本上任何繼承EntityLiving的類都能使用RenderLiving作爲渲染器,同樣的,任何繼承EntityMagic的類也都能使用RenderMagic.
另外對於world.spawnEntityInWorld(new EntityMagic01(world, myplayer));我建議寫成:
if (!world.isRemote)
{
world.spawnEntityInWorld(new EntityMagic01(world, myplayer));
}
這是爲多人遊戲而考慮的,多人遊戲中Entity只應由服務器端來創建.