"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個總線很好區分,分工也很明確,我就不詳細說明了...(群眾:我們去年買了個表)
謝謝提供建議, 我去好好思考一下.
我目前用你提的方法, 先自製一個叫 EntityMagic 的class, 然後讓EntityMagic01和EntityMagic02使用的參數都改寫為 EntityMagic , 編譯可以過, 但執行下去就會出錯了.
EntityMagic.java 內容如下:
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.projectile.EntityThrowable;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
public class EntityMagic extends EntityThrowable{
public EntityMagic(World world){
super(world);
}
public EntityMagic(World par1World, EntityLiving par2EntityLiving) {
super(par1World, par2EntityLiving);
// TODO Auto-generated constructor stub
}
@Override
protected void onImpact(MovingObjectPosition movingobjectposition) {
// TODO Auto-generated method stub
}
}
我這樣算只完成了開頭, 對吧!?
正確來看, 是不是我必須把 EntityThrowable.java 裡的所有內容都照抄進來才對?
所以, 我暫時先用 Entity entity 代替 (EntityMagic01)entity.
另外, 我有另一個疑問, 是關於Render 的生命週期, 在 EntityMagic02.java 裡的部分內容我使用如下:
@Override
protected void onImpact(MovingObjectPosition objMOP){
if(objMOP.typeOfHit == EnumMovingObjectType.ENTITY){
double OrgX = objMOP.entityHit.posX;
double OrgY = objMOP.entityHit.posY - 1;
double OrgZ = objMOP.entityHit.posZ;
for(int ypos = (int)OrgY; ypos < 3 + (int)OrgY; ypos++){
this.worldObj.setBlock(OrgX, ypos, OrgZ, Block.dirt.blockID);
}
}
this.setDead();
}
@Override
protected float getGravityVelocity(){
return 0.0F;
}
在這個碰撞事件裡, 是不是表示這顆子彈最後有碰撞到任何一個東西他都會 setDead(), 是不是?
我現在可能就是卡在這邊, 因為我有設置它射出去後是直線飛行, 永遠不會往下掉, 所以, 當我
朝天空或者最遠處沒有任何障礙物射出去後, 我隨後再沿著他直線飛行的路徑去看, 結果最後會看
到他繪製的子彈停留在空中, 怎麼摸怎麼碰都無法消除他, 到底是 EntityMagic02.java的問題, 還是
RenderMagic.java的問題?
我該在哪裡設置描繪這顆子彈的生命週期? (我希望他距離原始出發點20格後就自動消失)
希望我一直提問題不會影響你考試, 我可以等你考完試再來慢慢討論.
你可以參考一下EntityFireball的做法,它重寫了onUpdate,通過一個計時的int來限制火球的生命周期.另外this.setDead()也是必須在服務器端執行的,具體可以看EntitySnowball的onImpact中的寫法.
但願能解決子彈不消失的問題吧...
試了很多種方法, 最後發覺還是要用到NBT, 這方面我仿照EntityFireball的內容, 可以做一個計時器.
EntitySnowball方面不行, 它用的方法很單純, 沒用到計時器, 因為原生EntitySnowball沒有使用NBT, 而且也沒有
使用計時器的功能來讓雪球自動消失.
而且, 我發現我提的問題點不在 onImpact() 事件裡, 因為此事件是要這個Entity有擊中任何的東西時, 它才會動作, 而我目前的需求是要讓這個Entity在經過很久的時間後依然沒擊中任何東西就自動消失.
目前我額外加一個 onUpdate() 事件, 而且事件裡還要 super.onUpdate() 才行, 在計時器方面我設定了40tick後, 如果此Entity還沒擊中任何目標會自動爆炸, 執行後, 我試著射向天空, 畫面中沒看到我自製的子彈(射擊出去的一瞬間有看到而已), 但有看到射擊目標很遠的地方它自爆了.
我在想一個問題, onUpdate 跟 Render 是不是有很大的關係?
如果我不加onUpdate 事件就可以看到子彈射出去的樣子, 但是就無法讓計時器動作.
如果我加了onUpdate 事件就看不到子彈射出去的樣子, 但是計時器可以正常動作.
目前暫時解決生命週期的問題了, 不過我還是想不通 onUpdate 跟 Render 之間到底有什麼關聯.
我是在猜, 因為我 extends EntityThrowable 這個entity, 而EntityThrowable檔案裡我有看了很多遍, 是不是因為這檔案裡它是把物體移動的一些繪描指令寫在 onUpdate 事件裡, 而造成我不能再使用 onUpdate 來做其它事, 因為只要我註解掉 EntityMagic02裡的 onUpdate 就可以看到子彈飛出去的圖片, 沒註解掉時就只有發射的一瞬間有看到圖片, 之後就什麼都沒看到.
在MOD主檔我是用 RenderingRegistry.registerEntityRenderingHandler 來註冊Render, 假如果我不做註冊的動作, 改在 EntityMagic02.java 檔裡的 onUpdate 事件自己寫描繪子彈的程序, 不知道這方法行不行得通?
請問您的KABOOM的代碼里
取消了事件之後那幾行代碼有什麼用嗎?我感覺就是一個無關的自定義事件...試過注釋掉也似乎沒問題啊...
不錯的教程,但是我看完後有個問題想請教下。在客戶端mod監聽什麼事件才能在登錄服務器時觸髮指定函數?如在一個client side mode中檢測登入指定服務器時彈出gui. 謝謝!
在客戶端監聽玩家登陸服務器好像沒有太好的方法...FML有個PlayerEvent但僅供服務器使用,我的想法是在客戶端監聽EntityJoinWorldEvent事件,然後判斷"event.entity instanceof EntityClientPlayerMP",同時還要有個變量用來記錄玩家是否是剛剛進入服務器,因為EntityJoinWorldEvent是實體每次被添加到世界時觸發,因此玩家復活和進入其他世界時也會觸發,同時還要在玩家退出服務器時置零這個變量...總之感覺是個很蹩腳的實現.假如是客戶端和服務器協同工作的話,可以是服務器用PlayerLoggedInEvent監聽到玩家進入服務器後,用EntityPlayer的openGui來在客戶端顯示一個界面,不過這還需要在NetworkRegistry中註冊一個GuiHandler.
“同時還要在玩家退出服務器時置零這個變量”這個需要怎麼實現呢?監聽什麼事件?
感謝你的教程,不過說到弓呢我剛好請問一下,我新建了弓,裡面所有代碼都照抄ItemBow,但是為什麼拿在手上的顯示方法還是和一般物品一樣而不是像弓一樣?(以第三視角看的話)還有我新建了一個EntityArrow,可是我找不到EntityArrow的貼圖路徑該在哪裡改.十分不好意思請教你,不過我真的找不到辦法...
基本上凡是跟渲染相關的東西都是在RenderXXX中,比如EntityArrow的渲染是RenderArrow. MCBBS上的老版教程中的實體章節有說如何改渲染,但新版中漏掉了,算是我的一個疏忽... (其實就是懶)
有兩個個問題想問一下:
1、註冊一個事件的訂閱必須在Init過程或者Init之後是嗎?我在ClientProxy的preInit過程註冊結果出錯了qwq
2、對於像是RenderPlayerEvent這類和渲染有關的的事件,是可以只在客戶端進行註冊,還是兩邊都要進行註冊(或者直接在兩邊都會有的類裡面統一註冊一次),還是只可以在客戶端註冊呢?
1.按說註冊事件是什麼時候都行的,那個出錯提示是啥...
2.只在客戶端註冊
怎麼綁定一個按鍵 然後點擊那個按鍵模擬玩家發送命令
(我只是想做個傻瓜版輸入命令的233)
在客戶端可以用ClientCommandHandler的executeCommand來模擬玩家向服務器發送命令,返回值代表執行結果.
關於如何綁定按鍵我好像在GUI那篇里寫了.
你好,我用1.7.10的版本雖然能監聽和攔截,但一調用entityPlayer裡面的方法遊戲就閃退了(entityPlayer!=null) ,不知道出於什麼原因呢