Minecraft 1.16.5模组开发(五十四) 方块探测器(Detector)

我们本次预计实现一个方块探测器,让其可以探测我们想要找到的方块。

1.我们希望将方块放下后,可以探测以其坐标为中心的16×16×16的范围内是否具有目标方块:

cr1.jpg

新建一个方块类BlockBFS,为了方便枚举区域内的方块状态,我们可以采取一种宽度优先搜索(3维BFS)的算法来对区域内的所有方块进行探测:

BlockBFS.java

package com.joy187.fanfu.common.blocks;

import net.minecraft.block.*;
import net.minecraft.block.material.Material;
import net.minecraft.particles.RedstoneParticleData;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.Blockreader;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.ToolType;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

import static java.lang.Math.abs;
import static net.minecraft.block.BedBlock.getConnectedDirection;

public class BlockBFS extends RedstoneDiodeBlock {
    //3维偏移量
    int[]dx = {1,-1,0,0,0,0};
    int[]dy = {0,0,1,-1,0,0};
    int[]dz = {0,0,0,0,1,-1};

    protected static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D);
    
    //我们的方块有两个属性,一个是朝向,一个是是否充能
    public BlockBFS(AbstractBlock.Properties properties) {
        super(properties);
        this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(POWERED, Boolean.valueOf(false)));
    }

    public VoxelShape getCollisionShape(BlockState p_220071_1_, IBlockReader p_220071_2_, BlockPos p_220071_3_, ISelectionContext p_220071_4_) {
        return SHAPE;
    }

    public VoxelShape getBlockSupportShape(BlockState p_230335_1_, IBlockReader p_230335_2_, BlockPos p_230335_3_) {
        return VoxelShapes.block();
    }

    public VoxelShape getVisualShape(BlockState p_230322_1_, IBlockReader p_230322_2_, BlockPos p_230322_3_, ISelectionContext p_230322_4_) {
        return VoxelShapes.block();
    }

    //BFS函数
    private boolean checkBlock(World level, BlockPos pos, BlockState state) {
        boolean[][][] st = new boolean[16][16][16];
        for(int i=0;i<16;i++)
            for(int j=0;j<16;j++)
                for(int k=0;k<16;k++)
                    st[i][j][k]=false;


        boolean flag=false;
        //用一个队列,将方块探测器的坐标加入到队列中并将其坐标进行标记(由于mc坐标可能存在负数,我们要对坐标进行取模运算)
        Queue<BlockPos> q = new LinkedList();
        q.offer(pos);
        st[(pos.getX()-pos.getX()+8)%16][(pos.getY()-pos.getY()+8)%16][(pos.getZ()-pos.getZ()+8)%16]=true;
        while(q.size()!=0)
        {
            BlockPos blockP=q.poll();
            BlockState s=level.getBlockState(blockP);
            //如果当前我们找到了该目标方块,就跳出bfs循环
            if(s.getBlock()==Blocks.GRASS_BLOCK){
                flag=true;
                break;
            }
            for(int i=0;i<6;i++)
            {
                for(int j=0;j<6;j++)
                {
                    for(int z=0;z<6;z++)
                    {
                        int xx=blockP.getX()+dx[i];
                        int yy=blockP.getY()+dy[i];
                        int zz=blockP.getZ()+dz[i];
                        if(zz>0 && zz<256 && st[(xx-pos.getX()+8)%16][(yy-pos.getY()+8)%16][(zz-pos.getZ()+8)%16]==false && abs(pos.getX()-xx)<9 && abs(pos.getY()-yy)<9 && abs(pos.getZ()-zz)<9)
                        {
                            q.offer(new BlockPos(xx,yy,zz));
                            st[(xx-pos.getX()+8)%16][(yy-pos.getY()+8)%16][(zz-pos.getZ()+8)%16]=true;
                        }
                    }
                }
            }
        }
        //System.out.println();
        return flag;
    }

    //探测器是否检测到目标方块
    @OnlyIn(Dist.CLIENT)
    public void animateTick(BlockState p_180655_1_, World p_180655_2_, BlockPos p_180655_3_, Random p_180655_4_) {
        //如果方块没有激活就不断对区域内的方块进行检测
        if(!p_180655_1_.getValue(POWERED))
        {
            boolean flag = this.checkBlock(p_180655_2_, p_180655_3_, p_180655_1_);
            //如果找到了,就将探测器充能发光
            if(flag)
                p_180655_2_.setBlock(p_180655_3_, p_180655_1_.setValue(POWERED, Boolean.valueOf(true)), 2);
        }
        else{
            boolean flag = this.checkBlock(p_180655_2_, p_180655_3_, p_180655_1_);
            if(!flag)
                p_180655_2_.setBlock(p_180655_3_, p_180655_1_.setValue(POWERED, Boolean.valueOf(false)), 2);
        }
    }

    public void neighborChanged(BlockState p_220069_1_, World p_220069_2_, BlockPos p_220069_3_, Block p_220069_4_, BlockPos p_220069_5_, boolean p_220069_6_) {

    }

    @Override
    protected int getDelay(BlockState p_196346_1_) {
        return 1;
    }

    protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> p_206840_1_) {
        p_206840_1_.add(FACING,POWERED);
    }

    public int getSignal(BlockState p_180656_1_, IBlockReader p_180656_2_, BlockPos p_180656_3_, Direction p_180656_4_) {
        return p_180656_1_.getValue(POWERED) ? 15 : 0;
    }

    public boolean isSignalSource(BlockState p_149744_1_) {
        return true;
    }
    private void updateNeighbours(BlockState p_196378_1_, World p_196378_2_, BlockPos p_196378_3_) {
        p_196378_2_.updateNeighborsAt(p_196378_3_, this);
        p_196378_2_.updateNeighborsAt(p_196378_3_.relative(getConnectedDirection(p_196378_1_).getOpposite()), this);
    }

    public void onRemove(BlockState p_196243_1_, World p_196243_2_, BlockPos p_196243_3_, BlockState p_196243_4_, boolean p_196243_5_) {
        if (!p_196243_5_ && !p_196243_1_.is(p_196243_4_.getBlock())) {
            if (p_196243_1_.getValue(POWERED)) {
                this.updateNeighbours(p_196243_1_, p_196243_2_, p_196243_3_);
            }

            super.onRemove(p_196243_1_, p_196243_2_, p_196243_3_, p_196243_4_, p_196243_5_);
        }
    }
}

2.在ModBlocks类中注册我们的探测器方块:

ModBlocks.java

    public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, Utils.MOD_ID);

    //添加这个
    public static RegistryObject<Block> BFS_BLOCK = BLOCKS.register("bfs_block",()->
            new BlockBFS(AbstractBlock.Properties.copy(Blocks.IRON_BLOCK).harvestTool(ToolType.PICKAXE).harvestLevel(1).requiresCorrectToolForDrops()));

3.代码部分结束,来到资源包制作环节

resources\assets\你的modid\blockstates下新建bfs_block.json文件,枚举方块状态:

bfs_block.json

{
  "variants": {
    "facing=east,powered=false": {
      "model": "fanfu:block/bfs_block",
      "y": 90
    },
    "facing=east,powered=true": {
      "model": "fanfu:block/bfs_block_on",
      "y": 90
    },
    "facing=north,powered=false": {
      "model": "fanfu:block/bfs_block"
    },
    "facing=north,powered=true": {
      "model": "fanfu:block/bfs_block_on"
    },
    "facing=south,powered=false": {
      "model": "fanfu:block/bfs_block",
      "y": 180
    },
    "facing=south,powered=true": {
      "model": "fanfu:block/bfs_block_on",
      "y": 180
    },
    "facing=west,powered=false": {
      "model": "fanfu:block/bfs_block",
      "y": 270
    },
    "facing=west,powered=true": {
      "model": "fanfu:block/bfs_block_on",
      "y": 270
    }
  }
}
models\block中新建bfs_block.jsonbfs_block_on.json,分别表示充能和未充能的方块模型:
未充能时模型

bfs_block.json

{
  "parent": "block/orientable",
  "textures": {
    "top": "fanfu:blocks/virus_generator_side",
    "front": "fanfu:blocks/virus_generator",
    "side": "fanfu:blocks/virus_generator_side"
  }
}
充能时模型

bfs_block_on.json

{
  "parent": "block/orientable",
  "textures": {
    "top": "fanfu:blocks/virus_generator_side",
    "front": "fanfu:blocks/virus_generator_on",
    "side": "fanfu:blocks/virus_generator_side"
  }
}
models\item中新建玩家手持探测器的模型文件:

bfs_block.json

{
  "parent": "fanfu:block/bfs_block"
}
textures\blocks中添加探测器的材质:

cr2.jpg

在语言包lang\en_us.json中添加方块英文名称:
"block.fanfu.bfs_block":"Detector",

zh_cn.json中添加中文名称:

"block.fanfu.bfs_block":"探测器",
进入数据包,在resources\data\fanfu\loot_tables\blocks路径下新建bfs_block.json,编写方块破坏后的掉落物:

bfs_block.json

{
	"type": "minecraft:block",
	"pools": [
		{
			"rolls": 1,
			"bonus_rolls": 0,
			"entries": [
				{
					"type": "minecraft:item",
					"name": "fanfu:bfs_block"
				}
			],
			"conditions": [
				{
					"condition": "minecraft:match_tool"
				}
			]
		}
	]
}

4.保存所有代码 -> 运行游戏测试

我们在距离草方块8格和9格高的地方放置探测器

tan.jpg

可以看到,8格高的探测器成功探测到了草方块并且显示出充能状态!
<think>好的,我现在需要帮助用户解决在Minecraft 1.16.5模组开发中,如何在放置方块时直接在方块类中添加声音功能的问题。用户之前的问题是关于使用Forge事件系统来实现这个功能,现在他希望直接在方块代码中实现,可能想简化流程或者更紧密地与方块属性结合。 首先,回顾之前给出的答案,用户已经了解通过事件监听和网络包发送来实现声音播放。但这次他希望直接在方块类中处理,可能需要覆盖方块的放置方法或者利用方块的属性来触发声音。 我需要考虑Forge在1.16.5版本中方块放置的相关方法。通常,当方块被放置时,可以覆盖`onBlockPlacedBy`方法,这个方法在方块被放置后被调用。在这里直接播放声音可能更直接,但需要注意客户端和服务器的区别,因为声音应该在客户端播放,而服务器不处理声音。 另一个可能性是使用方块的`SoundType`属性,这个属性决定了方块被放置、破坏等时的默认声音。用户之前提到过通过覆盖SoundType来修改原版音效,但这次可能需要自定义SoundType来使用特定的声音资源。 需要确认如何注册自定义的SoundType,并在方块初始化时应用它。例如,在自定义方块的构造函数中设置Properties的sound方法,传入自定义的SoundType实例。这样,当方块被放置时,会自动播放对应的声音,无需额外的事件监听或网络包。 同时,用户可能希望声音只在特定条件下触发,或者使用不同于默认放置音效的声音。这时候可能需要结合覆盖`onBlockPlacedBy`方法,在客户端侧播放自定义声音。需要注意的是,服务器端不能直接播放声音,因此必须确保代码只在客户端执行,使用`world.isRemote`进行检查。 此外,需要指导用户如何正确注册声音资源,包括在sounds.json中添加条目,并提供对应的音频文件。这一点在之前的回答中已经涉及,但需要再次强调以确保正确性。 还需要考虑兼容性和性能问题,例如避免服务器端执行客户端代码,或者声音播放的时机是否正确。例如,在`onBlockPlacedBy`中调用`world.playEvent`方法,可能会触发原版的事件处理,进而播放声音,但需要确认参数是否正确。 总结步骤应该是:创建自定义方块时,在Properties中设置SoundType,或者在放置时覆盖方法,在客户端播放声音。需要分情况讨论这两种方法的适用场景,以及如何实现它们。 可能的问题点:用户可能会混淆客户端和服务器的执行环境,导致声音无法播放或者出现错误。因此,在代码示例中必须明确检查`world.isRemote`,并确保声音在客户端播放。 最后,生成相关问题时要围绕方块类的自定义、声音触发机制、资源注册等方向,帮助用户深入理解相关知识点。</think>在Minecraft 1.16.5模组开发中,直接在方块类中实现放置时触发声音的功能,可通过以下两种方案实现: --- ### 一、通过`SoundType`实现原生音效(推荐) #### 1. 自定义方块音效类型 在方块类中定义时,通过`Properties`设置自定义$SoundType$[^1],此方法直接复用Minecraft原生的音效触发机制: ```java public class CustomBlock extends Block { // 自定义音效类型(需注册声音资源) private static final SoundType CUSTOM_SOUND = new SoundType( 1.0F, // 音量 1.0F, // 音调 SoundEvents.BLOCK_ANVIL_BREAK, // 破坏音效 SoundEvents.BLOCK_GLASS_STEP, // 行走音效 SoundEvents.BLOCK_AMETHYST_BLOCK_PLACE, // 放置音效 SoundEvents.BLOCK_ANVIL_HIT, // 敲击音效 SoundEvents.BLOCK_GLASS_FALL // 掉落音效 ); public CustomBlock() { super(Properties.create(Material.IRON) .sound(CUSTOM_SOUND) // 绑定音效类型 .hardnessAndResistance(2.0f) ); } } ``` #### 2. 注册声音资源 在`resources/assets/<modid>/sounds.json`中注册音效路径: ```json { "block.custom.place": { "sounds": ["mymod:block/custom_place"], "subtitle": "subtitles.mymod.block.place" } } ``` --- ### 二、通过覆盖`onBlockPlacedBy`方法 #### 1.方块类中直接触发声音 覆盖方块的`onBlockPlacedBy`方法,在客户端执行声音播放: ```java public class CustomBlock extends Block { public CustomBlock() { super(Properties.create(Material.ROCK)); } @Override public void onBlockPlacedBy(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // 仅在客户端触发 if (world.isRemote) { world.playSound( pos.getX(), pos.getY(), pos.getZ(), ForgeRegistries.SOUND_EVENTS.getValue(new ResourceLocation("mymod:block.custom.place")), SoundCategory.BLOCKS, 1.0F, 1.0F, false ); } super.onBlockPlacedBy(world, pos, state, placer, stack); } } ``` #### 2. 参数说明 - `world.isRemote`:确保仅客户端执行 - `SoundCategory.BLOCKS`:声音分类(影响音量控制) - 坐标参数:使用方块位置`pos`获取精确播放位置 --- ### 三、两种方案的对比 | 方案 | 优点 | 局限性 | |---------------------|-------------------------------|---------------------------| | `SoundType`方案 | 自动关联所有方块交互音效 | 需预定义全部音效类型 | | `onBlockPlacedBy`方案| 可精确控制触发时机和条件 | 需手动处理客户端/服务端逻辑 | --- ### 四、常见问题排查 1. **声音未播放** - 检查`sounds.json`路径格式是否正确(`assets/<modid>/sounds.json`) - 确认音频文件已放入`assets/<modid>/sounds/block/`目录 - 验证是否调用了`world.isRemote`过滤服务端执行 2. **音效延迟或重复** - 避免在服务端调用`world.playSound` - 使用`DistExecutor`确保客户端执行: ```java DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { // 播放声音的代码 }); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jay_fearless

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值