ItemStackTileEntityRenderer(物品特殊渲染)

这一节中,我们将要来学习Item的特殊渲染:ItemStackTileEntityRenderer简称ISTER,它的作用和TileEntityRenderer非常类似,在以前ItemStackTileEntityRenderer甚至就是靠TileEntityRenderer实现的。

利用ISTER你可以实现一些非常酷的渲染效果,举例来说Create(机械动力)中的扳手,它会自动旋转的齿轮就是利用ISTER实现的。

首先我们先来看物品的代码

public class ObsidianWrench extends Item {
    public ObsidianWrench() {
        super(new Properties().group(ModGroup.itemGroup).setISTER(() -> ObsidianWrenchItemStackTileEntityRenderer::new));
    }
}

可以看到物品的代码其实很简单,这里唯一特别的地方就是我们调用了setISTER方法,为我们的物品绑定了一个setISTER。但是这样绑定还不能直接用,我还得替换物品原本的IBakedModel,并在其中启用ISTER

接下来我们来看替换物品IBakedModel的代码

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ModBusEventHandler {
    @SubscribeEvent
    public static void onModelBaked(ModelBakeEvent event) {
        Map<ResourceLocation, IBakedModel> modelRegistry = event.getModelRegistry();
        ModelResourceLocation location = new ModelResourceLocation(ItemRegistry.obsidianWrench.get().getRegistryName(), "inventory");
        IBakedModel existingModel = modelRegistry.get(location);
        if (existingModel == null) {
            throw new RuntimeException("Did not find Obsidian Hidden in registry");
        } else if (existingModel instanceof ObsidianWrenchBakedModel) {
            throw new RuntimeException("Tried to replaceObsidian Hidden twice");
        } else {
            ObsidianWrenchBakedModel obsidianWrenchBakedModel = new ObsidianWrenchBakedModel(existingModel);
            event.getModelRegistry().put(location, obsidianWrenchBakedModel);
        }
    }
}

可以看到,我们同样监听了ModelBakeEvent,然后通过modelRegistry.get获取了默认的IBakedModel并将它传入我们新的IBakedModel中,然后调用event.getModelRegistry().put替换了原版的IBakedModel,这里也别忘了value = Dist.CLIENT

接下来看我们的IBakedModel

public class ObsidianWrenchBakedModel implements IBakedModel {
    private IBakedModel existingModel;

    public ObsidianWrenchBakedModel(IBakedModel existingModel) {
        this.existingModel = existingModel;
    }

    @Nonnull
    @Override
    public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) {
        throw new AssertionError("IForgeBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads");
    }

    @Override
    public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, Random rand) {
        return this.existingModel.getQuads(state, side, rand);
    }

    @Override
    public boolean isAmbientOcclusion() {
        return this.existingModel.isAmbientOcclusion();
    }

    @Override
    public boolean isGui3d() {
        return this.existingModel.isGui3d();
    }

    @Override
    public boolean isSideLit() {
        return this.existingModel.isSideLit();
    }

    @Override
    public boolean isBuiltInRenderer() {
        return true;
    }

    @Override
    public TextureAtlasSprite getParticleTexture() {
        return this.existingModel.getParticleTexture();
    }

    @Override
    public ItemOverrideList getOverrides() {
        return this.existingModel.getOverrides();
    }

    @Override
    public IBakedModel handlePerspective(ItemCameraTransforms.TransformType cameraTransformType, MatrixStack mat) {
        if (cameraTransformType == ItemCameraTransforms.TransformType.FIRST_PERSON_RIGHT_HAND || cameraTransformType == ItemCameraTransforms.TransformType.FIRST_PERSON_LEFT_HAND) {
            return this;
        }
        return this.existingModel.handlePerspective(cameraTransformType, mat);
    }
}

可以看到,这里和之前创建的IBakedModel并无太大差别。同样是保存了一个原本的IBakedModel

这里我们来说说他们区别:

第一,对于物品的IBakedModel来说,只会调用IBakedModel#getQuads而不会调用IForgeBakedModel::getQuads,你可以和之前方块的IBakedModel做对比,可以发现刚好是相反的。

第二,对于物品,你可以通过 handlePerspective这个方块来选择不同TransformType下的IBakedModel,具体的TransformType请自行翻阅模型相关的Wiki,这里我们希望在第一人称的视角下用ISTER渲染我们的模型,所以在if语句中返回了this,注意只有当你这里返回了thisTransformType,才会启用ISTER渲染。

第三,你可以在getOverrides处理物品json文件中的Override,什么是Overrides请执行翻阅wiki。

第四,也是最重要的,如果你希望你的IBakedModelISTER渲染,你必须在isBuiltInRenderer返回true,如果你没有给你的物品中调用setISTER指定自定义的ISTER,默认会使用ItemStackTileEntityRenderer.instance渲染。

接下就是最为关键的部分,我们的ISTER

public class ObsidianWrenchItemStackTileEntityRenderer extends ItemStackTileEntityRenderer {
    private int degree = 0;

    @Override
    public void func_239207_a_(ItemStack stack, ItemCameraTransforms.TransformType transformType, MatrixStack matrixStack, IRenderTypeBuffer buffer, int combinedLight, int combinedOverlay) {
        if (degree == 360) {
            degree = 0;
        }
        degree++;
        ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();
        IBakedModel ibakedmodel = itemRenderer.getItemModelWithOverrides(stack, null, null);
        matrixStack.push();
        matrixStack.translate(0.5F, 0.5F, 0.5F);
        float xOffset = -1 / 32f;
        float zOffset = 0;
        matrixStack.translate(-xOffset, 0, -zOffset);
        matrixStack.rotate(Vector3f.YP.rotationDegrees(degree));
        matrixStack.translate(xOffset, 0, zOffset);
        itemRenderer.renderItem(stack, ItemCameraTransforms.TransformType.NONE, false, matrixStack, buffer, combinedLight, combinedOverlay, ibakedmodel.getBakedModel());
        matrixStack.pop();
    }
}

可以看到我们的ISTER类直接继承了ItemStackTileEntityRendererItemStackTileEntityRenderer只有一个方法需要复写,即func_239207_a_方法,这里的func_239207_a_方法和之前我们学习过的TER里的render方法作用类似。你可以在这里渲染,你想要渲染的东西。

我只在这里渲染了,我们的物品模型然后在调整完位置之后。让模型旋转起来。

float xOffset = -1 / 32f;
float zOffset = 0;
matrixStackIn.translate(-xOffset, 0, -zOffset);
matrixStackIn.rotate(Vector3f.YP.rotationDegrees(degree));
matrixStackIn.translate(xOffset, 0, zOffset);

这段就是让模型能按其中轴线旋转的代码,来源是Create Mod 的WrenchItemRenderer

因为render每一帧都会被调用一次。

if (degree == 360) {
  degree = 0;
}
degree++;

所以你可以利用这样写出平滑的旋转动画。

到此,我们的物品特殊渲染就完成了。

打开游戏看看,你应该就能看到一个会自动旋转的物品了。

源代码