跳到主要内容

创建一个新的维度

注意

本教程涉及到的 Minecraft 版本已经有些老旧,不再适用于主流的版本。

在本期教程中,我们将创建一个新的维度 (Dimension)

我们在包 coolclk.tutorial.world ,创建几个类并像这样输入代码:

/src/main/java/coolclk/tutorial/world/DimensionType.java
package coolclk.tutorial.world;

import static net.minecraft.world.DimensionType.register;

public class DimensionType {
public static final net.minecraft.world.DimensionType TUTORIAL;

static {
TUTORIAL = register("tutorial", "_tutorial", 2, WorldProviderTurotial.class, false);
}
}
/src/main/java/coolclk/tutorial/world/WorldProviderTutorial.java

package coolclk.tutorial.world;

import coolclk.tutorial.world.DimensionType;
import net.minecraft.world.WorldProvider;

public class WorldProviderTutorial extends WorldProvider {
@Override
public DimensionType getDimensionType() {
return DimensionType.TUTORIAL;
}
}

有很多可选项,我列出了一部分(没有 Forge 提供的方法):

方法作用
protected void generateLightBrightnessTable()生成各级光照强度的实际渲染亮度
protected void init()初始化
public void createChunkGenerator()创建一个 net.minecraft.world.gen.IChunkGenerator (用于生成世界)
public boolean canCoordinateBeSpawn(int chunkX, int chunkZ)检查出生点是否可用
public boolean calculateCelestialAngle(long worldTime, float partialTicks)通过世界时间计算太阳角度
public int getMoonPhase(long worldTime)获取月相 (最终反月食程度的值是 net.minecraft.world.WorldProvider.MOON_PHASE_FACTORS[this.getMoonPhase(worldTime)]
public boolean isSurfaceWorld()是否为非地狱/末地的世界
public float[] calcSunriseSunsetColors(float celestialAngle, float partialTicks)计算日出/日落颜色 (返回值其实是 RGB)
public Vec3d getFogColor(float celestialAngle, float partialTicks)计算雾的颜色 (返回值其实是 RGB)
public boolean canRespawnHere()玩家是否能在这里重生
public float getCloudHeight()决定云的高度
public boolean isSkyColored()天空是否有颜色
public net.minecraft.util.math.BlockPos getSpawnCoordinate()获取一个出生点
public int getAverageGroundLevel()决定了你认为地面平均的高度 (用作生成村庄一类结构)
public double getVoidFogYFactor()决定虚空雾的密度(越高的密集值,从天空调入虚空周围变成虚空黑更远但变化更快)
public boolean doesXZShowFog(int x, int z)决定某个地方是否应该有雾
public net.minecraft.world.biome.BiomeProvider getBiomeProvider()返回 net.minecraft.world.biome.BiomeProvider
public boolean doesWaterVaporize()决定水是否蒸发
public boolean hasSkyLight()决定天空是否有亮度
public boolean isNether()返回这个世界是否为地狱(或拥有地狱的特征
public float[] getLightBrightnessTable()protected void generateLightBrightnessTable() 不同,此处是获取已生成的亮度表
public net.minecraft.world.border.WorldBorder createWorldBorder()创建一个 net.minecraft.world.border.WorldBorder

为了让这个世界能够加载区块,你要添加 public net.minecraft.world.gen.IChunkGenerator createChunkGenerator()V 方法,给它返回你创建的新实例。

不过,你还没创建。

在此包下创建 gen.ChunkGeneratorTutorial ,默认是这样的:

/src/main/java/coolclk/tutorial/world/gen/ChunkGeneratorTutorial.java
package coolclk.tutorial.world.gen;

import net.minecraft.entity.EnumCreatureType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.gen.IChunkGenerator;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;

public class ChunkGeneratorTutorial implements IChunkGenerator {
@Nonnull
@Override
public Chunk generateChunk(int chunkX, int chunkZ) {
return null;
}

@Override
public void populate(int chunkX, int chunkZ) {

}

@Override
public boolean generateStructures(@Nonnull Chunk chunkIn, int chunkX, int chunkZ) {
return false;
}

@Nonnull
@Override
public List<Biome.SpawnListEntry> getPossibleCreatures(@Nonnull EnumCreatureType creatureType, @Nonnull BlockPos position) {
return Collections.emptyList();
}

@Nullable
@Override
public BlockPos getNearestStructurePos(@Nonnull World worldIn, @Nonnull String structureName, @Nonnull BlockPos position, boolean findUnexplored) {
return null;
}

@Override
public void recreateStructures(@Nonnull Chunk chunkIn, int chunkX, int chunkZ) {

}

@Override
public boolean isInsideStructure(@Nonnull World worldIn, @Nonnull String structureName, @Nonnull BlockPos pos) {
return false;
}
}

我们主要操作的是 generateChunk(int, int)populate(int, int) ,其余的大多是可选的。

这是一个简单生成拥有矿洞、村庄、要塞、水湖、岩浆湖、矿物的超平坦的例子(省略 import 等):

/src/main/java/coolclk/tutorial/world/gen/ChunkGeneratorTutorial.java
private final Random randomizer;
private final World world;
private final boolean mapFeaturesEnabled;
private final ChunkGeneratorSettings settings;
private final MapGenCaves caveGenerator;
private final MapGenVillage villageGenerator;
private final MapGenStrongholdPortalRoom strongholdGenerator;

public ChunkGeneratorTutorial(World world) {
this.world = world;
this.mapFeaturesEnabled = world.getWorldInfo().isMapFeaturesEnabled();
this.randomizer = new Random(world.getSeed());
this.caveGenerator = new MapGenCaves();
this.villageGenerator = new MapGenVillage();
this.strongholdGenerator = new MapGenStrongholdPortalRoom();

this.settings = ChunkGeneratorSettings.Factory.jsonToFactory(world.getWorldInfo().getGeneratorOptions()).build();
this.world.setSeaLevel(this.settings.seaLevel);
}

private void generateFlat(int chunkX, int chunkZ, ChunkPrimer chunkPrimer) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
IBlockState fillBlock = Blocks.STONE.getDefaultState();
for (int y = 0; y <= 60; y++) {
if (y == 60) fillBlock = Blocks.GRASS.getDefaultState();
chunkPrimer.setBlockState(x, y, z, fillBlock);
fillBlock = y >= 55 ? Blocks.DIRT.getDefaultState() : Blocks.STONE.getDefaultState();
}
}
}
}

@Nonnull
@Override
public Chunk generateChunk(int chunkX, int chunkZ) {
this.randomizer.setSeed((long) chunkX * 0x4f9939f508L + (long) chunkX * 0x1ef1565bd5L); // 我也不知道为什么这样,经典老番,notch 是这样写的

ChunkPrimer chunkPrimer = new ChunkPrimer();

this.generateFlat(chunkX, chunkZ, chunkPrimer);

if (this.settings.useCaves) { // 使你的世界更灵活,因而这是可选的 if,下面也差不多
this.caveGenerator.generate(this.world, chunkX, chunkZ, chunkPrimer);
}

if (this.mapFeaturesEnabled) {
if (this.settings.useVillages) {
this.villageGenerator.generate(this.world, chunkX, chunkZ, chunkPrimer);
}
if (this.settings.useStrongholds) {
this.strongholdGenerator.generate(this.world, chunkX, chunkZ, chunkPrimer);
}
}

Chunk chunk = new Chunk(this.world, chunkPrimer, chunkX, chunkZ);
chunk.generateSkylightMap(); // 给区块加载一波光照,要不然黑漆漆
return chunk;
}

@Override
public void populate(int chunkX, int chunkZ) {
BlockSand.fallInstantly = true;

int chunkWorldX = chunkX * 16;
int chunkWorldZ = chunkZ * 16;
BlockPos chunkWorldPosition = new BlockPos(chunkWorldX, this.getBaseHeight(chunkX, chunkZ), chunkWorldZ);
Biome biome = world.getBiome(chunkWorldPosition.add(16, this.getBaseHeight(chunkX, chunkZ), 16));
randomizer.setSeed(world.getSeed());
long xTemperature = (randomizer.nextLong() / 2L) * 2L + 1L;
long zTemperature = (randomizer.nextLong() / 2L) * 2L + 1L;
randomizer.setSeed((long) chunkX * xTemperature + (long) chunkZ * zTemperature ^ world.getSeed());
boolean hasVillage = false;
ChunkPos chunkPosition = new ChunkPos(chunkWorldX, chunkWorldZ);

if (this.mapFeaturesEnabled) {
if (this.settings.useVillages) {
hasVillage = this.villageGenerator.generateStructure(this.world, this.randomizer, chunkPosition);
}
if (this.settings.useStrongholds) {
this.strongholdGenerator.generateStructure(this.world, this.randomizer, chunkPosition);
}
}

if (biome != Biomes.DESERT && biome != Biomes.DESERT_HILLS && this.settings.useWaterLakes && !hasVillage && randomizer.nextInt(this.settings.waterLakeChance) == 0) {
int x = this.randomizer.nextInt(16) + 8,
y = this.randomizer.nextInt(256),
z = this.randomizer.nextInt(16) + 8;
(new WorldGenLakes(Blocks.WATER)).generate(world, randomizer, chunkWorldPosition.add(x, y, z));
}
if (!hasVillage && this.randomizer.nextInt(this.settings.lavaLakeChance / 10) == 0 && this.settings.useLavaLakes) {
int x = this.randomizer.nextInt(16) + 8,
y = this.randomizer.nextInt(this.randomizer.nextInt(240) + 8),
z = this.randomizer.nextInt(16) + 8;
if (y < this.world.getSeaLevel() || this.randomizer.nextInt(this.settings.lavaLakeChance / 8) == 0) {
(new WorldGenLakes(Blocks.LAVA)).generate(this.world, this.randomizer, chunkWorldPosition.add(x, y, z));
}
}
if (this.settings.useDungeons) {
for (int _times = 0; _times < this.settings.dungeonChance; _times++) {
int x = this.randomizer.nextInt(16) + 8,
y = this.randomizer.nextInt(256),
z = this.randomizer.nextInt(16) + 8;
(new WorldGenDungeons()).generate(this.world, this.randomizer, chunkWorldPosition.add(x, y, z));
}
}

biome.decorate(this.world, this.randomizer, new BlockPos(chunkWorldX, 0, chunkWorldZ));
WorldEntitySpawner.performWorldGenSpawning(this.world, biome, chunkWorldX + 8, chunkWorldZ + 8, 16, 16, this.randomizer);

for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
BlockPos precipitationHeight = this.world.getPrecipitationHeight(chunkWorldPosition.add(x, 0, z));
if (this.world.canBlockFreezeWater(precipitationHeight.down())) {
this.world.setBlockState(precipitationHeight.down(), Blocks.ICE.getDefaultState(), 2);
}
if (this.world.canSnowAt(precipitationHeight, true)) {
this.world.setBlockState(precipitationHeight, Blocks.SNOW_LAYER.getDefaultState(), 2);
}
}
}

BlockSand.fallInstantly = false;
}

@Override
public boolean generateStructures(@Nonnull Chunk chunkIn, int chunkX, int chunkZ) {
return false;
}

@Nonnull
@Override
public List<Biome.SpawnListEntry> getPossibleCreatures(@Nonnull EnumCreatureType creatureType, @Nonnull BlockPos position) {
return this.world.getBiome(blockPos).getSpawnableList(creatureType);
}

@Nullable
@Override
public BlockPos getNearestStructurePos(@Nonnull World worldIn, @Nonnull String structureName, @Nonnull BlockPos position, boolean findUnexplored) {
if (this.mapFeaturesEnabled) {
if (this.villageGenerator != null && "Village".getStructureName().equals(structureName)) {
return this.villageGenerator.getNearestStructurePos(world, blockPos, findUnexplored);
}
if (this.strongholdGenerator != null && "StrongHold".getStructureName().equals(structureName)) {
return this.strongholdGenerator.getNearestStructurePos(world, blockPos, findUnexplored);
}
}
return null;
}

@Override
public void recreateStructures(@Nonnull Chunk chunkIn, int chunkX, int chunkZ) {
if (this.mapFeaturesEnabled) {
if (this.settings.useStrongholds) {
this.strongholdGenerator.generate(this.world, chunkX, chunkZ, null);
}
if (this.settings.useVillages) {
this.villageGenerator.generate(this.world, chunkX, chunkZ, null);
}
}
}

@Override
public boolean isInsideStructure(@Nonnull World worldIn, @Nonnull String structureName, @Nonnull BlockPos position) {
if (this.mapFeaturesEnabled) {
if (this.villageGenerator != null && "Village".getStructureName().equals(structureName)) {
return this.villageGenerator.isInsideStructure(position);
}
if (this.strongholdGenerator != null && "StrongHold".getStructureName().equals(structureName)) {
return this.strongholdGenerator.isInsideStructure(position);
}
}
return false;
}

之后,为了进入到这个维度,我这里先用指令实现进入我的维度,若你也想这样,在你的 Forge 入口,像我这样注册简易指令:

/src/main/java/coolclk/tutorial/TutorialMod.java
package coolclk.tutorial;

import coolclk.tutorial.world.DimensionType;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.Teleporter;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;

@Mod(modid = "tutorial")
public class TutorialMod implements IObject {
@Mod.EventHandler
public static void onServerStarting(FMLServerStartingEvent event) {
List<ICommand> commands = Collections.singletonList(new CommandBase() {
@Nonnull
@Override
public String getName() {
return "tp-tutorial";
}

@Nonnull
@Override
public String getUsage(@Nonnull ICommandSender sender) {
return new TextComponentTranslation("command.tutorial.tp-tutorial.usage").getFormattedText();
}

@Override
public void execute(@Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args) {
EntityPlayer player = null;
if (args.length > 0) {
player = server.getPlayerList().getPlayerByUsername(args[0]);
} else if (sender instanceof EntityPlayer) {
player = (EntityPlayer) sender;
}
if (player != null) {
if (player.dimension != DimensionType.TUTORIAL.getId()) {
player.changeDimension(DimensionType.SKY.getId(), new Teleporter(event.getServer().getWorld(DimensionType.TUTORIAL.getId())));
}
}
}
});
commands.forEach(command -> {
if (event != null) {
event.registerServerCommand(command);
return;
}
ClientCommandHandler.instance.registerCommand(command);
});
}
}

大功告成!尽情享受吧。