Defeatedcrow (トーク | 投稿記録) (→実際に実装されているクラス) |
Defeatedcrow (トーク | 投稿記録) |
||
(同じ利用者による、間の9版が非表示) | |||
1行目: | 1行目: | ||
+ | <[[IGNIS制作記録|目次に戻る]]> | ||
=弾Entityの作成= | =弾Entityの作成= | ||
IGNISから発射する弾をEntityとして作成します。<br /> | IGNISから発射する弾をEntityとして作成します。<br /> | ||
20行目: | 21行目: | ||
*[[EntityFlameClass]] | *[[EntityFlameClass]] | ||
+ | |||
+ | ==Entityクラスを作る== | ||
+ | |||
+ | ===Entityクラスを継承する=== | ||
+ | Entityを作るには、'''Entity.class'''を継承します。また、今回は弾を作るため、バニラの発射体用インターフェイスである'''IProjectile'''を実装します。<br /> | ||
+ | Entity.class、IProjectileインターフェイスそれぞれ、いくつかの"必ず作らなければならないメソッド"があります。以下の状態が、弾Entityの最低限の状態です。<br /> | ||
+ | |||
+ | 以下のように生成した場合、継承しているEntity.classに備わっているデフォルトの機能のみ持った状態になります。座標移動や当たり判定周りの機能は備わっていますが自力移動は出来ず、デフォルトの見た目(灰色の無地のボックス)の高さ1.8F、幅0.6F、当たり判定無しの四角い物体が、(おそらく)初期地点を選べない状態で生成されます。 | ||
+ | |||
+ | <source lang="java"> | ||
+ | package defeatedcrow.flamethrower.entity; | ||
+ | |||
+ | import net.minecraft.entity.Entity; | ||
+ | import net.minecraft.entity.IProjectile; | ||
+ | import net.minecraft.nbt.NBTTagCompound; | ||
+ | import net.minecraft.world.World; | ||
+ | |||
+ | public class EntityFlame extends Entity implements IProjectile { | ||
+ | |||
+ | // コンストラクタ | ||
+ | public EntityFlame(World world) { | ||
+ | super(world); | ||
+ | } | ||
+ | |||
+ | /* Entityから実装を求められるメソッド */ | ||
+ | |||
+ | @Override | ||
+ | protected void entityInit() { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void readEntityFromNBT(NBTTagCompound tag) { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void writeEntityToNBT(NBTTagCompound tag) { | ||
+ | } | ||
+ | |||
+ | /* IProjectileから実装を求められるメソッド */ | ||
+ | |||
+ | @Override | ||
+ | public void setThrowableHeading(double par1, double par3, double par5, float par7, float par8) { | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | ---- | ||
+ | *コンストラクタ | ||
+ | |||
+ | <source lang="java"> | ||
+ | public EntityFlame(World world) { | ||
+ | super(world); | ||
+ | } | ||
+ | </source> | ||
+ | BlockやItemは起動時の一度だけしかインスタンスが生成されないため、通常は一種のBlock、Itemにつきコンストラクタが呼ばれるのは一度だけです。しかし、Entityはいくつでもnew Entity(world~)のように、インスタンスを生成出来ます。なので、コンストラクタもEntityの生成の度に呼ばれます。<br /> | ||
+ | ですから、Entityのコンストラクタの場合はBlock等と違って、個体ごとに異なる値を入れることを考えて作成します。このMODの場合、現在地や射撃の方向、ダメージ量などを生成時にEntityに渡します。<br /> | ||
+ | |||
+ | *Entityクラスから求められるメソッド | ||
+ | |||
+ | <source lang="java"> | ||
+ | /* Entityから実装を求められるメソッド */ | ||
+ | |||
+ | @Override | ||
+ | protected void entityInit() { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void readEntityFromNBT(NBTTagCompound tag) { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void writeEntityToNBT(NBTTagCompound tag) { | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | メソッドの中身は空でもクラッシュはしません。必要な場合のみ中身を作成します。このMODの場合は、NBTタグを読み書きしたいのでNBTのメソッド2つは中身を作っています。 | ||
+ | |||
+ | *IProjectile[から求められるメソッド | ||
+ | |||
+ | <source lang="java"> | ||
+ | @Override | ||
+ | public void setThrowableHeading(double par1, double par3, double par5, float par7, float par8) { | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | 発射体が飛んでいる間の速度や向きの制御。このMODの場合はXYZ方向速度から進行方向を計算して、Entityの向きを設定しています。 | ||
+ | |||
+ | ===初期パラメータを入れてみる=== | ||
+ | |||
+ | 上記のメソッドに必要なパラメータを入れてみます。<br /> | ||
+ | この弾Entityの生成座標、射撃したEntity(EntityLivingBase)、与えるダメージや弾速、サイズなどの初期値をここで入れます。<br /> | ||
+ | 今回はそれに加えて、Entityクラスのメソッドの一部をオーバーライドして内容を変えてみます。 | ||
+ | |||
+ | <source lang="java"> | ||
+ | /* この弾を撃ったエンティティ */ | ||
+ | public Entity shootingEntity; | ||
+ | |||
+ | /* 地中・空中にいる時間 */ | ||
+ | protected int ticksInGround; | ||
+ | protected int ticksInAir; | ||
+ | protected int livingTimeCount = 0; | ||
+ | |||
+ | /* ダメージの大きさ */ | ||
+ | protected final double damage; | ||
+ | |||
+ | /* 前進速度 */ | ||
+ | protected double speed = 1.0D; | ||
+ | |||
+ | /* 幅 */ | ||
+ | protected final double range; | ||
+ | |||
+ | /* ノックバックの大きさ */ | ||
+ | protected final int coolTime; | ||
+ | |||
+ | // コンストラクタ | ||
+ | public EntityFlame2(World par1World) { | ||
+ | super(par1World); | ||
+ | this.renderDistanceWeight = 10.0D; | ||
+ | this.setSize(0.5F, 0.5F); | ||
+ | this.damage = 10.0D; | ||
+ | this.range = 4.0D; | ||
+ | this.coolTime = 10; | ||
+ | } | ||
+ | |||
+ | public EntityFlame2(World par1World, float size, double thisdamage, double thisrange, int thisCoolTime) { | ||
+ | super(par1World); | ||
+ | this.setSize(size, size); | ||
+ | this.range = thisrange; | ||
+ | this.damage = thisdamage; | ||
+ | this.coolTime = thisCoolTime; | ||
+ | } | ||
+ | |||
+ | public EntityFlame2(World par1World, EntityLivingBase par2EntityLivingBase, float speed, float fluctuations, | ||
+ | double damage, double range, int cool) { | ||
+ | this(par1World, 0.5F, damage, range, cool); | ||
+ | this.shootingEntity = par2EntityLivingBase; | ||
+ | this.yOffset = 0.0F; | ||
+ | |||
+ | // 初期状態での向きの決定 - (1) | ||
+ | this.setLocationAndAngles(par2EntityLivingBase.posX, | ||
+ | par2EntityLivingBase.posY + par2EntityLivingBase.getEyeHeight() - 0.6D, par2EntityLivingBase.posZ, | ||
+ | par2EntityLivingBase.rotationYaw, par2EntityLivingBase.rotationPitch); | ||
+ | |||
+ | // 位置の調整 - (2) | ||
+ | this.posX += (-MathHelper.sin(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch | ||
+ | / 180.0F * (float) Math.PI)); | ||
+ | this.posZ += (MathHelper.cos(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch | ||
+ | / 180.0F * (float) Math.PI)); | ||
+ | this.setPosition(this.posX, this.posY, this.posZ); | ||
+ | |||
+ | float f1 = worldObj.rand.nextFloat() * 0.1F - 0.05F; | ||
+ | float f2 = worldObj.rand.nextFloat() * 0.1F - 0.05F; | ||
+ | float f3 = worldObj.rand.nextFloat() * 0.1F - 0.05F; | ||
+ | |||
+ | // 初速度 - (3) | ||
+ | this.motionX = f1 | ||
+ | + ((double) (-MathHelper.sin(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper | ||
+ | .cos(this.rotationPitch / 180.0F * (float) Math.PI))); | ||
+ | this.motionZ = f2 | ||
+ | + ((double) (MathHelper.cos(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper | ||
+ | .cos(this.rotationPitch / 180.0F * (float) Math.PI))); | ||
+ | this.motionY = f3 + ((double) (-MathHelper.sin(this.rotationPitch / 180.0F * (float) Math.PI))); | ||
+ | this.setThrowableHeading(this.motionX, this.motionY, this.motionZ, speed, fluctuations); | ||
+ | } | ||
+ | |||
+ | /* Entityから実装を求められるメソッド */ | ||
+ | |||
+ | @Override | ||
+ | protected void entityInit() { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void readEntityFromNBT(NBTTagCompound tag) { | ||
+ | |||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void writeEntityToNBT(NBTTagCompound tag) { | ||
+ | |||
+ | } | ||
+ | |||
+ | /* IProjectileから実装を求められるメソッド */ | ||
+ | |||
+ | @Override | ||
+ | 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 /= f2; | ||
+ | par3 /= f2; | ||
+ | par5 /= f2; | ||
+ | par1 += this.rand.nextGaussian() * (this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * par8; | ||
+ | par3 += this.rand.nextGaussian() * (this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * par8; | ||
+ | par5 += this.rand.nextGaussian() * (this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * par8; | ||
+ | par1 *= par7; | ||
+ | par3 *= par7; | ||
+ | par5 *= 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, f3) * 180.0D / Math.PI); | ||
+ | this.ticksInGround = 0; | ||
+ | } | ||
+ | |||
+ | /* 以下はEntity.classの処理のオーバーライド */ | ||
+ | |||
+ | // レンダー用に使われる、位置や向き情報などのメソッド | ||
+ | @Override | ||
+ | @SideOnly(Side.CLIENT) | ||
+ | public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9) { | ||
+ | this.setPosition(par1, par3, par5); | ||
+ | this.setRotation(par7, par8); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | @SideOnly(Side.CLIENT) | ||
+ | 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, 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; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // 接地したとき、ブロックが「Entityに踏み荒らされた」判定にするかどうか | ||
+ | @Override | ||
+ | protected boolean canTriggerWalking() { | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | // 影の描画サイズ これは影を無しにする | ||
+ | @Override | ||
+ | @SideOnly(Side.CLIENT) | ||
+ | public float getShadowSize() { | ||
+ | return 0.0F; | ||
+ | } | ||
+ | |||
+ | // Itemを使ってこのEntityを攻撃できるか | ||
+ | @Override | ||
+ | public boolean canAttackWithItem() { | ||
+ | return false; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | 処理の流れは、 | ||
+ | #ItemIgnis.classの右クリック処理から、このクラスのコンストラクタpublic EntityFlame2(World par1World, EntityLivingBase par2EntityLivingBase, float speed, float speed2, double damage, double range, int cool)を呼ぶ | ||
+ | #ShootingEntityに引数で渡したEntityLivingBase(射撃手)を入れ、これのpos(座標)、yaw(水平方向の向き)、pitch(垂直方向の向き)から初期座標と向きを算出 - (1) | ||
+ | #射撃手の前から弾が発生しているように見えるよう、射撃手の少し手前に位置を調整 - (2) | ||
+ | #引数のSpeedにあわせ、setThrowableHeading(double par1, double par3, double par5, float par7, float par8)メソッドを利用しつつ初速度を算出 - (3)<br /> | ||
+ | |||
+ | このような形です。とりあえず、初速度だけ与えてあるので飛んでは行くと思われますが、アップデート処理(onUpdate()メソッド)はEntity.classにあるデフォルトの物しか呼ばれませんし、当たっても何も起こらないし消えもしないんじゃないかと思います。 | ||
+ | |||
+ | ===Update処理を作る=== | ||
+ | |||
+ | Update処理というのは、ワールドの読み込みチャンク内に存在している間、毎Tick呼ばれ続ける更新処理です。BlockやTileEntityにも備わっており、Entityの場合はonUpdate()メソッドのオーバーライドで内容を追加してやることで作成できます。<br /> | ||
+ | 注意点として、必ずsuper.onUpdate();で継承元の処理を呼ぶこと。そうでないと必要な処理が行われなくなってしまいます。(代用の処理を自作できる猛者ならこの限りではありませんが…) | ||
+ | |||
+ | <source lang="java"> | ||
+ | // Tick処理部分 | ||
+ | @Override | ||
+ | public void onUpdate() { | ||
+ | super.onUpdate(); | ||
+ | |||
+ | // ここに処理を入れる | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | *移動や消滅条件などをつくる | ||
+ | |||
+ | まずは移動部分を作ります。攻撃能力はまだありません。 | ||
+ | #一定時間経過、ブロック内部に一定時間埋まっている、一定以下の速度に減速する、のどれかの条件で消滅する | ||
+ | #移動中に常に前方に正面を向ける | ||
+ | #水に入ると消火され、減速する<br /> | ||
+ | |||
+ | コンストラクタの前あたりに追加しておきます | ||
+ | <source lang="java"> | ||
+ | /* 地中判定に使うもの */ | ||
+ | protected int xTile = -1; | ||
+ | protected int yTile = -1; | ||
+ | protected int zTile = -1; | ||
+ | protected Block inTile; | ||
+ | protected int inData; | ||
+ | protected boolean inGround; | ||
+ | </source> | ||
+ | |||
+ | ---- | ||
+ | onUpdateメソッドにも書き足します | ||
+ | <source lang="java"> | ||
+ | // Tick処理部分 | ||
+ | @Override | ||
+ | public void onUpdate() { | ||
+ | super.onUpdate(); | ||
+ | |||
+ | // 60tick(3sec)経過すると自然消滅する | ||
+ | livingTimeCount++; | ||
+ | if (livingTimeCount > 60) | ||
+ | this.setDead(); | ||
+ | |||
+ | // 激突したブロックを確認している | ||
+ | // xyzTileには、前回処理時に当たったBlockの座標が記録されているので、継続して埋まったままか確認している。 | ||
+ | Block i = this.worldObj.getBlock(this.xTile, this.yTile, this.zTile); | ||
+ | boolean air = this.worldObj.isAirBlock(xTile, yTile, zTile); | ||
+ | |||
+ | // 空気じゃないブロックだった時(継続して埋まっている) | ||
+ | if (i != null && i.getMaterial() != Material.air) { | ||
+ | // 非固形ブロックなど、当たり判定が立方体でないブロックのAABBに接触しているかチェックしている | ||
+ | i.setBlockBoundsBasedOnState(this.worldObj, this.xTile, this.yTile, this.zTile); | ||
+ | AxisAlignedBB axisalignedbb = i.getCollisionBoundingBoxFromPool(this.worldObj, this.xTile, this.yTile, | ||
+ | this.zTile); | ||
+ | |||
+ | // 当たり判定に接触しているかどうか | ||
+ | if (axisalignedbb != null | ||
+ | && axisalignedbb.isVecInside(Vec3.createVectorHelper(this.posX, this.posY, this.posZ))) { | ||
+ | // 埋まり判定をtrueに | ||
+ | this.inGround = true; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // 空気じゃないブロックに当たった | ||
+ | if (this.inGround) { | ||
+ | Block j = this.worldObj.getBlock(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 = 20; | ||
+ | |||
+ | if (this.ticksInGround > limit) { | ||
+ | this.setDead(); | ||
+ | } | ||
+ | } else// 埋まり状態の解除処理 | ||
+ | { | ||
+ | this.inGround = false; | ||
+ | this.ticksInGround = 0; | ||
+ | this.ticksInAir = 0; | ||
+ | } | ||
+ | } else { | ||
+ | // ここは次の段階で書き込むところ! | ||
+ | } | ||
+ | |||
+ | // ポジションに速度を加算してEntityを移動させる処理。向きも更新。 | ||
+ | this.posX += this.motionX; | ||
+ | this.posY += this.motionY; | ||
+ | this.posZ += this.motionZ; | ||
+ | float 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, 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.9F; | ||
+ | |||
+ | // 水中に有る | ||
+ | if (this.isInWater()) { | ||
+ | |||
+ | // 着火状態が解除される | ||
+ | if (this.isBurning()) { | ||
+ | this.extinguish(); | ||
+ | } | ||
+ | |||
+ | // 減速も大きくなる | ||
+ | f4 = 0.1F; | ||
+ | } | ||
+ | |||
+ | this.motionX *= f4; | ||
+ | this.motionY *= f4; | ||
+ | this.motionZ *= f4; | ||
+ | |||
+ | // 一定以上遅くなったら消える | ||
+ | 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.func_145775_I(); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | *攻撃能力をつける | ||
+ | 先の記述でコメントを入れておいた場所に書き足します。内容はかなり多いので、Javadocを参考にどうぞ。<br /> | ||
+ | まずは必要なフラグをコンストラクタの前あたりに追加。のちの、エンチャントによる強化機能で使用予定のもの。 | ||
+ | <source lang="java"> | ||
+ | // 強化判定 | ||
+ | protected boolean isCorrosion = false; | ||
+ | protected boolean isFire = false; | ||
+ | </source> | ||
+ | ---- | ||
+ | 次に、onUpdate()の予告部分に以下を挿入。 | ||
+ | <source lang="java"> | ||
+ | ++this.ticksInAir; | ||
+ | // ブロックとの衝突判定 | ||
+ | Vec3 vec3 = Vec3.createVectorHelper(this.posX, this.posY, this.posZ); | ||
+ | Vec3 vec31 = Vec3.createVectorHelper(this.posX + this.motionX, this.posY + this.motionY, this.posZ | ||
+ | + this.motionZ); | ||
+ | MovingObjectPosition movingobjectposition = this.worldObj.func_147447_a(vec3, vec31, false, true, false); | ||
+ | vec3 = Vec3.createVectorHelper(this.posX, this.posY, this.posZ); | ||
+ | vec31 = Vec3.createVectorHelper(this.posX + this.motionX, this.posY + this.motionY, this.posZ | ||
+ | + this.motionZ); | ||
+ | |||
+ | |||
+ | // ブロックに当たった | ||
+ | if (movingobjectposition != null) { | ||
+ | vec31 = Vec3.createVectorHelper(movingobjectposition.hitVec.xCoord, movingobjectposition.hitVec.yCoord, | ||
+ | movingobjectposition.hitVec.zCoord); | ||
+ | } | ||
+ | |||
+ | // Entityとの衝突判定。 | ||
+ | // コンストラクタで入れたrangeの分だけ当たり判定を拡大し、接触しているEntityのリストを得ます。 | ||
+ | List list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, | ||
+ | this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ).expand(range, range, range)); | ||
+ | double d0 = 0.0D; | ||
+ | int l; | ||
+ | float f1; | ||
+ | boolean isVillager = false; | ||
+ | ArrayList<MovingObjectPosition> entityList = new ArrayList<MovingObjectPosition>(); | ||
+ | |||
+ | // 1ブロック分の範囲内にいるエンティティ全てに対して繰り返す | ||
+ | // ここは、EntityArrowでは一体だけに対象を絞っているが、このMODでは全てを対象に取る | ||
+ | for (l = 0; l < list.size(); ++l) { | ||
+ | Entity entity1 = (Entity) list.get(l); | ||
+ | Entity entity = null; | ||
+ | |||
+ | // 自傷防止のため、発射者自身or発射後5tick以外だとすりぬける | ||
+ | if (entity1.canBeCollidedWith() && entity1 != this.shootingEntity) { | ||
+ | f1 = 0.5F; | ||
+ | // 上下方向は広めに見る | ||
+ | AxisAlignedBB axisalignedbb1 = entity1.boundingBox.expand(f1, 1.0F, f1); | ||
+ | MovingObjectPosition movingobjectposition1 = axisalignedbb1.calculateIntercept(vec3, vec31); | ||
+ | |||
+ | if (movingobjectposition1 != null) { | ||
+ | double d1 = vec3.distanceTo(movingobjectposition1.hitVec); | ||
+ | |||
+ | if (d1 < d0 || d0 == 0.0D) { | ||
+ | // arrowと異なり、あたったEntityすべてをリストに入れる | ||
+ | entityList.add(new MovingObjectPosition(entity1)); | ||
+ | d0 = d1; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | * 当たったエンティティそれそれについての判定部分。 | ||
+ | * ここで特定の種類のエンティティに当たらないようにできる。 | ||
+ | */ | ||
+ | if (entityList != null && !entityList.isEmpty()) { | ||
+ | // リストを回す | ||
+ | for (int n = 0; n < entityList.size(); n++) { | ||
+ | Entity target = entityList.get(n).entityHit; | ||
+ | |||
+ | if (target instanceof EntityPlayer) { | ||
+ | // プレイヤーに当たった時 | ||
+ | EntityPlayer entityplayer = (EntityPlayer) target; | ||
+ | |||
+ | if (entityplayer.capabilities.disableDamage || this.shootingEntity instanceof EntityPlayer | ||
+ | && !((EntityPlayer) this.shootingEntity).canAttackPlayer(entityplayer)) { | ||
+ | // PvPが許可されていないと当たらない | ||
+ | entityList.remove(n); | ||
+ | } | ||
+ | } else if (target == this.shootingEntity) { | ||
+ | // 対象が撃った本人の場合も当たらない | ||
+ | entityList.remove(n); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | float f3; | ||
+ | |||
+ | // 当たったあとの処理 | ||
+ | // まずはリストから | ||
+ | if (entityList != null && !entityList.isEmpty()) { | ||
+ | for (MovingObjectPosition target : entityList) { | ||
+ | if (target != null && target.entityHit != null) { | ||
+ | // ダメージは固定 | ||
+ | int i1 = MathHelper.ceiling_double_int(this.damage); | ||
+ | // 0~2程度の乱数値を上乗せ | ||
+ | i1 += this.rand.nextInt(3); | ||
+ | // 別メソッドでダメージソースを確認 …ここでは、仮としてLavaに固定にしている | ||
+ | DamageSource damagesource = this.thisDamageSource(this.shootingEntity); | ||
+ | |||
+ | if (target.entityHit instanceof IProjectile && !(target.entityHit instanceof EntityFlame)) { | ||
+ | // 対象が矢などの飛翔Entityの場合、打ち消すことが出来る | ||
+ | target.entityHit.setDead(); | ||
+ | } else { | ||
+ | |||
+ | // ダメージを与える | ||
+ | if (target.entityHit.attackEntityFrom(damagesource, i1)) { | ||
+ | // ダメージを与えることに成功したら以下の処理を行う | ||
+ | if (target.entityHit instanceof EntityLivingBase) { | ||
+ | EntityLivingBase living = (EntityLivingBase) target.entityHit; | ||
+ | |||
+ | // 腐食による弱体化potion付与 | ||
+ | if (this.isCorrosion) { | ||
+ | living.addPotionEffect(new PotionEffect(Potion.weakness.getId(), 200, 0)); | ||
+ | } | ||
+ | |||
+ | // 着火 | ||
+ | if (this.isFire) { | ||
+ | // 着火はせず、アマ貫の炎属性ダメージが入る | ||
+ | living.attackEntityFrom(DamageSource.onFire, 1.0F); | ||
+ | } | ||
+ | |||
+ | // 無敵時間 | ||
+ | if (this.coolTime > 0) { | ||
+ | target.entityHit.hurtResistantTime = coolTime; | ||
+ | } else { | ||
+ | target.entityHit.hurtResistantTime = 0; | ||
+ | } | ||
+ | |||
+ | // マルチプレイ時に、両者がプレイヤーだった時のパケット送信処理 | ||
+ | if (this.shootingEntity != null && target.entityHit != this.shootingEntity | ||
+ | && target.entityHit instanceof EntityPlayer | ||
+ | && this.shootingEntity instanceof EntityPlayerMP) { | ||
+ | ((EntityPlayerMP) this.shootingEntity).playerNetServerHandler | ||
+ | .sendPacket(new S2BPacketChangeGameState(6, 0.0F)); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (movingobjectposition != null)// blockの接触判定をとっておいたのをここで処理 | ||
+ | { | ||
+ | // 当たったブロックを記憶し、次Tickで上の方の埋まり判定に使う | ||
+ | this.xTile = movingobjectposition.blockX; | ||
+ | this.yTile = movingobjectposition.blockY; | ||
+ | this.zTile = movingobjectposition.blockZ; | ||
+ | this.inTile = this.worldObj.getBlock(this.xTile, this.yTile, this.zTile); | ||
+ | this.inData = this.worldObj.getBlockMetadata(this.xTile, this.yTile, this.zTile); | ||
+ | |||
+ | if (this.inTile != null) { | ||
+ | this.inTile.onEntityCollidedWithBlock(this.worldObj, this.xTile, this.yTile, this.zTile, this); | ||
+ | |||
+ | // 腐食属性の時 configでONならブロック破壊効果を起こす | ||
+ | if (this.isCorrosion && FlameCore.meltBlock) { | ||
+ | if (inTile == Blocks.stone) { | ||
+ | this.worldObj.setBlock(xTile, yTile, zTile, Blocks.cobblestone); | ||
+ | } else if (inTile == Blocks.grass) { | ||
+ | this.worldObj.setBlock(xTile, yTile, zTile, Blocks.dirt); | ||
+ | } else if (inTile == Blocks.cobblestone || inTile == Blocks.dirt || inTile == Blocks.gravel | ||
+ | || inTile == Blocks.sand) { | ||
+ | this.worldObj.setBlockToAir(xTile, yTile, zTile); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // フレイムエンチャント時にWood属性のブロックを破壊 | ||
+ | if (this.isFire && FlameCore.meltBlock) { | ||
+ | if (inTile instanceof BlockLeavesBase || inTile instanceof BlockBush) { | ||
+ | this.worldObj.setBlockToAir(xTile, yTile, zTile); | ||
+ | } else if (inTile.getMaterial() == Material.wood) { | ||
+ | this.worldObj.setBlockToAir(xTile, yTile, zTile); | ||
+ | boolean b = false; | ||
+ | ArrayList<ItemStack> ores = OreDictionary.getOres("logWood"); | ||
+ | for (ItemStack log : ores) { | ||
+ | if (log == null || log.getItem() == null) | ||
+ | continue; | ||
+ | if (log.getItem() == Item.getItemFromBlock(inTile)) { | ||
+ | b = true; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | // 原木を破壊した時に木炭をドロップさせている | ||
+ | if (b && !worldObj.isRemote) { | ||
+ | ItemStack c = new ItemStack(Items.coal, 1, 1); | ||
+ | EntityItem drop = new EntityItem(worldObj, xTile, yTile, zTile, c); | ||
+ | worldObj.spawnEntityInWorld(drop); | ||
+ | } | ||
+ | } else if (inTile.getMaterial() == Material.snow || inTile.getMaterial() == Material.ice) { | ||
+ | this.worldObj.setBlockToAir(xTile, yTile, zTile); | ||
+ | } | ||
+ | if (worldObj.isAirBlock(xTile, yTile + 1, zTile) | ||
+ | && inTile.isFlammable(worldObj, xTile, yTile, zTile, ForgeDirection.UP)) { | ||
+ | worldObj.setBlock(xTile, yTile + 1, zTile, Blocks.fire); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } else { // どうもmovingobjectpositionで取りこぼすようなので、nullの場合も同じ事をしている。ひょっとしてすごく無駄なことしているのでは… | ||
+ | |||
+ | // inTile(埋まり判定)は更新しない。 | ||
+ | int tarX = MathHelper.floor_double(this.posX); | ||
+ | int tarY = MathHelper.floor_double(this.posY); | ||
+ | int tarZ = MathHelper.floor_double(this.posZ); | ||
+ | Block target = worldObj.getBlock(tarX, tarY, tarZ); | ||
+ | |||
+ | if (target != null && target.getMaterial() != Material.air) { | ||
+ | // 上の処理より出来ることを少なめにしていたりする | ||
+ | if (this.isCorrosion && FlameCore.meltBlock) { | ||
+ | if (inTile == Blocks.stone) { | ||
+ | this.worldObj.setBlock(xTile, yTile, zTile, Blocks.cobblestone); | ||
+ | } else if (inTile == Blocks.grass) { | ||
+ | this.worldObj.setBlock(xTile, yTile, zTile, Blocks.dirt); | ||
+ | } | ||
+ | } | ||
+ | if (this.isFire) { | ||
+ | if (FlameCore.meltBlock) { | ||
+ | if (inTile instanceof BlockLeavesBase || inTile instanceof BlockBush) { | ||
+ | this.worldObj.setBlockToAir(xTile, yTile, zTile); | ||
+ | } else if (target.getMaterial() == Material.snow || target.getMaterial() == Material.ice) { | ||
+ | this.worldObj.setBlockToAir(tarX, tarY, tarZ); | ||
+ | } | ||
+ | } | ||
+ | // あらゆるブロックに着火 | ||
+ | if (worldObj.isAirBlock(tarX, tarY + 1, tarZ) | ||
+ | /* && target.isFlammable(worldObj, tarX, tarY, tarZ, ForgeDirection.UP) */) { | ||
+ | worldObj.setBlock(tarX, tarY + 1, tarZ, Blocks.fire); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | ---- | ||
+ | 最後に、上記処理内で使っているフラグ類のsetter、getterなど。<br /> | ||
+ | setterは射撃時にItemクラス側から呼び出します。 | ||
+ | <source lang="java"> | ||
+ | public void setCorrosion(boolean b) { | ||
+ | this.isCorrosion = b; | ||
+ | } | ||
+ | |||
+ | public boolean getCorrosion() { | ||
+ | return this.isCorrosion; | ||
+ | } | ||
+ | |||
+ | public void setAdvFire(boolean b) { | ||
+ | this.isFire = b; | ||
+ | } | ||
+ | |||
+ | public boolean getAdvFire() { | ||
+ | return this.isFire; | ||
+ | } | ||
+ | |||
+ | /* ダメージソースのタイプ */ | ||
+ | public DamageSource thisDamageSource(Entity entity) { | ||
+ | return isCorrosion ? DamageSource.onFire : DamageSource.lava; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | *登録処理 | ||
+ | もちろんこれも、登録しなければ使用できません。<br /> | ||
+ | 登録時にはIDも決めますが、以下のメソッドを使って登録する場合、同じMOD内でIDが被らなければ大丈夫です。 | ||
+ | *FlameCore.class | ||
+ | <source lang="java"> | ||
+ | @EventHandler | ||
+ | public void init(FMLInitializationEvent event) throws IOException { | ||
+ | |||
+ | // entity | ||
+ | int idFlame = 1; | ||
+ | EntityRegistry.registerModEntity(EntityFlame.class, "dcsflame.entity_flame", idFlame, this, 128, 5, true); | ||
+ | |||
+ | // render | ||
+ | proxy.registerRenderers(); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ===Entityの見た目を作る=== | ||
+ | ここまでで、なんとかEntityの処理を作ってきました。見た目は灰色の四角形ですが、一応動きます。次は、見た目部分を作っていきます。<br /> | ||
+ | といっても今回は手抜きの方法をとります。<br /> | ||
+ | '''着火されたEntityには上に炎が描画'''されることを利用し、'''Entity自体を不可視にしてしまうことで、炎が飛んでいくように見せる'''わけです。 | ||
+ | |||
+ | *RenderBullet.class | ||
+ | やっていることは単純で、'''doRenderメソッドで何もしない'''だけ。その他のパラメータ(ResourceLocationなど)も、不要ですがnullを返すのも不安があるのでダミーを渡しておきます。 | ||
+ | <source lang="java"> | ||
+ | package defeatedcrow.flamethrower.client.model; | ||
+ | |||
+ | import net.minecraft.client.model.ModelBase; | ||
+ | import net.minecraft.client.renderer.entity.Render; | ||
+ | import net.minecraft.entity.Entity; | ||
+ | import net.minecraft.util.ResourceLocation; | ||
+ | import cpw.mods.fml.relauncher.Side; | ||
+ | import cpw.mods.fml.relauncher.SideOnly; | ||
+ | import defeatedcrow.flamethrower.entity.EntityFlame; | ||
+ | |||
+ | @SideOnly(Side.CLIENT) | ||
+ | public class RenderBullet extends Render { | ||
+ | private static final ResourceLocation bulletTextures = new ResourceLocation("textures/items/particle_flame.png"); | ||
+ | protected ModelBase modelBullet; | ||
+ | |||
+ | public RenderBullet(ModelBase par1ModelBase) { | ||
+ | super(); | ||
+ | this.modelBullet = par1ModelBase; | ||
+ | this.shadowSize = 0.0F; | ||
+ | } | ||
+ | |||
+ | protected ResourceLocation getArrowTextures(EntityFlame par1EntityArrow) { | ||
+ | return bulletTextures; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected ResourceLocation getEntityTexture(Entity par1Entity) { | ||
+ | return this.getArrowTextures((EntityFlame) par1Entity); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void doRender(Entity par1Entity, double par2, double par4, double par6, float par8, float par9) { | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | *ClientProxyF.class | ||
+ | 作ったRenderクラスをEntityに紐付けます。 | ||
+ | <source lang="java"> | ||
+ | @Override | ||
+ | public void registerRenderers() { | ||
+ | // 書き足した部分はこれ | ||
+ | RenderingRegistry.registerEntityRenderingHandler(EntityFlame.class, new RenderBullet(null)); | ||
+ | MinecraftForgeClient.registerItemRenderer(FlameCore.flamethrower, new ItemRenderIgnis()); | ||
+ | } | ||
+ | </source> | ||
+ | ---- | ||
+ | さてここまででEntityを作成しました。しかし、まだゲーム中で確かめることは出来ません。Entityの生成手段を作っていないからです。 |
2016年9月21日 (水) 10:53時点における最新版
<目次に戻る>
目次
弾Entityの作成
IGNISから発射する弾をEntityとして作成します。
EntityはBlockのように座標で固定されない、動きを持たせることが出来るオブジェクトです。ただし生成するだけではその場から動かないし何もしません。動きや向きの制御、接触判定、接触後の処理など、必要なことは自分で追加しないといけません。
(そのため、チュートリアル等ではアイテムやブロックに比べてやや上級向けとして扱われます。初心者が作りたくなるアイデアを最も理想に近い形で実装できる道でもあり、理解不足の初心者Modderを叩き落とす罠でもあります。)
私の場合、自作チュートリアルからソースを引っ張ってきて、改良しつつ作っていきます。チュートリアル(村人波動砲)のほうは、バニラの矢Entityを参考にしつつ作っています。
弾Entityの作成にあたり、どんなふうに作るのか考えてみます。
- Entityを貫通する
- Blockを貫通するが、貫通可能距離には限界がある
- ダメージを与える対象を選択する(火炎放射なので、火炎耐性を持っているとダメージを与えられない、など)
- 武器のエンチャントに応じた効果の強化
銃アイテムはEntityに座標や方角などの初期パラメータを与えて生成する程度のことしかしません。 実際の攻撃処理は、Entity側で行います。
実際に実装されているクラス
なお、内容はVer1.1aのものです。 記事が冗長になるため、クラス全文は別ページに移します。
Entityクラスを作る
Entityクラスを継承する
Entityを作るには、Entity.classを継承します。また、今回は弾を作るため、バニラの発射体用インターフェイスであるIProjectileを実装します。
Entity.class、IProjectileインターフェイスそれぞれ、いくつかの"必ず作らなければならないメソッド"があります。以下の状態が、弾Entityの最低限の状態です。
以下のように生成した場合、継承しているEntity.classに備わっているデフォルトの機能のみ持った状態になります。座標移動や当たり判定周りの機能は備わっていますが自力移動は出来ず、デフォルトの見た目(灰色の無地のボックス)の高さ1.8F、幅0.6F、当たり判定無しの四角い物体が、(おそらく)初期地点を選べない状態で生成されます。
package defeatedcrow.flamethrower.entity; import net.minecraft.entity.Entity; import net.minecraft.entity.IProjectile; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; public class EntityFlame extends Entity implements IProjectile { // コンストラクタ public EntityFlame(World world) { super(world); } /* Entityから実装を求められるメソッド */ @Override protected void entityInit() { } @Override protected void readEntityFromNBT(NBTTagCompound tag) { } @Override protected void writeEntityToNBT(NBTTagCompound tag) { } /* IProjectileから実装を求められるメソッド */ @Override public void setThrowableHeading(double par1, double par3, double par5, float par7, float par8) { } }
- コンストラクタ
public EntityFlame(World world) { super(world); }
BlockやItemは起動時の一度だけしかインスタンスが生成されないため、通常は一種のBlock、Itemにつきコンストラクタが呼ばれるのは一度だけです。しかし、Entityはいくつでもnew Entity(world~)のように、インスタンスを生成出来ます。なので、コンストラクタもEntityの生成の度に呼ばれます。
ですから、Entityのコンストラクタの場合はBlock等と違って、個体ごとに異なる値を入れることを考えて作成します。このMODの場合、現在地や射撃の方向、ダメージ量などを生成時にEntityに渡します。
- Entityクラスから求められるメソッド
/* Entityから実装を求められるメソッド */ @Override protected void entityInit() { } @Override protected void readEntityFromNBT(NBTTagCompound tag) { } @Override protected void writeEntityToNBT(NBTTagCompound tag) { }
メソッドの中身は空でもクラッシュはしません。必要な場合のみ中身を作成します。このMODの場合は、NBTタグを読み書きしたいのでNBTのメソッド2つは中身を作っています。
- IProjectile[から求められるメソッド
@Override public void setThrowableHeading(double par1, double par3, double par5, float par7, float par8) { }
発射体が飛んでいる間の速度や向きの制御。このMODの場合はXYZ方向速度から進行方向を計算して、Entityの向きを設定しています。
初期パラメータを入れてみる
上記のメソッドに必要なパラメータを入れてみます。
この弾Entityの生成座標、射撃したEntity(EntityLivingBase)、与えるダメージや弾速、サイズなどの初期値をここで入れます。
今回はそれに加えて、Entityクラスのメソッドの一部をオーバーライドして内容を変えてみます。
/* この弾を撃ったエンティティ */ public Entity shootingEntity; /* 地中・空中にいる時間 */ protected int ticksInGround; protected int ticksInAir; protected int livingTimeCount = 0; /* ダメージの大きさ */ protected final double damage; /* 前進速度 */ protected double speed = 1.0D; /* 幅 */ protected final double range; /* ノックバックの大きさ */ protected final int coolTime; // コンストラクタ public EntityFlame2(World par1World) { super(par1World); this.renderDistanceWeight = 10.0D; this.setSize(0.5F, 0.5F); this.damage = 10.0D; this.range = 4.0D; this.coolTime = 10; } public EntityFlame2(World par1World, float size, double thisdamage, double thisrange, int thisCoolTime) { super(par1World); this.setSize(size, size); this.range = thisrange; this.damage = thisdamage; this.coolTime = thisCoolTime; } public EntityFlame2(World par1World, EntityLivingBase par2EntityLivingBase, float speed, float fluctuations, double damage, double range, int cool) { this(par1World, 0.5F, damage, range, cool); this.shootingEntity = par2EntityLivingBase; this.yOffset = 0.0F; // 初期状態での向きの決定 - (1) this.setLocationAndAngles(par2EntityLivingBase.posX, par2EntityLivingBase.posY + par2EntityLivingBase.getEyeHeight() - 0.6D, par2EntityLivingBase.posZ, par2EntityLivingBase.rotationYaw, par2EntityLivingBase.rotationPitch); // 位置の調整 - (2) this.posX += (-MathHelper.sin(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float) Math.PI)); this.posZ += (MathHelper.cos(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float) Math.PI)); this.setPosition(this.posX, this.posY, this.posZ); float f1 = worldObj.rand.nextFloat() * 0.1F - 0.05F; float f2 = worldObj.rand.nextFloat() * 0.1F - 0.05F; float f3 = worldObj.rand.nextFloat() * 0.1F - 0.05F; // 初速度 - (3) this.motionX = f1 + ((double) (-MathHelper.sin(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper .cos(this.rotationPitch / 180.0F * (float) Math.PI))); this.motionZ = f2 + ((double) (MathHelper.cos(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper .cos(this.rotationPitch / 180.0F * (float) Math.PI))); this.motionY = f3 + ((double) (-MathHelper.sin(this.rotationPitch / 180.0F * (float) Math.PI))); this.setThrowableHeading(this.motionX, this.motionY, this.motionZ, speed, fluctuations); } /* Entityから実装を求められるメソッド */ @Override protected void entityInit() { } @Override protected void readEntityFromNBT(NBTTagCompound tag) { } @Override protected void writeEntityToNBT(NBTTagCompound tag) { } /* IProjectileから実装を求められるメソッド */ @Override 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 /= f2; par3 /= f2; par5 /= f2; par1 += this.rand.nextGaussian() * (this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * par8; par3 += this.rand.nextGaussian() * (this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * par8; par5 += this.rand.nextGaussian() * (this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * par8; par1 *= par7; par3 *= par7; par5 *= 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, f3) * 180.0D / Math.PI); this.ticksInGround = 0; } /* 以下はEntity.classの処理のオーバーライド */ // レンダー用に使われる、位置や向き情報などのメソッド @Override @SideOnly(Side.CLIENT) public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9) { this.setPosition(par1, par3, par5); this.setRotation(par7, par8); } @Override @SideOnly(Side.CLIENT) 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, 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; } } // 接地したとき、ブロックが「Entityに踏み荒らされた」判定にするかどうか @Override protected boolean canTriggerWalking() { return false; } // 影の描画サイズ これは影を無しにする @Override @SideOnly(Side.CLIENT) public float getShadowSize() { return 0.0F; } // Itemを使ってこのEntityを攻撃できるか @Override public boolean canAttackWithItem() { return false; }
処理の流れは、
- ItemIgnis.classの右クリック処理から、このクラスのコンストラクタpublic EntityFlame2(World par1World, EntityLivingBase par2EntityLivingBase, float speed, float speed2, double damage, double range, int cool)を呼ぶ
- ShootingEntityに引数で渡したEntityLivingBase(射撃手)を入れ、これのpos(座標)、yaw(水平方向の向き)、pitch(垂直方向の向き)から初期座標と向きを算出 - (1)
- 射撃手の前から弾が発生しているように見えるよう、射撃手の少し手前に位置を調整 - (2)
- 引数のSpeedにあわせ、setThrowableHeading(double par1, double par3, double par5, float par7, float par8)メソッドを利用しつつ初速度を算出 - (3)
このような形です。とりあえず、初速度だけ与えてあるので飛んでは行くと思われますが、アップデート処理(onUpdate()メソッド)はEntity.classにあるデフォルトの物しか呼ばれませんし、当たっても何も起こらないし消えもしないんじゃないかと思います。
Update処理を作る
Update処理というのは、ワールドの読み込みチャンク内に存在している間、毎Tick呼ばれ続ける更新処理です。BlockやTileEntityにも備わっており、Entityの場合はonUpdate()メソッドのオーバーライドで内容を追加してやることで作成できます。
注意点として、必ずsuper.onUpdate();で継承元の処理を呼ぶこと。そうでないと必要な処理が行われなくなってしまいます。(代用の処理を自作できる猛者ならこの限りではありませんが…)
// Tick処理部分 @Override public void onUpdate() { super.onUpdate(); // ここに処理を入れる }
- 移動や消滅条件などをつくる
まずは移動部分を作ります。攻撃能力はまだありません。
- 一定時間経過、ブロック内部に一定時間埋まっている、一定以下の速度に減速する、のどれかの条件で消滅する
- 移動中に常に前方に正面を向ける
- 水に入ると消火され、減速する
コンストラクタの前あたりに追加しておきます
/* 地中判定に使うもの */ protected int xTile = -1; protected int yTile = -1; protected int zTile = -1; protected Block inTile; protected int inData; protected boolean inGround;
onUpdateメソッドにも書き足します
// Tick処理部分 @Override public void onUpdate() { super.onUpdate(); // 60tick(3sec)経過すると自然消滅する livingTimeCount++; if (livingTimeCount > 60) this.setDead(); // 激突したブロックを確認している // xyzTileには、前回処理時に当たったBlockの座標が記録されているので、継続して埋まったままか確認している。 Block i = this.worldObj.getBlock(this.xTile, this.yTile, this.zTile); boolean air = this.worldObj.isAirBlock(xTile, yTile, zTile); // 空気じゃないブロックだった時(継続して埋まっている) if (i != null && i.getMaterial() != Material.air) { // 非固形ブロックなど、当たり判定が立方体でないブロックのAABBに接触しているかチェックしている i.setBlockBoundsBasedOnState(this.worldObj, this.xTile, this.yTile, this.zTile); AxisAlignedBB axisalignedbb = i.getCollisionBoundingBoxFromPool(this.worldObj, this.xTile, this.yTile, this.zTile); // 当たり判定に接触しているかどうか if (axisalignedbb != null && axisalignedbb.isVecInside(Vec3.createVectorHelper(this.posX, this.posY, this.posZ))) { // 埋まり判定をtrueに this.inGround = true; } } // 空気じゃないブロックに当たった if (this.inGround) { Block j = this.worldObj.getBlock(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 = 20; if (this.ticksInGround > limit) { this.setDead(); } } else// 埋まり状態の解除処理 { this.inGround = false; this.ticksInGround = 0; this.ticksInAir = 0; } } else { // ここは次の段階で書き込むところ! } // ポジションに速度を加算してEntityを移動させる処理。向きも更新。 this.posX += this.motionX; this.posY += this.motionY; this.posZ += this.motionZ; float 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, 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.9F; // 水中に有る if (this.isInWater()) { // 着火状態が解除される if (this.isBurning()) { this.extinguish(); } // 減速も大きくなる f4 = 0.1F; } this.motionX *= f4; this.motionY *= f4; this.motionZ *= f4; // 一定以上遅くなったら消える 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.func_145775_I(); }
- 攻撃能力をつける
先の記述でコメントを入れておいた場所に書き足します。内容はかなり多いので、Javadocを参考にどうぞ。
まずは必要なフラグをコンストラクタの前あたりに追加。のちの、エンチャントによる強化機能で使用予定のもの。
// 強化判定 protected boolean isCorrosion = false; protected boolean isFire = false;
次に、onUpdate()の予告部分に以下を挿入。
++this.ticksInAir; // ブロックとの衝突判定 Vec3 vec3 = Vec3.createVectorHelper(this.posX, this.posY, this.posZ); Vec3 vec31 = Vec3.createVectorHelper(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ); MovingObjectPosition movingobjectposition = this.worldObj.func_147447_a(vec3, vec31, false, true, false); vec3 = Vec3.createVectorHelper(this.posX, this.posY, this.posZ); vec31 = Vec3.createVectorHelper(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ); // ブロックに当たった if (movingobjectposition != null) { vec31 = Vec3.createVectorHelper(movingobjectposition.hitVec.xCoord, movingobjectposition.hitVec.yCoord, movingobjectposition.hitVec.zCoord); } // Entityとの衝突判定。 // コンストラクタで入れたrangeの分だけ当たり判定を拡大し、接触しているEntityのリストを得ます。 List list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ).expand(range, range, range)); double d0 = 0.0D; int l; float f1; boolean isVillager = false; ArrayList<MovingObjectPosition> entityList = new ArrayList<MovingObjectPosition>(); // 1ブロック分の範囲内にいるエンティティ全てに対して繰り返す // ここは、EntityArrowでは一体だけに対象を絞っているが、このMODでは全てを対象に取る for (l = 0; l < list.size(); ++l) { Entity entity1 = (Entity) list.get(l); Entity entity = null; // 自傷防止のため、発射者自身or発射後5tick以外だとすりぬける if (entity1.canBeCollidedWith() && entity1 != this.shootingEntity) { f1 = 0.5F; // 上下方向は広めに見る AxisAlignedBB axisalignedbb1 = entity1.boundingBox.expand(f1, 1.0F, f1); MovingObjectPosition movingobjectposition1 = axisalignedbb1.calculateIntercept(vec3, vec31); if (movingobjectposition1 != null) { double d1 = vec3.distanceTo(movingobjectposition1.hitVec); if (d1 < d0 || d0 == 0.0D) { // arrowと異なり、あたったEntityすべてをリストに入れる entityList.add(new MovingObjectPosition(entity1)); d0 = d1; } } } } /* * 当たったエンティティそれそれについての判定部分。 * ここで特定の種類のエンティティに当たらないようにできる。 */ if (entityList != null && !entityList.isEmpty()) { // リストを回す for (int n = 0; n < entityList.size(); n++) { Entity target = entityList.get(n).entityHit; if (target instanceof EntityPlayer) { // プレイヤーに当たった時 EntityPlayer entityplayer = (EntityPlayer) target; if (entityplayer.capabilities.disableDamage || this.shootingEntity instanceof EntityPlayer && !((EntityPlayer) this.shootingEntity).canAttackPlayer(entityplayer)) { // PvPが許可されていないと当たらない entityList.remove(n); } } else if (target == this.shootingEntity) { // 対象が撃った本人の場合も当たらない entityList.remove(n); } } } float f3; // 当たったあとの処理 // まずはリストから if (entityList != null && !entityList.isEmpty()) { for (MovingObjectPosition target : entityList) { if (target != null && target.entityHit != null) { // ダメージは固定 int i1 = MathHelper.ceiling_double_int(this.damage); // 0~2程度の乱数値を上乗せ i1 += this.rand.nextInt(3); // 別メソッドでダメージソースを確認 …ここでは、仮としてLavaに固定にしている DamageSource damagesource = this.thisDamageSource(this.shootingEntity); if (target.entityHit instanceof IProjectile && !(target.entityHit instanceof EntityFlame)) { // 対象が矢などの飛翔Entityの場合、打ち消すことが出来る target.entityHit.setDead(); } else { // ダメージを与える if (target.entityHit.attackEntityFrom(damagesource, i1)) { // ダメージを与えることに成功したら以下の処理を行う if (target.entityHit instanceof EntityLivingBase) { EntityLivingBase living = (EntityLivingBase) target.entityHit; // 腐食による弱体化potion付与 if (this.isCorrosion) { living.addPotionEffect(new PotionEffect(Potion.weakness.getId(), 200, 0)); } // 着火 if (this.isFire) { // 着火はせず、アマ貫の炎属性ダメージが入る living.attackEntityFrom(DamageSource.onFire, 1.0F); } // 無敵時間 if (this.coolTime > 0) { target.entityHit.hurtResistantTime = coolTime; } else { target.entityHit.hurtResistantTime = 0; } // マルチプレイ時に、両者がプレイヤーだった時のパケット送信処理 if (this.shootingEntity != null && target.entityHit != this.shootingEntity && target.entityHit instanceof EntityPlayer && this.shootingEntity instanceof EntityPlayerMP) { ((EntityPlayerMP) this.shootingEntity).playerNetServerHandler .sendPacket(new S2BPacketChangeGameState(6, 0.0F)); } } } } } } } if (movingobjectposition != null)// blockの接触判定をとっておいたのをここで処理 { // 当たったブロックを記憶し、次Tickで上の方の埋まり判定に使う this.xTile = movingobjectposition.blockX; this.yTile = movingobjectposition.blockY; this.zTile = movingobjectposition.blockZ; this.inTile = this.worldObj.getBlock(this.xTile, this.yTile, this.zTile); this.inData = this.worldObj.getBlockMetadata(this.xTile, this.yTile, this.zTile); if (this.inTile != null) { this.inTile.onEntityCollidedWithBlock(this.worldObj, this.xTile, this.yTile, this.zTile, this); // 腐食属性の時 configでONならブロック破壊効果を起こす if (this.isCorrosion && FlameCore.meltBlock) { if (inTile == Blocks.stone) { this.worldObj.setBlock(xTile, yTile, zTile, Blocks.cobblestone); } else if (inTile == Blocks.grass) { this.worldObj.setBlock(xTile, yTile, zTile, Blocks.dirt); } else if (inTile == Blocks.cobblestone || inTile == Blocks.dirt || inTile == Blocks.gravel || inTile == Blocks.sand) { this.worldObj.setBlockToAir(xTile, yTile, zTile); } } // フレイムエンチャント時にWood属性のブロックを破壊 if (this.isFire && FlameCore.meltBlock) { if (inTile instanceof BlockLeavesBase || inTile instanceof BlockBush) { this.worldObj.setBlockToAir(xTile, yTile, zTile); } else if (inTile.getMaterial() == Material.wood) { this.worldObj.setBlockToAir(xTile, yTile, zTile); boolean b = false; ArrayList<ItemStack> ores = OreDictionary.getOres("logWood"); for (ItemStack log : ores) { if (log == null || log.getItem() == null) continue; if (log.getItem() == Item.getItemFromBlock(inTile)) { b = true; break; } } // 原木を破壊した時に木炭をドロップさせている if (b && !worldObj.isRemote) { ItemStack c = new ItemStack(Items.coal, 1, 1); EntityItem drop = new EntityItem(worldObj, xTile, yTile, zTile, c); worldObj.spawnEntityInWorld(drop); } } else if (inTile.getMaterial() == Material.snow || inTile.getMaterial() == Material.ice) { this.worldObj.setBlockToAir(xTile, yTile, zTile); } if (worldObj.isAirBlock(xTile, yTile + 1, zTile) && inTile.isFlammable(worldObj, xTile, yTile, zTile, ForgeDirection.UP)) { worldObj.setBlock(xTile, yTile + 1, zTile, Blocks.fire); } } } } else { // どうもmovingobjectpositionで取りこぼすようなので、nullの場合も同じ事をしている。ひょっとしてすごく無駄なことしているのでは… // inTile(埋まり判定)は更新しない。 int tarX = MathHelper.floor_double(this.posX); int tarY = MathHelper.floor_double(this.posY); int tarZ = MathHelper.floor_double(this.posZ); Block target = worldObj.getBlock(tarX, tarY, tarZ); if (target != null && target.getMaterial() != Material.air) { // 上の処理より出来ることを少なめにしていたりする if (this.isCorrosion && FlameCore.meltBlock) { if (inTile == Blocks.stone) { this.worldObj.setBlock(xTile, yTile, zTile, Blocks.cobblestone); } else if (inTile == Blocks.grass) { this.worldObj.setBlock(xTile, yTile, zTile, Blocks.dirt); } } if (this.isFire) { if (FlameCore.meltBlock) { if (inTile instanceof BlockLeavesBase || inTile instanceof BlockBush) { this.worldObj.setBlockToAir(xTile, yTile, zTile); } else if (target.getMaterial() == Material.snow || target.getMaterial() == Material.ice) { this.worldObj.setBlockToAir(tarX, tarY, tarZ); } } // あらゆるブロックに着火 if (worldObj.isAirBlock(tarX, tarY + 1, tarZ) /* && target.isFlammable(worldObj, tarX, tarY, tarZ, ForgeDirection.UP) */) { worldObj.setBlock(tarX, tarY + 1, tarZ, Blocks.fire); } } } }
最後に、上記処理内で使っているフラグ類のsetter、getterなど。
setterは射撃時にItemクラス側から呼び出します。
public void setCorrosion(boolean b) { this.isCorrosion = b; } public boolean getCorrosion() { return this.isCorrosion; } public void setAdvFire(boolean b) { this.isFire = b; } public boolean getAdvFire() { return this.isFire; } /* ダメージソースのタイプ */ public DamageSource thisDamageSource(Entity entity) { return isCorrosion ? DamageSource.onFire : DamageSource.lava; }
- 登録処理
もちろんこれも、登録しなければ使用できません。
登録時にはIDも決めますが、以下のメソッドを使って登録する場合、同じMOD内でIDが被らなければ大丈夫です。
- FlameCore.class
@EventHandler public void init(FMLInitializationEvent event) throws IOException { // entity int idFlame = 1; EntityRegistry.registerModEntity(EntityFlame.class, "dcsflame.entity_flame", idFlame, this, 128, 5, true); // render proxy.registerRenderers(); }
Entityの見た目を作る
ここまでで、なんとかEntityの処理を作ってきました。見た目は灰色の四角形ですが、一応動きます。次は、見た目部分を作っていきます。
といっても今回は手抜きの方法をとります。
着火されたEntityには上に炎が描画されることを利用し、Entity自体を不可視にしてしまうことで、炎が飛んでいくように見せるわけです。
- RenderBullet.class
やっていることは単純で、doRenderメソッドで何もしないだけ。その他のパラメータ(ResourceLocationなど)も、不要ですがnullを返すのも不安があるのでダミーを渡しておきます。
package defeatedcrow.flamethrower.client.model; import net.minecraft.client.model.ModelBase; import net.minecraft.client.renderer.entity.Render; import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import defeatedcrow.flamethrower.entity.EntityFlame; @SideOnly(Side.CLIENT) public class RenderBullet extends Render { private static final ResourceLocation bulletTextures = new ResourceLocation("textures/items/particle_flame.png"); protected ModelBase modelBullet; public RenderBullet(ModelBase par1ModelBase) { super(); this.modelBullet = par1ModelBase; this.shadowSize = 0.0F; } protected ResourceLocation getArrowTextures(EntityFlame par1EntityArrow) { return bulletTextures; } @Override protected ResourceLocation getEntityTexture(Entity par1Entity) { return this.getArrowTextures((EntityFlame) par1Entity); } @Override public void doRender(Entity par1Entity, double par2, double par4, double par6, float par8, float par9) { } }
- ClientProxyF.class
作ったRenderクラスをEntityに紐付けます。
@Override public void registerRenderers() { // 書き足した部分はこれ RenderingRegistry.registerEntityRenderingHandler(EntityFlame.class, new RenderBullet(null)); MinecraftForgeClient.registerItemRenderer(FlameCore.flamethrower, new ItemRenderIgnis()); }
さてここまででEntityを作成しました。しかし、まだゲーム中で確かめることは出来ません。Entityの生成手段を作っていないからです。