提供: Minecraft Modding Wiki
移動先: 案内検索


パーティクルの追加

溶岩の炎のエフェクトやポータルのエフェクトのような効果を追加するためのチュートリアルです。

Minecraft本体に含まれるエフェクトを発生させる方法と、独自のエフェクトを発生させる方法を解説します。

独自のエフェクトの例としては、Forestry for Minecraftの養蜂で蜂の形のパーティクル等があります。

本チュートリアルはForge 9.10.0.840で確認しました。


煙突ブロックを追加し、煙突ブロックから煙のパーティクルを発生させる例を以下に示します。

AddParticle.java

package addparticle;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.creativetab.CreativeTabs;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.LanguageRegistry;

@Mod(modid = "AddParticle", name = "AddParticle")
public class AddParticle {
	@SidedProxy(clientSide = "addparticle.ClientProxy", serverSide = "addparticle.CommonProxy")
	public static CommonProxy proxy;

	// 煙突ブロックのブロックID。良い子のみんなは設定ファイルで設定できるようにしましょう。
	public static int chimneyBlockID = 4000;

	// 煙突ブロック。今回追加するパーティクルの発生源として用意したブロック。脇役なのでパーティクル発生以外の機能はありません。
	public static Block chimney = new BlockChimney(chimneyBlockID, Material.rock).setUnlocalizedName("addparticle:chimney").func_111022_d("addparticle:chimney").setCreativeTab(CreativeTabs.tabDecorations);

	@EventHandler
	public void preInit(FMLPreInitializationEvent event) {
		GameRegistry.registerBlock(chimney, "addparticle:chimney");
		LanguageRegistry.instance().addStringLocalization("addparticle:chimney", "ja_JP", "煙突");
	}

	@EventHandler
	public void preInit(FMLInitializationEvent event) {
		proxy.init();
	}
}

CommonProxy.java

package addparticle;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
import cpw.mods.fml.common.network.IGuiHandler;

public class CommonProxy implements IGuiHandler {

	@Override
	public Object getServerGuiElement(int ID, EntityPlayer player, World world,
			int x, int y, int z) {
		return null;
	}

	@Override
	public Object getClientGuiElement(int ID, EntityPlayer player, World world,
			int x, int y, int z) {
		return null;
	}

	public void init() {
	}
}

ClientProxy.java

package addparticle;

import net.minecraftforge.common.MinecraftForge;

public class ClientProxy extends CommonProxy {

	@Override
	public void init() {
		// イベントをフックしてパーティクル用の画像を登録します
		MinecraftForge.EVENT_BUS.register(new Particles());
	}

}

Particles.java

package addparticle;

import net.minecraft.client.renderer.texture.IconRegister;
import net.minecraft.util.Icon;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.event.ForgeSubscribe;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

/**
 * パーティクル用の画像登録クラス
 */
public class Particles {
	private static Particles instance;

	// 画像パス。eclipseの場合はforge/mcp/src/minecraft/assets/addparticle/textures/items/smokecircle.pngに展開される
	// 実環境では%ziproot%/assets/addparticle/textures/items/smokecircle.png
	String[] iconNames = {"addparticle:smokecircle"};
	Icon icons[];

	public static Particles getInstance() {
		if (instance == null) {
			instance = new Particles();
		}

		return instance;
	}

	/**
	 * パーティクル用の画像をまとめて登録(今回はひとつしかないけど)
	 * ブロックやアイテムと異なりEntityFXはregisterIconメソッドがないのでTextureStitchEvent.Preイベントをフックして登録します
	 */
	@ForgeSubscribe
	@SideOnly(Side.CLIENT)
	public void handleTextureRemap(TextureStitchEvent.Pre event) {
		if (event.map.textureType == 1) {
			this.getInstance().registerIcons(event.map);
		}
	}

	@SideOnly(Side.CLIENT)
	public void registerIcons(IconRegister par1IconRegister) {
		icons = new Icon[iconNames.length];
		for(int i = 0; i < icons.length; ++i) {
			icons[i] = par1IconRegister.registerIcon(iconNames[i]);
		}
	}

	@SideOnly(Side.CLIENT)
	public Icon getIcon(String iconName) {
		for(int i = 0; i < iconNames.length; ++i) {
			if(iconName.equalsIgnoreCase(iconNames[i])) {
				return icons[i];
			}
		}
		return null;
	}

}

BlockChimney.java

package addparticle;

import java.util.Random;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.world.World;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

public class BlockChimney extends Block {

	public BlockChimney(int par1, Material par2Material) {
		super(par1, par2Material);
	}

	@SideOnly(Side.CLIENT)
	@Override
	public void randomDisplayTick(World par1World, int par2, int par3,
			int par4, Random par5Random) {
		if(par5Random.nextInt(10) == 0) {

			// Minecraft本体に含まれるパーティクルを発生させる場合
			// 第一引数の文字列はパーティクルの種類です。どのような種類があるかはRenderGlobalクラスのdoSpawnParticleを参照してください。
			// par1World.spawnParticle("portal", d0, d1, d2, d3, d4, d5);



			// 本MODで追加するパーティクルを発生させる場合

			// パーティクル発生地点。ブロック上面中心から半径0.8の円周上のランダムな場所
			double r = 0.8D + par5Random.nextDouble();
			double t = par5Random.nextDouble() * 2 * Math.PI;

			double d0 = par2 + 0.5D + r * Math.sin(t);
			double d1 = par3 + 1.0D + par5Random.nextDouble();
			double d2 = par4 + 0.5D + r * Math.cos(t);

			// パーティクルの移動速度。+0.03Dで上昇する
			double d3 = Math.sin(t) / 64.0D;
			double d4 = 0.03D;
			double d5 = Math.cos(t) / 64.0D;

			EntitySmokeCircleFX entityFX = new EntitySmokeCircleFX(par1World, d0, d1, d2, d3, d4, d5);

			// パーティクルの画像を設定。画像は各自で用意してください
			entityFX.func_110125_a(Particles.getInstance().getIcon("addparticle:smokecircle"));

			FMLClientHandler.instance().getClient().effectRenderer.addEffect(entityFX);

		}

	}

}

EntitySmokeCircleFX.java

package addparticle;

import net.minecraft.client.particle.EntityFX;
import net.minecraft.world.World;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;


@SideOnly(Side.CLIENT)
public class EntitySmokeCircleFX extends EntityFX {

	// 発生地点のY座標。消滅条件に利用する
	double orginalPosY;

	public EntitySmokeCircleFX(World par1World, double par2, double par4,
			double par6, double par8, double par10, double par12) {
		super(par1World, par2, par4, par6);
		this.orginalPosY = par4;
		this.motionX = par8;
		this.motionY = par10;
		this.motionZ = par12;
	}

	/*
	 * tickごとの更新。スーパークラスであるEntityFXでは放射状に拡散してparticleMaxAge経過で消滅しますが、
	 * ここでは発生地点から1m上昇したら消滅するように変更しています。
	 * 作ろうとしているパーティクルに適した動きを実装してください。
	 * 例えばポータルエフェクトのパーティクルはポータルブロックに吸い込まれるような動きを実装しています。
	 */
	@Override
	public void onUpdate() {
		this.prevPosX = this.posX;
		this.prevPosY = this.posY;
		this.prevPosZ = this.posZ;

		// 時間経過とともに大きくなる
		this.particleScale = this.particleScale + 0.1F;

		// 発生地点から1m以上上昇したら消滅する
		if (this.posY > orginalPosY + 1.0D
				&& this.particleAge++ >= this.particleMaxAge) {
			this.setDead();
		}
		this.moveEntity(this.motionX, this.motionY, this.motionZ);
	}

	@Override
	public int getFXLayer() {
		// たぶん数値が大きいほど手前に描画される?
		// 1or2でないと例外が発生するのでとりあえず2に設定
		return 2;
	}

}

補足

注意すべき点としては、EntityFXは他のEntityと異なりクライアントサイドのみに存在する点が挙げられる。(描画のみのため)

そのため、他のEntityと異なりサーバーサイドには存在せず、EntityRegistryに登録する必要もない。