 霰弹枪进阶:从单发到连发与附魔系统)
1. 连发模式实现原理与代码实战在基础霰弹枪实现中每次射击都需要重新点击右键。要实现连发功能我们需要改造射击逻辑。核心思路是通过Item#onUpdate方法持续检测射击状态配合NBT标签记录射击间隔。首先在ItemM1897类中添加连发控制字段// 连发模式开关 private boolean isBurstMode false; // 连发间隔单位tick20tick1秒 private int burstInterval 4; // 连发次数 private int burstCount 3;接着修改右键点击事件处理public ActionResultItemStack onItemRightClick(World world, EntityPlayer player, EnumHand hand) { ItemStack stack player.getHeldItem(hand); // 潜行时切换连发模式 if(player.isSneaking()) { isBurstMode !isBurstMode; String mode isBurstMode ? 连发 : 单发; player.sendMessage(new TextComponentString(切换为mode模式)); return new ActionResult(EnumActionResult.SUCCESS, stack); } // 正常射击逻辑 if(!world.isRemote) { if(isBurstMode) { // 标记开始连发 stack.getOrCreateSubCompound(burst).setInteger(shotsRemaining, burstCount); } else { fireShot(world, player, stack); } } return new ActionResult(EnumActionResult.SUCCESS, stack); }然后添加每tick检测的更新方法public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isSelected) { if(!isSelected || !(entity instanceof EntityPlayer)) return; NBTTagCompound burstTag stack.getSubCompound(burst); if(burstTag ! null burstTag.hasKey(shotsRemaining)) { int remaining burstTag.getInteger(shotsRemaining); int lastShotTick burstTag.getInteger(lastShotTick); if(remaining 0 world.getTotalWorldTime() - lastShotTick burstInterval) { fireShot(world, (EntityPlayer)entity, stack); burstTag.setInteger(lastShotTick, (int)world.getTotalWorldTime()); burstTag.setInteger(shotsRemaining, remaining - 1); } else if(remaining 0) { stack.removeSubCompound(burst); } } }实测中我发现连发后坐力需要调整可以在fireShot方法中添加水平偏移private void fireShot(World world, EntityPlayer player, ItemStack stack) { // 原有射击代码... float spread isBurstMode ? 0.8f : 0.3f; // 连发时增大散布 entity1.shoot(player, player.rotationPitch, player.rotationYaw, 0.0F, velocity, spread * 0.5f); entity2.shoot(player, player.rotationPitch, player.rotationYaw, 0.0F, velocity, spread * 0.8f); // 其余子弹实体... }2. 弹药装填系统开发原版实现中直接从背包消耗弹药不够真实我们可以添加装弹机制。首先在ItemM1897类中添加容量字段// 弹仓容量 private int magazineSize 6; // 装弹时间tick private int reloadTime 40;然后创建装弹状态跟踪方法public void startReloading(EntityPlayer player, ItemStack stack) { if(!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); } stack.getTagCompound().setInteger(reloading, reloadTime); player.setActiveHand(EnumHand.MAIN_HAND); } public boolean isReloading(ItemStack stack) { return stack.hasTagCompound() stack.getTagCompound().hasKey(reloading); }修改右键点击逻辑在弹药不足时触发装弹public ActionResultItemStack onItemRightClick(World world, EntityPlayer player, EnumHand hand) { ItemStack stack player.getHeldItem(hand); if(isReloading(stack)) { return new ActionResult(EnumActionResult.PASS, stack); } int ammo getCurrentAmmo(stack); if(ammo 0) { startReloading(player, stack); return new ActionResult(EnumActionResult.SUCCESS, stack); } // 原有射击逻辑... }添加每tick更新装弹进度的方法public void onUsingTick(ItemStack stack, EntityLivingBase player, int count) { if(!isReloading(stack)) return; int remaining stack.getTagCompound().getInteger(reloading) - 1; if(remaining 0) { finishReloading(stack, (EntityPlayer)player); } else { stack.getTagCompound().setInteger(reloading, remaining); } }装弹完成的处理逻辑private void finishReloading(ItemStack stack, EntityPlayer player) { stack.getTagCompound().removeTag(reloading); int needed magazineSize - getCurrentAmmo(stack); int found 0; // 从背包查找弹药 for(int i 0; i player.inventory.getSizeInventory() found needed; i) { ItemStack slotStack player.inventory.getStackInSlot(i); if(slotStack.getItem() instanceof ItemM1897B) { int take Math.min(slotStack.getCount(), needed - found); slotStack.shrink(take); found take; } } setCurrentAmmo(stack, getCurrentAmmo(stack) found); player.playSound(SoundEvents.ITEM_ARMOR_EQUIP_IRON, 1.0F, 1.0F); }3. 附魔系统深度集成Minecraft原版附魔系统可以通过EnchantmentHelper类访问。我们为霰弹枪设计几个专属附魔3.1 多重射击附魔实现创建自定义附魔类public class EnchantmentMultishot extends Enchantment { public EnchantmentMultishot() { super(Rarity.RARE, EnumEnchantmentType.WEAPON, new EntityEquipmentSlot[]{EntityEquipmentSlot.MAINHAND}); this.setName(multishot); this.setRegistryName(multishot); } public int getMaxLevel() { return 3; } public boolean canApply(ItemStack stack) { return stack.getItem() instanceof ItemM1897; } }在射击逻辑中应用效果private void fireShot(World world, EntityPlayer player, ItemStack stack) { int multishotLevel EnchantmentHelper.getEnchantmentLevel(MyEnchantments.MULTISHOT, stack); int pelletCount 4 multishotLevel * 2; // 基础4发每级2 for(int i 0; i pelletCount; i) { EntityM1897B pellet new EntityM1897B(world, player); // 设置弹道参数... world.spawnEntity(pellet); } }3.2 穿透附魔效果穿透附魔允许子弹穿过多个实体public class EnchantmentPenetration extends Enchantment { // 构造方法类似多重射击... } // 在子弹实体类中修改碰撞检测 protected void onImpact(RayTraceResult result) { int penetration EnchantmentHelper.getEnchantmentLevel(MyEnchantments.PENETRATION, this.shootingItem); if(result.entityHit ! null) { result.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, this.getThrower()), damage); if(penetration 0 this.penetrated penetration) { this.penetrated; return; // 不销毁子弹 } } this.setDead(); }3.3 火焰附加适配直接复用原版火焰附加附魔if(EnchantmentHelper.getEnchantmentLevel(Enchantments.FLAME, stack) 0) { for(EntityM1897B pellet : pellets) { pellet.setFire(100); } }4. 动画与音效增强4.1 装弹动画实现使用ModelBiped和ClientEventHandler实现第一人称动画SideOnly(Side.CLIENT) SubscribeEvent public void onRenderPlayer(RenderPlayerEvent.Pre event) { EntityPlayer player event.getEntityPlayer(); ItemStack stack player.getHeldItemMainhand(); if(stack.getItem() instanceof ItemM1897 ((ItemM1897)stack.getItem()).isReloading(stack)) { ModelBiped model event.getRenderer().getMainModel(); float progress 1f - ((ItemM1897)stack.getItem()).getReloadProgress(stack); // 右手装弹动作 model.bipedRightArm.rotateAngleX -0.8f * progress; model.bipedRightArm.rotateAngleY 0.4f * progress; } }4.2 定制音效系统添加多种音效增强真实感public class ModSounds { public static SoundEvent SHOTGUN_PUMP; public static SoundEvent SHOTGUN_RELOAD; public static SoundEvent SHOTGUN_DRYFIRE; static { SHOTGUN_PUMP registerSound(item.shotgun.pump); SHOTGUN_RELOAD registerSound(item.shotgun.reload); SHOTGUN_DRYFIRE registerSound(item.shotgun.dryfire); } private static SoundEvent registerSound(String name) { ResourceLocation loc new ResourceLocation(MODID, name); return SoundEvent.REGISTRY.getObject(loc); } }在适当位置触发音效// 射击后播放上膛音效 if(!world.isRemote) { world.playSound(null, player.posX, player.posY, player.posZ, ModSounds.SHOTGUN_PUMP, SoundCategory.PLAYERS, 1.0F, 1.0F); } // 空仓击发音效 if(getCurrentAmmo(stack) 0) { player.playSound(ModSounds.SHOTGUN_DRYFIRE, 1.0F, 1.0F); return new ActionResult(EnumActionResult.FAIL, stack); }4.3 粒子效果增强添加枪口火焰和弹壳抛射效果SideOnly(Side.CLIENT) private void spawnMuzzleFlash(World world, Vec3d pos, Vec3d direction) { for(int i 0; i 5; i) { world.spawnParticle(EnumParticleTypes.FLAME, pos.x, pos.y, pos.z, direction.x * 0.02 * world.rand.nextGaussian(), direction.y * 0.02 * world.rand.nextGaussian(), direction.z * 0.02 * world.rand.nextGaussian()); } // 弹壳抛射 world.spawnParticle(EnumParticleTypes.ITEM_CRACK, pos.x, pos.y, pos.z, -direction.x * 0.1 world.rand.nextGaussian() * 0.05, 0.2 world.rand.nextGaussian() * 0.05, -direction.z * 0.1 world.rand.nextGaussian() * 0.05, Item.getIdFromItem(ModItems.SHELL_CASING)); }