概要
- この記事を拝見し、マイクラでも実装できるのではないかと思いました。
- GUIの実装が鬼門すぎたのでとりあえず内部的に処理することにします。
とりあえず絵を描く
処理の負荷を考えるとできるだけ小さいのが望ましいです。

こちらの画像をassets/examplemod/textures/fireworksのディレクトリ構造を作って格納しましょう。
コード
Entityクラスを作る
FireworkRocketEntityクラスを継承します。
MyArtFireworkEntity.java
package com.example.examplemod.EditHere;
import com.example.examplemod.ExampleMod;
import com.mojang.math.Vector3f;
import net.minecraft.client.Minecraft;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.network.protocol.Packet;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkHooks;
import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
public class MyArtFireworkEntity extends FireworkRocketEntity {
// コンストラクタ1: 登録用
public MyArtFireworkEntity(EntityType<? extends FireworkRocketEntity> entityType, Level level)
{
super(entityType,level);
}
// コンストラクタ2: 発射用
public MyArtFireworkEntity(Level level, double x, double y, double z, ItemStack itemStack)
{
super(level, x, y, z, itemStack);
}
// コンストラクタ3: プレイヤーが撃つ用
public MyArtFireworkEntity(Level level, ItemStack itemStack, LivingEntity shooter)
{
super(level, itemStack, shooter);
}
@Override
public @NotNull EntityType<?> getType() {
return ExampleMod.MY_ART_FIREWORK;
}
@Override
public void handleEntityEvent(byte pId) {
if(this.level.isClientSide)
{
System.out.println("DEBUG: イベント受信 ID="+pId);
}
// ID 17が花火の爆発イベント
if(pId==17 && this.level.isClientSide)
{
System.out.println("DEBUG: 爆発イベント(17)を検知!自作パーティクルを開始します。");
// バニラの爆発エフェクトは呼ばずに、
// 自作のパーティクル生成処理を呼ぶ
spawnArtParticle();
return;
}
super.handleEntityEvent(pId);
}
@OnlyIn(Dist.CLIENT)
private void spawnArtParticle(){
try{
ResourceLocation textureLocation=new ResourceLocation(ExampleMod.MODID, "textures/fireworks/star_firework.png");
if(!Minecraft.getInstance().getResourceManager().hasResource(textureLocation))
{
System.out.println("DEBUG: エラー!画像ファイルが見つかりません: "+textureLocation);
return;
}
Resource resource=Minecraft.getInstance().getResourceManager().getResource(textureLocation);
InputStream stream=resource.getInputStream();
BufferedImage image= ImageIO.read(stream);
System.out.println("画像読み込み完了!");
int width=image.getWidth();
int height=image.getHeight();
double scale=0.2D;
double startX=this.getX();
double startY=this.getY();
double startZ=this.getZ();
double yawRadians=Math.toRadians(-this.getYRot());
double cos=Math.cos(yawRadians);
double sin=Math.sin(yawRadians);
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
int color=image.getRGB(x,y);
int alpha=(color>>24)&0xff;
if(alpha==0){
continue;
}
float r=((color>>16)&0xff)/255.0F;
float g=((color>>8)&0xff)/255.0F;
float b=(color&0xff)/255.0F;
double offsetX=(x-width/2.0F)*scale;
double offsetY=(height-y-height/2.0F)*scale;
double rotatedX=offsetX*cos;
double rotatedZ=offsetX*sin;
this.level.addParticle(
new DustParticleOptions(new Vector3f(r,g,b),1.0F),
startX+rotatedX,
startY+offsetY,
startZ+rotatedZ,
0,0,0
);
}
}
stream.close();
} catch (IOException e) {
System.out.println("DEBUG: 画像読み込み中に例外が発生しました");
throw new RuntimeException(e);
}
}
@Override
public @NotNull Packet<?> getAddEntityPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
}
- colorについての補足
getRGBによって返ってくる値は32ビットの2進数の形で、透明度、赤、緑、青の順にそれぞれ8ビット(1バイト)ずつ数値が割り振られています。
つまり16進数で表せば0xAA|RR|GG|BBといった形になっています。(それぞれの文字が4ビット分の情報を持つとしています。)
ここから、論理シフト演算と論理積を用いてそれぞれの個別の情報を取り出していきます。
例えば赤の場合、右シフト演算で16桁ずらすことで0x00|00|AA|RRという形(0が1つで4ビット分としています)になり、最後に下位8ビットが全て1である16進数0xffと論理積を取ることで、RRだけが取り出されることになります。
Itemクラスを作る
MyArtFireworkItem.java
package com.example.examplemod.EditHere;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.*;
import net.minecraft.world.level.Level;
public class MyArtFireworkItem extends Item {
public MyArtFireworkItem()
{
super(new Properties().tab(CreativeModeTab.TAB_MISC));
}
@Override
public InteractionResultHolder<ItemStack> use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) {
if(!pLevel.isClientSide)
{
MyArtFireworkEntity firework=new MyArtFireworkEntity(pLevel, pPlayer.getX(), pPlayer.getEyeY()-0.1F, pPlayer.getZ(), pPlayer.getItemInHand(pUsedHand));
firework.setOwner(pPlayer);
firework.setDeltaMovement(0.0F, 0.5F, 0.0F);
firework.setYRot(pPlayer.getYRot());
pLevel.addFreshEntity(firework);
}
if(!pPlayer.getAbilities().instabuild)
{
pPlayer.getItemInHand(pUsedHand).shrink(1);
}
pPlayer.awardStat(Stats.ITEM_USED.get(this));
return InteractionResultHolder.sidedSuccess(pPlayer.getItemInHand(pUsedHand), pLevel.isClientSide());
}
}
ExampleModで登録
アイテムとエンティティのインスタンス
ExampleMod.java
public static final Item MY_ART_FIREWORK_ITEM=new MyArtFireworkItem().setRegistryName(MODID, "my_art_firework_item");
public static final EntityType<MyArtFireworkEntity> MY_ART_FIREWORK=EntityType.Builder
.<MyArtFireworkEntity>of(MyArtFireworkEntity::new, MobCategory.MISC)
.sized(0.25F, 0.25F)
.setTrackingRange(64)
.updateInterval(1)
.setShouldReceiveVelocityUpdates(true)
.build("my_art_firework");
エンティティの登録
ExampleMod.java
@SubscribeEvent
public static void onEntitiesRegistry(final RegistryEvent.Register<EntityType<?>> event) {
event.getRegistry().register(MY_ART_FIREWORK.setRegistryName(MODID,"my_art_firework"));
}
アイテムの登録
ExampleMod.java
private static final Item[] registerItems = {
MY_ART_FIREWORK_ITEM
};
レンダラーの登録(既存のものを使う)
ExampleMod.java
private void doClientStuff(final FMLClientSetupEvent event) {
EntityRenderers.register(MY_ART_FIREWORK, FireworkEntityRenderer::new);
}
Dispenserの登録
これをやっておかないとディスペンサーに入れた時に打ち上がりません。
ExampleMod.java
private void doClientStuff(final FMLClientSetupEvent event) {
DispenserBlock.registerBehavior(MY_ART_FIREWORK_ITEM, new DefaultDispenseItemBehavior(){
@Override
protected ItemStack execute(BlockSource pSource, ItemStack pStack) {
Direction direction=pSource.getBlockState().getValue(DispenserBlock.FACING);
double x=pSource.x()+(double) direction.getStepX()*0.7D;
double y= pSource.y()+(double) direction.getStepY()*0.7D;
double z=pSource.z()+(double) direction.getStepZ()*0.7D;
MyArtFireworkEntity entity=new MyArtFireworkEntity(pSource.getLevel(), x, y, z, pStack);
entity.shoot(direction.getStepX(), direction.getStepY(), direction.getStepZ(), 0.5F, 1.0F);
pSource.getLevel().addFreshEntity(entity);
pStack.shrink(1);
return pStack;
}
});
}