提供: Minecraft Modding Wiki
2014年6月20日 (金) 00:41時点におけるDefeatedcrow (トーク | 投稿記録)による版 (不要なメソッドを削除しました)
移動先: 案内検索

この記事は"Minecraft Forge Universal 9.10.0.871~"を前提MODとしています。

Stone pickaxe.png
中級者向けのチュートリアルです。

1.6の発射体Entityの追加

IProjectileインターフェースを実装した発射体Entityの作成のチュートリアルです。
テストプレイ用に、実際に右クリック使用で発射体Entityを発射することの出来るアイテムを併せて追加します。
発射体Entity(Projectile)とは、バニラの矢のようにワールド内に飛ばすためのエンティティで、モブによる間接攻撃などに用いられます。

このようなEntityを作成する場合にIProjectileの実装は必ずしも必要ではありませんが、実装することにより、主に初速度の設定部分や、ディスペンサーを使った発射処理などにこのインターフェースが利用されます。

ソースコード

ProjectileTutorialCore.java

このModのコアクラス

package mods.projectiletutorial.common;

import java.util.logging.Level;

import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.EnumToolMaterial;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.Configuration;
import net.minecraftforge.common.Property;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkMod;
import cpw.mods.fml.common.registry.EntityRegistry;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.LanguageRegistry;

/*
 * Modの基本部分の登録。
 * ここでは説明を割愛する。*/
@Mod(
		modid = "ProjectileTutorial",
		name = "VillagerCannonMod",
		version = "1.6.2_1.0"
		)
@NetworkMod(
		clientSideRequired = true,
		serverSideRequired = false
		)

public class ProjectileTutorialCore {
	
	@SidedProxy(clientSide = "mods.projectiletutorial.client.ClientSideProxy", 
                    serverSide = "mods.projectiletutorial.common.CommonSideProxy")
	public static CommonSideProxy proxy;
	
	@Instance("ProjectileTutorial")
        public static ProjectileTutorialCore instance;
	
	public static Item bulletSource;
	public int newItemID = 7070;
	public static int entityIdHead = 170;
	public static int entityIdNose = 171;
	
	@EventHandler
	public void preInit(FMLPreInitializationEvent event)
	{
		//コンフィグファイルの作成。アイテム、エンティティのIDを変更可能にする。
		Configuration cfg = new Configuration(event.getSuggestedConfigurationFile());
		try
		{
			cfg.load();
			Property itemID = cfg.getItem("BulletSourceItem", newItemID);
			Property entityHead = cfg.get("entity", "VillagerHead", entityIdHead);
			Property entityNose = cfg.get("entity", "VillagerNose", entityIdNose);
			
			newItemID = itemID.getInt();
			entityIdHead = entityHead.getInt();
			entityIdNose = entityNose.getInt();
		}
		catch (Exception e)
		{
			FMLLog.log(Level.SEVERE, e, "Error Message");

		}
		finally
		{
			cfg.save();
		}
		
		//アイテムの登録。
		bulletSource = new ItemBulletSource(newItemID, EnumToolMaterial.EMERALD)
		.setUnlocalizedName("ProjectileTutorial.bulletSource")
		.setCreativeTab(CreativeTabs.tabCombat);
		
		GameRegistry.registerItem(bulletSource, "ProjectileTutorial.bulletSource");
		
	}
	
	@EventHandler
	public void init(FMLInitializationEvent event)
	{
		//エンティティの登録。
		EntityRegistry.registerModEntity(EntityVillagerHead.class, "villagerHead", entityIdHead, this, 128, 5, true);
		
		//プロキシを通して、クライアントサイドのみでエンティティのモデル・レンダーの登録を行う。
		proxy.registerRenderers();
		
		//アイテムとエンティティの名前の登録。
		LanguageRegistry.addName(new ItemStack(this.bulletSource, 1, 0), "Villager Core");
		LanguageRegistry.instance().addNameForObject(new ItemStack(this.bulletSource, 1, 0), "ja_JP", "村人コア");
		
		LanguageRegistry.instance().addStringLocalization("entity.villagerHead.name", "en_US", "FlingVillagerHead");
	        LanguageRegistry.instance().addStringLocalization("entity.villagerHead.name", "ja_JP", "村人波動砲");
		
	}

}

CommonSideProxy.java

package mods.projectiletutorial.common;

import net.minecraft.world.World;

public class CommonSideProxy {
	
	public void registerRenderers()
	{
		
	}

	public World getClientWorld() {
		
		return null;
	}

}

ClientSideProxy.java

クライアントサイドのプロキシクラス。このModでは、Entityのモデルとレンダーの登録のために用いている。

package mods.projectiletutorial.client;

import net.minecraft.world.World;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.client.registry.RenderingRegistry;
import mods.projectiletutorial.common.CommonSideProxy;
import mods.projectiletutorial.common.EntityVillagerHead;

public class ClientSideProxy extends CommonSideProxy{
	
	@Override
	public World getClientWorld()
	{
		return FMLClientHandler.instance().getClient().theWorld;
	}
	
	@Override
	public void registerRenderers()
	{
		RenderingRegistry.registerEntityRenderingHandler(EntityVillagerHead.class, new EntityRenderer(new VillagerHeadModel()));
	}

}

ItemBulletSource.java

発射体を撃つことが出来るサンプルアイテム。以下の機能を持つ。
・右クリックで弓のようにタメ動作を行い、離した瞬間に追加Entityを1つ発射する。
 発射するエンティティの速度や効果音の高さはタメ時間によって変化する。
・発射する際に、アイテムがダメージを受ける。

package mods.projectiletutorial.common;

import com.google.common.collect.Multimap;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.renderer.texture.IconRegister;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.item.EnumAction;
import net.minecraft.item.EnumToolMaterial;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.item.ItemTool;
import net.minecraft.util.Icon;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.ArrowLooseEvent;
import net.minecraftforge.event.entity.player.ArrowNockEvent;

/*
 * ItemSwordを継承しているため、見た目はエメラルドだが攻撃力を持つ。
 * ただし右クリック動作はItemBowから持ってきており、引き絞る動作、タメ時間による発射エンティティの変化がある。
 * タメ時間によるエンティティの撃ち分けができれば何でも良かったので、剣のような仕様にした意味はあまりない。*/
public class ItemBulletSource extends ItemSword{
	
	/*剣としてのダメージ。*/
	private float damage;
	
	public ItemBulletSource (int par1, EnumToolMaterial par2){
		super (par1, par2);
		this.setMaxStackSize(1);
		this.setMaxDamage(64);
		this.damage = 5;
	}
	
	/*剣としてのダメージを設定するメソッド。今回はdamageに代入した値(ハート2.5個分)を使用。*/
	@Override
	public Multimap getItemAttributeModifiers()
    {
        Multimap multimap = super.getItemAttributeModifiers();
        multimap.put(SharedMonsterAttributes.attackDamage.getAttributeUnlocalizedName(), 
                        new AttributeModifier(field_111210_e, "Weapon modifier", (double)this.damage, 0));
        return multimap;
    }
	
	/*アイコンはバニラのエメラルドを使用。*/
	@Override
	@SideOnly(Side.CLIENT)
	public void registerIcons(IconRegister par1IconRegister)
	{
		this.itemIcon = par1IconRegister.registerIcon("emerald");
	}
	
	/*
         * 右クリック使用をやめた時に呼ばれるメソッド。右クリックを継続して押していた時間をもとに、エンティティを発射する処理を行う。
         */
        public void onPlayerStoppedUsing(ItemStack par1ItemStack, World par2World, EntityPlayer par3EntityPlayer, int par4)
        {
    	//par4は右クリックの押下時間。
        int j = this.getMaxItemUseDuration(par1ItemStack) - par4;
        
        //まず、クリエイティブであるか&このアイテムにInfinityエンチャントが付いているかを確かめる。いずれかを満たすとtrue。
        boolean flag = par3EntityPlayer.capabilities.isCreativeMode || 
             EnchantmentHelper.getEnchantmentLevel(Enchantment.infinity.effectId, par1ItemStack) > 0;

        if (!flag)//アイテムにダメージを与える。
        {
        	par1ItemStack.damageItem(1, par3EntityPlayer);
        }
        
        //右クリック押下時間をもとに計算。20で割り(単位を秒に変換)、なにやら二次関数的な計算式に入れている。
        //ここではバニラ弓のまま使っているが、独自の計算式でも良いと思います。
        float f = (float)j / 20.0F;
        f = (f * f + f * 2.0F) / 3.0F;

        //タメ時間が一定以下の場合、何も起こさず処理から抜ける。
        if ((double)f < 0.1D)
        {
            return;
        }

        //fの上限値。
        if (f > 1.0F)
        {
            f = 1.0F;
        }

        EntityVillagerHead bullet = new EntityVillagerHead(par2World, par3EntityPlayer, f * 2.5F, 1.0F, 0.0F, 0.0F, 0.0F);
        
        par2World.playSoundAtEntity(par3EntityPlayer, "mob.villager.idle", 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + f * 0.5F);

        if (!par2World.isRemote)
        {
            par2World.spawnEntityInWorld(bullet);
        }
    }
	
	public ItemStack onEaten(ItemStack par1ItemStack, World par2World, EntityPlayer par3EntityPlayer)
    {
    	return par1ItemStack;
    }

    /*
     * 右クリックでの使用(タメ)時間の上限。
     */
    public int getMaxItemUseDuration(ItemStack par1ItemStack)
    {
        return 72000;
    }

    /*
     * 右クリック時の動作のタイプ。
     * ここではbow(引き絞るタメ動作)にしているが、ガードや飲食などに変えることも出来、呼ばれるメソッドが異なる。
     */
    public EnumAction getItemUseAction(ItemStack par1ItemStack)
    {
        return EnumAction.bow;
    }

    /*
     * 右クリックでの使用時に呼ばれるメソッド。
     */
    public ItemStack onItemRightClick(ItemStack par1ItemStack, World par2World, EntityPlayer par3EntityPlayer)
    {
    	par3EntityPlayer.setItemInUse(par1ItemStack, this.getMaxItemUseDuration(par1ItemStack));

        return par1ItemStack;
    }

    @SideOnly(Side.CLIENT)
    /*
     * 右クリックでタメている時のアイコンを変えられる。今回は特に変えていない。
     */
    public Icon getItemIconForUseDuration(int par1)
    {
        return this.itemIcon;
    }
    
    @SideOnly(Side.CLIENT)
	//アイテムアイコンのエフェクト
    public boolean hasEffect(ItemStack par1ItemStack)
    {
        return par1ItemStack.getItemDamage() > 0;
    }

}

EntityVillagerHead.java

発射体Entityのクラス。以下の内容を含む。
・Entity生成時の初速度・初期方向の算出
・飛行中の速度計算
・Entityやブロックとの衝突判定、及び当たった時の効果

package mods.projectiletutorial.common;

import java.util.List;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.enchantment.EnchantmentThorns;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IProjectile;
import net.minecraft.entity.monster.EntityEnderman;
import net.minecraft.entity.passive.EntityHorse;
import net.minecraft.entity.passive.EntityTameable;
import net.minecraft.entity.passive.EntityVillager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.packet.Packet70GameEvent;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;

/*
 * 発射されるエンティティのクラス。
 * */
public class EntityVillagerHead extends Entity implements IProjectile{
	
    /* 地中判定に使うもの */
    protected int xTile = -1;
    protected int yTile = -1;
    protected int zTile = -1;
    protected int inTile;
    protected int inData;
    protected boolean inGround;

    /* この弾を撃ったエンティティ */
    public Entity shootingEntity;
    
    /* 地中・空中にいる時間 */
    protected int ticksInGround;
    protected int ticksInAir;
    
    /* ダメージの大きさ */
    protected double damage = 15.0D;

    /* ノックバックの大きさ */
    protected int knockbackStrength = 1;

    public EntityVillagerHead(World par1World)
    {
        super(par1World);
        this.renderDistanceWeight = 10.0D;
        this.setSize(0.5F, 0.5F);
        this.damage = 2.5D;
    }
    
    /** 発射する弾を生成・初期パラメータの定義をする。
     * @param par1World :このワールド
     * @param par2EntityLivingBase :弾源となるエンティティ。このModの場合、弾を撃ったプレイヤーがここに入る
     * @param speed :弾の速度計算に使われる値
     * @param speed2 :弾の速度計算に使われる値2
     * @param adjustX :プレイヤーから見て水平方向に、発射する弾をずらす(複数発射するときなどに使用する)
     * @param adjustZ :プレイヤーから見て前後方向に弾をずらす
     * @param adjustY :プレイヤーから見て上下方向に弾をずらす*/
    public EntityVillagerHead(World par1World, EntityLivingBase par2EntityLivingBase, float speed, float speed2,
         float adjustX, float adjustZ, float adjustY)
    {
    	super(par1World);
        this.renderDistanceWeight = 10.0D;
        this.shootingEntity = par2EntityLivingBase;
        this.yOffset = 0.0F;
        this.setSize(0.5F, 0.5F);
        
        //初期状態での向きの決定
        this.setLocationAndAngles(par2EntityLivingBase.posX, par2EntityLivingBase.posY + 
            (double)par2EntityLivingBase.getEyeHeight(), par2EntityLivingBase.posZ,
            par2EntityLivingBase.rotationYaw, par2EntityLivingBase.rotationPitch);
        
        //位置の調整
        this.posX += -(double)(MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * (1.0F + adjustZ))
        		- (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * adjustX);
        this.posY += 0.05000000149011612D + adjustY;
        this.posZ += (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * (1.0F + adjustZ))
        		- (double)(MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * adjustX);
        this.setPosition(this.posX, this.posY, this.posZ);
        
        //初速度
        this.motionX = (double)(-MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * 
                           MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI));
        this.motionZ = (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * 
                           MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI));
        this.motionY = (double)(-MathHelper.sin(this.rotationPitch / 180.0F * (float)Math.PI));
        this.setThrowableHeading(this.motionX, this.motionY, this.motionZ, speed * 1.5F, speed2);
    }

    /*dataWatcherを利用したサーバ・クライアント間の同期処理だと思う*/
    protected void entityInit()
    {
        
    }

    /*
     * IProjectileで実装が必要なメソッド。
     * ディスペンサーによる発射メソッドなどで使用されている。
     */
    public void setThrowableHeading(double par1, double par3, double par5, float par7, float par8)
    {
        float f2 = MathHelper.sqrt_double(par1 * par1 + par3 * par3 + par5 * par5);
        par1 /= (double)f2;
        par3 /= (double)f2;
        par5 /= (double)f2;
        par1 += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)par8;
        par3 += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)par8;
        par5 += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)par8;
        par1 *= (double)par7;
        par3 *= (double)par7;
        par5 *= (double)par7;
        this.motionX = par1;
        this.motionY = par3;
        this.motionZ = par5;
        float f3 = MathHelper.sqrt_double(par1 * par1 + par5 * par5);
        this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(par1, par5) * 180.0D / Math.PI);
        this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(par3, (double)f3) * 180.0D / Math.PI);
        this.ticksInGround = 0;
    }

    @SideOnly(Side.CLIENT)
    /*
     * Sets the position and rotation. Only difference from the other one is no bounding on the rotation. Args: posX,
     * posY, posZ, yaw, pitch
     */
    public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9)
    {
        this.setPosition(par1, par3, par5);
        this.setRotation(par7, par8);
    }

    @SideOnly(Side.CLIENT)
    /*
     * Sets the velocity to the args. Args: x, y, z
     * 速度の処理。クライアント・サーバ間での同期処理にて利用されている。
     */
    public void setVelocity(double par1, double par3, double par5)
    {
        this.motionX = par1;
        this.motionY = par3;
        this.motionZ = par5;

        if (this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F)
        {
            float f = MathHelper.sqrt_double(par1 * par1 + par5 * par5);
            this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(par1, par5) * 180.0D / Math.PI);
            this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(par3, (double)f) * 180.0D / Math.PI);
            this.prevRotationPitch = this.rotationPitch;
            this.prevRotationYaw = this.rotationYaw;
            this.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch);
            this.ticksInGround = 0;
        }
    }

    /*
     * Tick毎に呼ばれる更新処理。
     * 速度の更新、衝突判定などをここで行う。
     */
    public void onUpdate()
    {
        super.onUpdate();

        //直前のパラメータと新パラメータを一致させているところ。
        //また、速度に応じてエンティティの向きを調整し、常に進行方向に前面が向くようにしている。
        if (this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F)
        {
            float f = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
            this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);
            this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(this.motionY, (double)f) * 180.0D / Math.PI);
        }

        //激突したブロックを確認している
        int i = this.worldObj.getBlockId(this.xTile, this.yTile, this.zTile);

        //空気じゃないブロックに当たった&ブロック貫通エンティティでない時
        if (i > 0 && !this.isPenetrateBlock())
        {
            Block.blocksList[i].setBlockBoundsBasedOnState(this.worldObj, this.xTile, this.yTile, this.zTile);
            AxisAlignedBB axisalignedbb = Block.blocksList[i].getCollisionBoundingBoxFromPool(this.worldObj, this.xTile, this.yTile, this.zTile);

            //当たり判定に接触しているかどうか
            if (axisalignedbb != null && axisalignedbb.isVecInside(this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX, this.posY, this.posZ)))
            {
                this.inGround = true;
            }
        }

        //空気じゃないブロックに当たった
        if (this.inGround)
        {
            int j = this.worldObj.getBlockId(this.xTile, this.yTile, this.zTile);
            int k = this.worldObj.getBlockMetadata(this.xTile, this.yTile, this.zTile);

            /* 前のTickに確認した埋まりブロックのIDとメタを照合している。違ったら埋まり状態を解除、一致したら埋まり状態を継続。
            /* 埋まり状態2tick継続でこのエンティティを消す
             */
            if (j == this.inTile && k == this.inData)
            {
            	++this.ticksInGround;
            	//ブロック貫通の場合、20tick(1秒間)はブロック中にあっても消えないようになる。
            	int limit = this.isPenetrateBlock() ? 20 : 2;

                if (this.ticksInGround > limit)
                {
                    this.setDead();
                }
            }
            else//埋まり状態の解除処理
            {
                this.inGround = false;
                this.motionX *= (double)(this.rand.nextFloat() * 0.1F);
                this.motionY *= (double)(this.rand.nextFloat() * 0.1F);
                this.motionZ *= (double)(this.rand.nextFloat() * 0.1F);
                this.ticksInGround = 0;
                this.ticksInAir = 0;
            }
        }
        else//埋まってない時。速度の更新。
        {
            ++this.ticksInAir;
            //ブロックとの衝突判定
            Vec3 vec3 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX, this.posY, this.posZ);
            Vec3 vec31 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX + this.motionX, this.posY + 
                             this.motionY, this.posZ + this.motionZ);
            MovingObjectPosition movingobjectposition = this.worldObj.rayTraceBlocks_do_do(vec3, vec31, false, true);
            vec3 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX, this.posY, this.posZ);
            vec31 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX + this.motionX, this.posY + 
                        this.motionY, this.posZ + this.motionZ);
            
            //ブロック貫通がONの場合、ブロック衝突判定をスキップ
            if (this.isPenetrateBlock())
            {
            	movingobjectposition = null;
            }
            
            //ブロックに当たった
            if (movingobjectposition != null)
            {
                vec31 = this.worldObj.getWorldVec3Pool().getVecFromPool(movingobjectposition.hitVec.xCoord, 
                            movingobjectposition.hitVec.yCoord, movingobjectposition.hitVec.zCoord);
            }

            //Entityとの衝突判定。
            Entity entity = null;
            List list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, 
                            this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ).expand(1.0D, 1.0D, 1.0D));
            double d0 = 0.0D;
            int l;
            float f1;
            boolean isVillager = false;

            //1ブロック分の範囲内にいるエンティティ全てに対して繰り返す
            for (l = 0; l < list.size(); ++l)
            {
                Entity entity1 = (Entity)list.get(l);

                //発射物自身or発射後5tick以外だとすりぬける
                if (entity1.canBeCollidedWith() && (entity1 != this.shootingEntity || this.ticksInAir >= 5))
                {
                    f1 = 0.3F;
                    AxisAlignedBB axisalignedbb1 = entity1.boundingBox.expand((double)f1, (double)f1, (double)f1);
                    MovingObjectPosition movingobjectposition1 = axisalignedbb1.calculateIntercept(vec3, vec31);

                    if (movingobjectposition1 != null)
                    {
                        double d1 = vec3.distanceTo(movingobjectposition1.hitVec);

                        if (d1 < d0 || d0 == 0.0D)
                        {
                            entity = entity1;
                            d0 = d1;
                        }
                    }
                }
            }

            //エンティティに当たった
            if (entity != null)
            {
                movingobjectposition = new MovingObjectPosition(entity);
            }

            /* 当たったエンティティそれそれについての判定部分。
             * ここでmovingobjectposition = nullにすることで特定の種類のエンティティに当たらないようにできる。*/
            if (movingobjectposition != null && movingobjectposition.entityHit != null)
            {
                if (movingobjectposition.entityHit instanceof EntityPlayer) 
                {
                	//プレイヤーに当たった時
                	EntityPlayer entityplayer = (EntityPlayer)movingobjectposition.entityHit;

                    if (entityplayer.capabilities.disableDamage || this.shootingEntity instanceof EntityPlayer && 
                           !((EntityPlayer)this.shootingEntity).canAttackPlayer(entityplayer))
                    {
                    	//PvPが許可されていないと当たらない
                        movingobjectposition = null;
                    }
                    else if (entityplayer == this.shootingEntity)
                    {
                    	//対象が撃った本人の場合も当たらない
                    	movingobjectposition = null;
                    }
                }
                else if (movingobjectposition.entityHit instanceof EntityTameable || 
                             movingobjectposition.entityHit instanceof EntityHorse)
                {
                	//事故防止の為、EntityTameable(犬や猫などのペット)、馬にも当たらないようにする
                	movingobjectposition = null;
                }
                else if (movingobjectposition.entityHit instanceof EntityVillager)
                {
                	//村人に当たった場合にフラグがtrueになる
                	isVillager = true;
                }
            }

            float f2;
            float f3;

            //当たったあとの処理
            if (movingobjectposition != null)
            {
            	//エンティティに当たった
                if (movingobjectposition.entityHit != null)
                {
                	//衝突時の弾の速度を計算
                    f2 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionY * this.motionY + 
                            this.motionZ * this.motionZ);
                    //速度が大きいほど、ダメージも大きくなる
                    int i1 = MathHelper.ceiling_double_int((double)f2 * this.damage);
                    //0~2程度の乱数値を上乗せ
                    i1 += this.rand.nextInt(3);

                    DamageSource damagesource = null;

                    //別メソッドでダメージソースを確認
                    damagesource = this.thisDamageSource(this.shootingEntity);

                    //バニラ矢と同様、このエンティティが燃えているなら対象に着火することも出来る
                    if (this.isBurning() && !(movingobjectposition.entityHit instanceof EntityEnderman))
                    {
                        movingobjectposition.entityHit.setFire(5);
                    }
                    
                    if (isVillager)
                    {
                    	//対象が村人だった場合の処理
                    	EntityVillager villager = (EntityVillager) movingobjectposition.entityHit;
                    	//ダメージに相当する量の回復効果をもたらす
                    	villager.heal((float)i1);
                    	//ただしノックバックは有る
                    	if (this.knockbackStrength > 0)
                        {
                            f3 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);

                            if (f3 > 0.0F)
                            {
                                movingobjectposition.entityHit.addVelocity(this.motionX * 
                                    (double)this.knockbackStrength * 0.6000000238418579D / (double)f3, 0.1D, this.motionZ * 
                                    (double)this.knockbackStrength * 0.6000000238418579D / (double)f3);
                            }
                        }
                        else
                        {
                        	movingobjectposition.entityHit.hurtResistantTime = 0;
                        }
                    }
                    else
                    {
                    	//村人以外なら、ダメージを与える処理を呼ぶ
                    	if (movingobjectposition.entityHit.attackEntityFrom(damagesource, (float)i1))
                        {
                    		//ダメージを与えることに成功したら以下の処理を行う
                            if (movingobjectposition.entityHit instanceof EntityLivingBase)
                            {
                                EntityLivingBase entitylivingbase = (EntityLivingBase)movingobjectposition.entityHit;

                                //ノックバック
                                if (this.knockbackStrength > 0)
                                {
                                    f3 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);

                                    if (f3 > 0.0F)
                                    {
                                        movingobjectposition.entityHit.addVelocity(this.motionX * 
                                            (double)this.knockbackStrength * 0.6000000238418579D / (double)f3, 0.1D, this.motionZ * 
                                            (double)this.knockbackStrength * 0.6000000238418579D / (double)f3);
                                    }
                                }
                                else
                                {
                                	movingobjectposition.entityHit.hurtResistantTime = 0;
                                }

                                //Thornのエンチャント効果で反撃を受ける
                                if (this.shootingEntity != null)
                                {
                                    EnchantmentThorns.func_92096_a(this.shootingEntity, entitylivingbase, this.rand);
                                }

                                //マルチプレイ時に、両者がプレイヤーだった時のパケット送信処理
                                if (this.shootingEntity != null && movingobjectposition.entityHit != this.shootingEntity && 
                                        movingobjectposition.entityHit instanceof EntityPlayer && this.shootingEntity instanceof EntityPlayerMP)
                                {
                                    ((EntityPlayerMP)this.shootingEntity).playerNetServerHandler.sendPacketToPlayer(new Packet70GameEvent(6, 0));
                                }
                            }

                            //ここでヒット時の効果音がなる
                            this.playSound("random.bowhit", 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));

                            //当たったあと、弾を消去する。エンティティ貫通がONの弾種はそのまま残す。
                            if (!(movingobjectposition.entityHit instanceof EntityEnderman) && !this.isPenetrateEntity())
                            {
                                this.setDead();
                            }
                        }
                    }
                    
                }
                else if (!this.isPenetrateBlock())  //エンティティには当たってない。ブロックに当たった。
                {
                    this.xTile = movingobjectposition.blockX;
                    this.yTile = movingobjectposition.blockY;
                    this.zTile = movingobjectposition.blockZ;
                    this.inTile = this.worldObj.getBlockId(this.xTile, this.yTile, this.zTile);
                    this.inData = this.worldObj.getBlockMetadata(this.xTile, this.yTile, this.zTile);
                    this.motionX = (double)((float)(movingobjectposition.hitVec.xCoord - this.posX));
                    this.motionY = (double)((float)(movingobjectposition.hitVec.yCoord - this.posY));
                    this.motionZ = (double)((float)(movingobjectposition.hitVec.zCoord - this.posZ));
                    f2 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionY * this.motionY + 
                             this.motionZ * this.motionZ);
                    this.posX -= this.motionX / (double)f2 * 0.05000000074505806D;
                    this.posY -= this.motionY / (double)f2 * 0.05000000074505806D;
                    this.posZ -= this.motionZ / (double)f2 * 0.05000000074505806D;
                    this.playSound("random.bowhit", 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
                    this.inGround = true;
                    
                    if (this.inTile != 0)
                    {
                        Block.blocksList[this.inTile].onEntityCollidedWithBlock(this.worldObj, this.xTile, this.yTile, this.zTile, this);
                    }
                }
            }
            
            //改めてポジションに速度を加算。向きも更新。
            this.posX += this.motionX;
            this.posY += this.motionY;
            this.posZ += this.motionZ;
            f2 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
            this.rotationYaw = (float)(Math.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);
            this.rotationPitch = (float)(Math.atan2(this.motionY, (double)f2) * 180.0D / Math.PI);
            
            while ( this.rotationPitch - this.prevRotationPitch < -180.0F)
            {
            	this.prevRotationPitch -= 360.0F;
            }

            while (this.rotationPitch - this.prevRotationPitch >= 180.0F)
            {
                this.prevRotationPitch += 360.0F;
            }

            while (this.rotationYaw - this.prevRotationYaw < -180.0F)
            {
                this.prevRotationYaw -= 360.0F;
            }

            while (this.rotationYaw - this.prevRotationYaw >= 180.0F)
            {
                this.prevRotationYaw += 360.0F;
            }

            this.rotationPitch = this.prevRotationPitch + (this.rotationPitch - this.prevRotationPitch) * 0.2F;
            this.rotationYaw = this.prevRotationYaw + (this.rotationYaw - this.prevRotationYaw) * 0.2F;
            
            //徐々に減速する
            float f4 = 0.99F;
            
            //重力落下
            //落下速度は別メソッドで設定している。デフォルトでは0.0F。
            f1 = this.fallSpeed();

            //水中に有る
            if (this.isInWater())
            {
            	//泡パーティクルが出る
                for (int j1 = 0; j1 < 4; ++j1)
                {
                    f3 = 0.25F;
                    this.worldObj.spawnParticle("bubble", this.posX - this.motionX * (double)f3, this.posY - this.motionY * 
                         (double)f3, this.posZ - this.motionZ * (double)f3, this.motionX, this.motionY, this.motionZ);
                }

                //減速も大きくなる
                f4 = 0.8F;
            }

            this.motionX *= (double)f4;
            this.motionY *= (double)f4;
            this.motionZ *= (double)f4;
            this.motionY -= (double)f1;
            
            //一定以上遅くなったら消える
            if (this.worldObj.isRemote && this.motionX * this.motionX + this.motionZ * this.motionZ < 0.001D)
            {
            	this.setDead();
            }
            
            
            this.setPosition(this.posX, this.posY, this.posZ);
            this.doBlockCollisions();
        }
    }

    /*
     * (abstract) Protected helper method to write subclass entity data to NBT.
     */
    public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
    {
        par1NBTTagCompound.setShort("xTile", (short)this.xTile);
        par1NBTTagCompound.setShort("yTile", (short)this.yTile);
        par1NBTTagCompound.setShort("zTile", (short)this.zTile);
        par1NBTTagCompound.setByte("inTile", (byte)this.inTile);
        par1NBTTagCompound.setByte("inData", (byte)this.inData);
        par1NBTTagCompound.setByte("inGround", (byte)(this.inGround ? 1 : 0));
        par1NBTTagCompound.setDouble("damage", this.damage);
    }

    /*
     * (abstract) Protected helper method to read subclass entity data from NBT.
     */
    public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
    {
        this.xTile = par1NBTTagCompound.getShort("xTile");
        this.yTile = par1NBTTagCompound.getShort("yTile");
        this.zTile = par1NBTTagCompound.getShort("zTile");
        this.inTile = par1NBTTagCompound.getByte("inTile") & 255;
        this.inData = par1NBTTagCompound.getByte("inData") & 255;
        this.inGround = par1NBTTagCompound.getByte("inGround") == 1;

        if (par1NBTTagCompound.hasKey("damage"))
        {
            this.damage = par1NBTTagCompound.getDouble("damage");
        }
    }

    /*
     * プレイヤーと衝突した時のメソッド。今回は何もしない
     */
    public void onCollideWithPlayer(EntityPlayer par1EntityPlayer)
    {
    	
    }

    /*
     * ブロックに対し、上を歩いたかという判定の対象になるか、というEntityクラスのメソッド。
     * 耕地を荒らしたりするのに使う。
     */
    protected boolean canTriggerWalking()
    {
        return false;
    }

    @SideOnly(Side.CLIENT)
    public float getShadowSize()
    {
        return 0.0F;
    }

    public void setDamage(double par1)
    {
        this.damage = par1;
    }

    public double getDamage()
    {
        return this.damage;
    }

    public void setKnockbackStrength(int par1)
    {
        this.knockbackStrength = par1;
    }

    public boolean canAttackWithItem()
    {
        return false;
    } 
    
    /** 以下、当MOD用のパラメータ定義部分*/
    
    /* 落下速度 */
    public float fallSpeed()
    {
    	return 0.0F;
    }
    
    /* ダメージソースのタイプ */
    public DamageSource thisDamageSource(Entity entity)
    {
        //発射元のEntityがnullだった場合の対策を含む。
    	return entity != null ? EntityDamageSource.causeIndirectMagicDamage(entity, this) : DamageSource.magic;
    }
    
    /* ブロック貫通 */
    public boolean isPenetrateBlock()
    {
    	return false;
    }
    
    /* エンティティ貫通 */
    public boolean isPenetrateEntity()
    {
    	return true;
    }

}

VillagerHeadModel.java

発射体Entityのモデルクラス。
時間経過による向きの変化(回転しながら飛んで行く)もここで設定されている。

package mods.projectiletutorial.client;

import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity;

public class VillagerHeadModel extends ModelBase{
	
	ModelRenderer Head;
    ModelRenderer Nose;
  
  public VillagerHeadModel()
  {
    textureWidth = 64;
    textureHeight = 64;
    
      Head = new ModelRenderer(this, 0, 0);
      Head.addBox(-4F, -5F, -4F, 8, 10, 8);
      Head.setRotationPoint(0F, 16F, 0F);
      Head.setTextureSize(64, 64);
      Head.mirror = true;
      setRotation(Head, 0F, 0F, 0F);
      Nose = new ModelRenderer(this, 24, 0);
      Nose.addBox(-1F, 2F, -6F, 2, 4, 2);
      Nose.setRotationPoint(0F, 16F, 0F);
      Nose.setTextureSize(64, 64);
      Nose.mirror = true;
	  }
	  
	  public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5)
	  {
	    super.render(entity, f, f1, f2, f3, f4, f5);
	    setRotationAngles(f, f1, f2, f3, f4, f5, entity);
	    Head.render(f5);
	    Nose.render(f5);
	  }
	  
	  public void renderNose(Entity entity, float f, float f1, float f2, float f3, float f4, float f5)
	  {
	    super.render(entity, f, f1, f2, f3, f4, f5);
	    setRotationAngles(f, f1, f2, f3, f4, f5, entity);
	    Nose.render(f5);
	  }
	  
	  private void setRotation(ModelRenderer model, float x, float y, float z)
	  {
	    model.rotateAngleX = x;
	    model.rotateAngleY = y;
	    model.rotateAngleZ = z;
	  }
	  
	  public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity)
	  {
	    super.setRotationAngles(f, f1, f2, f3, f4, f5, entity);
            //飛ばしたエンティティが回転しながら飛んで行く。
	    this.Head.rotateAngleZ += 0.5F / (180F / (float)Math.PI);
	    this.Nose.rotateAngleZ += 0.5F / (180F / (float)Math.PI);
	  }

}

EntityRenderer.java

発射体Entityのレンダ―クラス。 Entityのテクスチャ、モデルのレンダー処理などが行われている。

package mods.projectiletutorial.client;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import mods.projectiletutorial.common.EntityVillagerHead;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;

@SideOnly(Side.CLIENT)
public class EntityRenderer extends Render
{
    private static final ResourceLocation bulletTextures = new ResourceLocation("textures/entity/villager/villager.png");

    protected ModelBase modelBullet;
    
    public EntityRenderer(ModelBase par1ModelBase) {
		super();
		this.modelBullet = par1ModelBase;
		this.shadowSize = 0.0F;
	}

	public void renderArrow(EntityVillagerHead par1Entity, double par2, double par4, double par6, float par8, float par9)
    {
        VillagerHeadModel model = (VillagerHeadModel) this.modelBullet;
		
		this.bindEntityTexture(par1Entity);
        GL11.glPushMatrix();
        GL11.glDisable(GL11.GL_LIGHTING);
        GL11.glEnable(GL12.GL_RESCALE_NORMAL);
        GL11.glColor4f(2.0F, 2.0F, 2.0F, 1.0F);
        GL11.glTranslatef((float)par2, (float)par4 + 1.0F, (float)par6);
        GL11.glRotatef(par1Entity.prevRotationYaw + (par1Entity.rotationYaw - par1Entity.prevRotationYaw) * par9, 0.0F, 1.0F, 0.0F);
        GL11.glRotatef(par1Entity.prevRotationPitch + (par1Entity.rotationPitch - par1Entity.prevRotationPitch) * par9, 0.0F, 0.0F, 1.0F);
        GL11.glScalef(1.0F, -1.0F, -1.0F);
        model.render((Entity)null, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0625F);
        GL11.glDisable(GL12.GL_RESCALE_NORMAL);
        GL11.glEnable(GL11.GL_LIGHTING);
        GL11.glPopMatrix();
    }

    protected ResourceLocation getArrowTextures(EntityVillagerHead par1EntityArrow)
    {
        return bulletTextures;
    }

    protected ResourceLocation getEntityTexture(Entity par1Entity)
    {
        return this.getArrowTextures((EntityVillagerHead)par1Entity);
    }

    public void doRender(Entity par1Entity, double par2, double par4, double par6, float par8, float par9)
    {
        this.renderArrow((EntityVillagerHead)par1Entity, par2, par4, par6, par8, par9);
    }
}