魔改Minecraft Classic网页重制版的地形生成器
为了庆祝Minecraft十周年,一份用JavaScript重制的Classic 0.0.23a_01被上传到了Minecraft的官网上。虽然此版本的内容不多,但作为试验地形生成算法的原材料却十分合适(尤其是便于调试),于是我把资源文件下载到了本地,以便进行魔改。
重制版原来的JavaScript脚本把游戏本体和地形生成器分开了,正好方便修改。因为我想把地形生成得独特一点,所以选择使用Worley Noise来营造一种异世界的风格。
Worley Noise有多个变种,最简单的是直接取每个点到最近的随机生成的特征点的距离作为该点的高度,效果如下(为了直观地看出地形的高度差异,我把地表不同的高度用不同颜色的布料来填充):
通常将像素到特征点的距离称为F values, 如F1是到最近的特征点的距离,F2是到第二近的特征点的距离,以此类推。如果把F2的值作为输出,效果如下:
那如果用F2-F1呢?结果是这样的:
可以看到,生成的地形呈现出了规则的多边形形状,其实就是特征点所形成的泰森多边形。
最后还是把修改后的代码(RandomLevelWorker.js)放上来,供大家参考:
// World generation as a worker.
/**
* Creates a pseudo-random value generator. The seed must be an integer.
*
* Uses an optimized version of the Park-Miller PRNG.
* http://www.firstpr.com.au/dsp/rand31/
*/
function Random(seed) {
this._seed = seed % 2147483647;
if (this._seed <= 0) this._seed += 2147483646;
}
/**
* Returns a pseudo-random value between 1 and 2^32 - 2.
*/
Random.prototype.next = function () {
return this._seed = this._seed * 16807 % 2147483647;
};
Random.prototype.nextInt = function (max) {
return Math.floor(this.nextFloat() * max);
};
/**
* Returns a pseudo-random floating point number in range [0, 1).
*/
Random.prototype.nextFloat = function (opt_minOrMax, opt_max) {
// We know that result of next() will be 1 to 2147483646 (inclusive).
return (this.next() - 1) / 2147483646;
};
var RandomLevel = function () {
var progress = {
string: "",
percent: 0,
tiles: null
}
this.createLevel = function (seed, xSize, zSize, ySize) {
//this.progressRenderer.progressStart("Generating level");
//return;
var random = new Random(seed);
this.xSize = xSize;
this.zSize = zSize;
this.ySize = 64;
this.random = random.nextFloat();
this.tiles = [];//new Array(xSize*zSize*ySize);//[];
this.fillQueue = [];
// Bobcorn's Edition
class WorleyNoise {
constructor(config) {
console.log(config.dim);
config = config || {};
if (config.dim !== 2 && config.dim !== 3 && config.dim !== undefined)
throw '"dim" can be 2 or 3';
this._dim = config.dim || 2;
//this._rng = new Alea(config.seed || Math.random());
this._points = [];
for (let i = 0; i < config.numPoints; i++) {
console.log(i);
this._points.push({
x: this.random(),
y: this.random(),
z: this.random()
});
}
}
addPoint(coord) {
this._points.push(coord);
}
printPoints() {
console.log(this._points)
}
getEuclidean(coord, k) {
return Math.sqrt(this._calculateValue(coord, k, euclidean));
}
getManhattan(coord, k) {
return this._calculateValue(coord, k, manhattan);
}
_calculateValue(coord, k, distFn) {
let minDist;
this._points.forEach(p => { p.selected = false; });
for (let j = 0; j < k; ++j) {
let minIdx;
minDist = Number.POSITIVE_INFINITY;
for (let i = 0; i < this._points.length; ++i) {
const p = this._points[i];
const dz = this._dim === 2 ? 0 : coord.z - p.z;
const dist = distFn(coord.x - p.x, coord.y - p.y, dz);
if (dist < minDist && !p.selected) {
minDist = dist;
minIdx = i;
}
}
this._points[minIdx].selected = true;
}
return minDist;
}
}
const euclidean = (dx, dy, dz) => dx * dx + dy * dy + dz * dz;
const manhattan = (dx, dy, dz) => Math.abs(dx) + Math.abs(dy) + Math.abs(dz);
this.genWorley = function (aint) {
var i = this.xSize;
var j = this.zSize;
var k = this.ySize;
wor = new WorleyNoise({});
for (var ia = 0; ia < 50; ++ia) {
wor.addPoint({ x: Math.random(), y: Math.random(), z: 0 });
}
wor.printPoints();
for (var l = 0; l < i; ++l) { // x direction
progress.percent = l * 100 / (this.xSize - 1);
self.postMessage(progress);
for (var i1 = 0; i1 < j; ++i1) { // z direction
var j1;
//var loc = parseInt( ((j1 = parseInt(aint[l + i1 * i]) + [Y OFFSET]) * this.zSize + i1 + [Z OFFSET]) * this.xSize + l + [X OFFSET]); Get the height at (l ,i1)
//var block = (parseInt(this.tiles[((j1 + 1) * this.zSize + i1) * this.xSize + l]) & 255)
var ht = wor.getEuclidean({ x: l / this.xSize, y: i1 / this.zSize, z: 0 }, 1) * 160 + 20; //F1
//var ht = wor.getEuclidean({x: l/this.xSize,y: i1/this.zSize,z: 0}, 2) * 160 + 20; //F2
//var ht = (wor.getEuclidean({x: l/this.xSize,y: i1/this.zSize,z: 0}, 2) - wor.getEuclidean({x: l/this.xSize,y: i1/this.zSize,z: 0}, 1)) * 220 + 20; //F2-F1
ht = parseInt(Math.min(Math.max(ht, 1), 63));
//Make ground
var loc = parseInt(((j1 = parseInt(ht)) * this.zSize + i1) * this.xSize + l);
var loc1 = parseInt(((j1 = parseInt(ht) - 1) * this.zSize + i1) * this.xSize + l);
this.tiles[loc] = ht % 13 + 24;
this.tiles[loc1] = (ht - 1) % 13 + 24;
}
}
}
progress.string = "Filling Air..";
var j2 = this.xSize;
var k2 = this.zSize;
j1 = this.ySize;
var l2;
var i3;
for (l = 0; l < j2; ++l) {
progress.percent = l * 100 / (xSize - 1);
self.postMessage(progress);
for (i1 = 0; i1 < k2; ++i1) {
for (i3 = 0; i3 < j1; ++i3) {
var j3 = (i3 * zSize + i1) * xSize + l;
this.tiles[j3] = 0;
}
}
}
progress.string = "Generating Worley..";
this.genWorley();
progress.tiles = this.tiles;
progress.string = "";
self.postMessage(progress);
}
}
function startGeneration(obj) { //{worldSize: worldSize, seed: props.seed, seedrandom: seedrandom}
var level = new RandomLevel();
//console.log(level)
var width = obj.worldSize;
var depth = obj.worldSize;
var height = 64;
level.createLevel(obj.seed, width, depth, height);
}
self.addEventListener('message', function (e) {
//console.log("worker get "+e.data);
startGeneration(e.data)
}, false);
Special thanks to:
图形学—Cell/Worley Noise - 腾讯游戏学院
Zsoltc - Github
Classic remake data value - Minecraft Wiki
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!