[1.2.5 - 1.7.10]クリエイティブのような飛行モードの実装の仕方[Forge]

クライアント/サーバーMODの開発に関する話題、技術交換はこちらで。質問は質問フォーラムへお願いします。
  • (PostNo.106144)

[1.2.5 - 1.7.10]クリエイティブのような飛行モードの実装の仕方[Forge]

投稿記事by A.K. » 2013年6月08日(土) 23:24

クリエイティブの飛行モードになれるMODやそのためのアイテムを追加するMODは昔からあるのですが,
ここで紹介するものは”クリエイティブのような飛行モード”です.
何故クリエイティブの飛行モードじゃだめなのかのウンチク
そもそもクリエイティブの飛行モードはEntityPlayeのcapability変数のallowFlyingというBoolean型変数で管理されています.
この変数の真偽を切り替えることでサバイバルでも飛べるようになります.
(ちなみに飛んでいる状態はisFlyingで管理されています.)
その変数はpublicなのでEntityPlayerのインスタンスさえ手に入れば簡単に操作することが出来るのですが,管理変数が1つであることによって弊害が出てきます.
例えば,2つのMODが別々に「手に持っている間だけ飛行モードになれる」アイテムを追加したとします.
利用者がその2つのMODを入れた時にどうなるかというと,2つのMODのどちらのアイテムを手に持っていても飛行モードになれない状況になります.
これは互いが互いのアイテムを認識できずallowFlyngをfalseにし続けているからです.

従って,競合を回避するあるいは競合による不必要な不具合報告を回避するという観点からクリエイティブの飛行モードの代替システムを実装する必要があるのです.


今回はForgeのLivingUpdateEvent(1.2.5版はIEntityLivingEventHandler)を使って実装します.
これらを使わなくても実装しようと思えば出来ますが,クリエイティブの飛行モード自体がEntityPlayer のLivingUpdateで実装されているのでこちらを利用するほうがスマートだと思います.
まず必要なのは,LivingUpdateEventを実装したクラス(1.2.5ではIEntityLivingHandlerをインターフェースにもつクラス)です.
LivingUpdateEventHooks.java(1.3.2以降)
コード: 全て選択
package 適当
import 必要なもの
public class LivingEventHooks
{
   private boolean allowLevitatiton = false;//飛べるかどうか
   private boolean isLevitation = false;//飛んでいるかどうか
   private int flyToggleTimer = 0;//Jumpキーの入力間隔
   private int sprintToggleTimer = 0;//ダッシュの入力間隔(何故必要なのかは後述)
@SubscribeEvent//(1.6までは@ForgeSubscribe)
public void LivingUpdate(LivingUpdateEvent event)
{
if(event.entityLiving != null && event.entityLiving instanceof EntityPlayer && event.entityLiving .worldObj.isRemote)
{
EntityPlayerSP player = (EntityPlayerSP) event.entityLiving;
Flight(player);
}
}
//落下時ダメージ無効化処理。LivingFallEventが実装されたバージョンのみ
   @SubscribeEvent
    public void onPlayerFall(LivingFallEvent event) {
        if (event.entityLiving instanceof EntityPlayer) {
            EntityPlayer player = (EntityPlayer)event.entityLiving;
            if (this.allowLevitatiton) {
                event.setCanceled(true);
            }
        }
    }

public void Flight(EntityPlayerSP player)
{
//ここに処理を書く.
}
}

EntityLivingHandler.java(1.2.5)
コード: 全て選択
package 適当
import 必要なもの
public class EntityLivingHandler implements IEntityLivingHandler
{
   private boolean allowLevitatiton = false;
   private boolean isLevitation = false;
   private int flyToggleTimer = 0;
   private int sprintToggleTimer = 0;
   @Override
   public boolean onEntityLivingSpawn(EntityLiving entity, World world,float x, float y, float z) {return false;}

   @Override
   public boolean onEntityLivingDeath(EntityLiving entity, DamageSource killer) {return false;}

   @Override
   public void onEntityLivingSetAttackTarget(EntityLiving entity,EntityLiving target) {}

   @Override
   public boolean onEntityLivingAttacked(EntityLiving entity,DamageSource attack, int damage) {return false;}

   @Override
   public void onEntityLivingJump(EntityLiving entity) {}

   @Override
   public boolean onEntityLivingFall(EntityLiving entity, float distance) {   return false;}

   @Override
   public boolean onEntityLivingUpdate(EntityLiving entity) {
      if(entity != null && entity instanceof EntityPlayer)
      {
         EntityPlayer player = (EntityPlayer) entity;
                        Flight(player);
      return false;
   }

   @Override
   public int onEntityLivingHurt(EntityLiving entity, DamageSource source, int damage) {return damage;}

   @Override
   public void onEntityLivingDrops(EntityLiving entity, DamageSource source, ArrayList<EntityItem> drops, int lootingLevel, boolean recentlyHit, int specialDropValue) {}

        public void Flight(EntityPlayer player)
{
//ここに処理を書く
}
}

以降Flightメソッド内は全バージョン共通なので,先にForgeへのクラスの登録方法を書きます.
(1.3.2以降)
@Modクラスの@Initメソッド内で
コード: 全て選択
      MinecraftForge.EVENT_BUS.register(new LivingEventHooks());

(1.2.5)
BaseMod継承クラスのload()メソッドで
コード: 全て選択
      MinecraftForge.registerEntityLivingHandler(new EntityLivingHandler());


ではFlightメソッドを解説します.簡単な部分はコメントで,説明の必要な箇所は後述します.
コード: 全て選択
         this.allowLevitatiton = !(player.capabilities.isCreativeMode || player.capabilities.allowFlying);
//クリエイティブ時と他MODでクリエイティブの飛行モードが許可されている場合は実行しないように
         if(!this.allowLevitatiton)//飛行が許可されていない状況では落ちるように
         {
            this.isLevitation = false;
            return false;
         }
                        //後述するLivingFallEventが未実装のバージョンの場合。
         //player.fallDistance = 0.0f;//これを挟まないと飛びつつ足首を挫ける.
         boolean jump = ((EntityPlayerSP)player).movementInput.jump;//ジャンプ入力があるか
            float var2 = 0.8F;//ダッシュのための変数
            boolean var3 = ((EntityPlayerSP)player).movementInput.moveForward >= var2;//一定距離以上前に進んだか.ダッシュ用
         ((EntityPlayerSP)player).movementInput.updatePlayerMoveState();//入力ステートを更新
         if (this.allowLevitatiton && !jump && ((EntityPlayerSP)player).movementInput.jump)//Jumpキーを押して離したかどうか
         {
            if (this.flyToggleTimer == 0)//Jumpキー入力1回目
            {
               this.flyToggleTimer = 7;//入力間隔のタイマー:7tick
            }
            else//2回目
            {
               this.isLevitation = !this.isLevitation;//飛行モードのトグル
               this.flyToggleTimer = 0;
            }
         }
//ここからダッシュ関係
         boolean var4 = (float)((EntityPlayerSP)player).getFoodStats().getFoodLevel() > 6.0F;
         if (((EntityPlayerSP)player).onGround && !var3 && ((EntityPlayerSP)player).movementInput.moveForward >= var2 && !((EntityPlayerSP)player).isSprinting() && var4 && !((EntityPlayerSP)player).isUsingItem() && !((EntityPlayerSP)player).isPotionActive(Potion.blindness))
         {
            if (this.sprintToggleTimer == 0)
            {
               this.sprintToggleTimer = 7;
            }
            else
            {
               ((EntityPlayerSP)player).setSprinting(true);
               this.sprintToggleTimer = 0;
            }
         }
         if (this.sprintToggleTimer > 0)
         {
            --this.sprintToggleTimer;
         }
//ここまでダッシュ関係
         if (this.flyToggleTimer > 0)
         {
            --this.flyToggleTimer;//入力間隔の判定タイマーのカウントを減らす.
         }
         if (player.onGround && this.isLevitation)
         {
            this.isLevitation = false;//地面についたら飛行から歩行へ
         }
         if (this.isLevitation)//飛行中の処理
         {
            player.motionY = 0D;//Y軸方向への移動量は入力なしでは滞空
            player.jumpMovementFactor = 0.1f;//滞空時の滞空移動速度.クリエイティブより少し早い
            if (((EntityPlayerSP)player).movementInput.sneak)
            {
               player.motionY -= 0.4D;//スニークで下降.クリエイティブより少し早い
            }

            if (((EntityPlayerSP)player).movementInput.jump)
            {
               player.motionY += 0.4D;//Jumpキーで上昇.クリエ〈略〉
            }

         }
         else
            player.jumpMovementFactor = 0.02f;//歩行時の滞空移動速度は通常と同じに
}

ダッシュ関係の処理の抜き出し
コード: 全て選択
            float var2 = 0.8F;//ダッシュのための変数
            boolean var3 = ((EntityPlayerSP)player).movementInput.moveForward >= var2;//一定距離以上前に進んだか.ダッシュ用
~~
boolean var4 = (float)((EntityPlayerSP)player).getFoodStats().getFoodLevel() > 6.0F;
         if (((EntityPlayerSP)player).onGround && !var3 && ((EntityPlayerSP)player).movementInput.moveForward >= var2 && !((EntityPlayerSP)player).isSprinting() && var4 && !((EntityPlayerSP)player).isUsingItem() && !((EntityPlayerSP)player).isPotionActive(Potion.blindness))
         {
            if (this.sprintToggleTimer == 0)
            {
               this.sprintToggleTimer = 7;
            }
            else
            {
               ((EntityPlayerSP)player).setSprinting(true);
               this.sprintToggleTimer = 0;
            }
         }
         if (this.sprintToggleTimer > 0)
         {
            --this.sprintToggleTimer;
         }

何故ダッシュの処理を入れているのかというと,Flightメソッド内で
コード: 全て選択
         ((EntityPlayerSP)player).movementInput.updatePlayerMoveState();//入力ステートを更新

を実行し,入力ステートを更新しているためです.
このイベントのあとにEntityPlayerのLivingUpdateメソッドが呼び出されて,ダッシュの処理をするのですが,こちらで入力ステートを更新したためにその後のダッシュ処理が正常に行われません.

取り敢えずソース内のコメントでだいたいわかると思いますが,質問があれば詳細を解説します.
ダッシュもFlightメソッドもEntityPlayerSPのLivingUpdateメソッド内のクリエイティブの飛行モード判定をそっくりそのまま持ってきているので,そちらも参照してみると良いと思います.

(追記)1.6.2でも使えることを確認。
(追記)1.7.2でも使えることを確認。コメント内誤字訂正(メソッド名、変数名の違いはあるかもしれません。)
(追記)1.7.10でも使えることを確認。落下ダメージ無効化処理を追加。
最後に編集したユーザー A.K. [ 2014年8月10日(日) 22:05 ], 累計 1 回
もじんぐしたい。。。。
アバター
A.K.
ID:1e285010
ラピスラズリ収集家
 
記事: 1375
登録日時: 2012年9月03日(月) 19:34

  • (PostNo.185850)

Re: [1.2.5 - 1.7.2]クリエイティブのような飛行モードの実装の仕方[Forge]

投稿記事by ryokusitai » 2014年8月03日(日) 18:03

minecraft 1.6.2
forge 9.10.1.871
の開発環境にて実行致しました。

質問なのですが
Flightメソッド内の
player.fallDistance = 0.0f;
というのは、飛行中に上昇下降を繰り返すことで落下ダメージが増えていくことを防ぐものであり、
着地時のダメージを無効化するものではないということなのでしょうか?
それともこちらに何か問題があり、着地時のダメージを無効化出来ていないのでしょうか?

まだこちらのトピックを見ていらっしゃるか分からないのですが、どうぞよろしくお願い致します。

同日 22:43 追記
当方の環境での原因が分かりましたので追記いたします。どうやら
net.minecraft.client.entity.EntityPlayerSP
のインスタンスのfallDistanceを0にしても判定に使用されず、
net.minecraft.entity.player.EntityPlayer
のほうで生成したインスタンスのfallDistanceを0にしなくてはならないようです。
Minecraft1.7.10のModderでした9割死亡。公開場所
A.K.さんが製作している1.2.5のEE2アドオンであるEEAAを1.7.10のProjectE環境に対応させたPEAAを公開しています。
その他「OldMassFabricator」、「MoonLightDetector(月光センサーMod)」など。
アバター
ryokusitai
ID:29f64502
石炭掘り
 
記事: 243
登録日時: 2014年5月24日(土) 11:37

  • (PostNo.185932)

Re: [1.2.5 - 1.7.2]クリエイティブのような飛行モードの実装の仕方[Forge]

投稿記事by A.K. » 2014年8月04日(月) 00:17

chocolen4 さんが書きました:

しばらく、見直してなかったので、色々処理に無駄があります。
今なら、falleventあたりで、無効化できるので、fallDistanceをいじる必要はないです。
1.6.2にfalleventがあったかどうかは忘れましたが。
もじんぐしたい。。。。
アバター
A.K.
ID:559af9e1
ラピスラズリ収集家
 
記事: 1375
登録日時: 2012年9月03日(月) 19:34

  • (PostNo.185936)

Re: [1.2.5 - 1.7.2]クリエイティブのような飛行モードの実装の仕方[Forge]

投稿記事by ryokusitai » 2014年8月04日(月) 01:06

A.K. さんが書きました:
chocolen4 さんが書きました:

しばらく、見直してなかったので、色々処理に無駄があります。
今なら、falleventあたりで、無効化できるので、fallDistanceをいじる必要はないです。
1.6.2にfalleventがあったかどうかは忘れましたが。


教えていただいた通り、falleventを使用してみたところ、
無事に落下ダメージをキャンセルすることが出来ました。
どうも有り難う御座いました。
Minecraft1.7.10のModderでした9割死亡。公開場所
A.K.さんが製作している1.2.5のEE2アドオンであるEEAAを1.7.10のProjectE環境に対応させたPEAAを公開しています。
その他「OldMassFabricator」、「MoonLightDetector(月光センサーMod)」など。
アバター
ryokusitai
ID:29f64502
石炭掘り
 
記事: 243
登録日時: 2014年5月24日(土) 11:37


Return to 開発関連

x