我使用了我的先前的积木游戏并添加了一些性能改进,并评论了一些需要注释的内容,以便进行更好的解释。
/** Here are few instructions before you start...
* IMPORTANT (ON KHANACADEMY ONLY): When you make changes to the code, all tiles will be black.
* Just hit the restart button to fix that.
*
* CONTROLS:
* MOUSE - Move around the current selected tile.
* LEFT MOUSE BUTTON - Add a tile to the screen.
* RIGHT MOUSE BUTTON - Delete a tile from the screen.
* CONTROL KEY - Change the block type forward.
* SHIFT KEY - Change the block type backward.
* WASD or LEFT RIGHT DOWN UP - Move around the map.
*/
/* Global program constants */
var CURSOR_FONT = createFont("monospace", 15);
var WHITE = color(255, 255, 255);
var SUN_WIDTH = 100;
var SUN_HEIGHT = 100;
var TILE_SIZE = 10;
var COLORS = [
color(180, 120, 20),
color(20, 150, 20),
color(100, 100, 100),
color(240, 200, 10),
color(5, 44, 117),
color(255, 255, 255),
color(110, 70, 10),
color(10, 210, 20),
];
var TILE_TYPES = [
"Dirt",
"Grass",
"Stone",
"Sand",
"Water",
"Snow",
"Wood",
"Leaves",
];
/* Colors for the sun and moon */
var skyObjectColor1 = color(194, 181, 33);
var skyObjectColor2 = color(255, 140, 0);
/* Variables concerning map movement */
var movingUp = false;
var movingDown = false;
var movingLeft = false;
var movingRight = false;
/* Variables that control the darkness of the sky */
var starVisibilityChange = 0;
var starVisibility = 15;
var skyDarknessChange = 0.25;
var skyDarkness = 125;
/* Array containing cloud data */
var cloudArray = [];
/* Current selected color */
var selectedColor = 0;
/* Array contaning all tile data */
var tileArray = [];
/* Array containing all star data */
var starArray = [];
/* Variables controlling the sun and moon's position */
var skyObjectYChange = -0.05;
var SKY_OBJ_X_POS = 189;
var skyObjectYPos = 200;
/* Populate the starArray */
var generateStars = function() {
for(var d = 0; d <= round(random(100, 150)); d++) {
starArray.push({
xPos: random(0, 400),
yPos: random(0, 400),
});
}
};
/* Draw stars in the sky */
var drawStars = function() {
for(var s = starArray.length-1; s >= 0; s--) {
var star = starArray[s];
noStroke();
fill(255, 255, 255, starVisibility);
rect(star.xPos, star.yPos, 2, 2);
}
};
/* Render the day and night cycle*/
var renderDayNightCycle = function() {
if(skyObjectYPos >= 250) {
starVisibilityChange = 1;
skyObjectColor1 = color(122, 117, 117);
skyObjectColor2 = color(71, 68, 68);
drawStars();
}
if(skyObjectYPos < 250) {
starVisibilityChange = 0;
skyObjectColor1 = color(194, 181, 33);
skyObjectColor2 = color(255, 140, 0);
}
noStroke();
fill(skyObjectColor2);
rect(SKY_OBJ_X_POS, skyObjectYPos, 45, 45);
fill(skyObjectColor1);
rect(SKY_OBJ_X_POS+5, skyObjectYPos+5, 35, 35);
skyObjectYPos += skyObjectYChange;
skyDarkness += skyDarknessChange;
starVisibility += starVisibilityChange;
if(skyObjectYPos <= -50) {
skyObjectYChange = 0.05;
skyDarknessChange = -0.05;
}
if(skyObjectYPos >= 450) {
skyObjectYChange = -0.05;
skyDarknessChange = 0.05;
starVisibilityChange = -2.5;
}
};
/* Generate new terrain for the world */
var generateTerrain = function() {
var c = random(1, 3);
/* Variable concerning the current Y position of the tile generator */
var blockY = 300;
/* Overarching for loop, this loop determines world size */
for(var x = -2500; x <= 2500; x += TILE_SIZE) {
/* Create part of the dirt section, with grass on top*/
var rb = round(random(-1, 0));
tileArray.push({
xPos: x,
yPos: blockY+TILE_SIZE*rb,
colr: COLORS[1],
});
if(rb === -1) {
tileArray.push({
xPos: x,
yPos: blockY,
colr: COLORS[0],
});
}
/* If this statement is true, then place a tree down */
if(random() >= random()*random()/random()+random()) {
var treeHeight = round(random(2, 6));
/* Create the wood section of the tree */
for(var h = blockY-TILE_SIZE; h >= blockY-TILE_SIZE*treeHeight; h -= TILE_SIZE) {
tileArray.push({
xPos: x,
yPos: h,
colr: COLORS[6],
});
}
/* Create the leaf top of the tree */
for(var i = 0; i <= 1; i++) {
tileArray.push({
xPos: x,
yPos: blockY-TILE_SIZE*(treeHeight+i)-TILE_SIZE,
colr: COLORS[7],
});
}
/* Add two extra leaf blocks on the sides for a more realistic tree */
tileArray.push({
xPos: x+TILE_SIZE,
yPos: blockY-TILE_SIZE*treeHeight-TILE_SIZE,
colr: COLORS[7]
});
tileArray.push({
xPos: x-TILE_SIZE,
yPos: blockY-TILE_SIZE*treeHeight-TILE_SIZE,
colr: COLORS[7]
});
}
/* Create the last part of the dirt section */
for(var y = blockY; y <= blockY+TILE_SIZE*round(random(2, 4)); y += TILE_SIZE) {
tileArray.push({
xPos: x,
yPos: y+TILE_SIZE,
colr: COLORS[0],
});
}
/* Create the stone section */
for(var y = blockY+TILE_SIZE*round(c); y <= blockY+TILE_SIZE*random(18, 22); y += TILE_SIZE) {
tileArray.push({
xPos: x,
yPos: y+TILE_SIZE,
colr: COLORS[2],
});
}
/* Change the blockY by a random, but rounded amount */
blockY += (ceil(random(-1, 1)/TILE_SIZE)*TILE_SIZE)*round(random(-2, 2));
}
};
/* Initalize the array of clouds */
var generateClouds = function() {
for(var i = 0; i <= round(random(2, 12)); i++) {
cloudArray.push({
xPos: random(50, 350),
yPos: random(50, 150),
w: random(30, 60),
h: random(10, 20),
});
}
};
/* Draw the background */
var drawBackground = function() {
for(var c = cloudArray.length-1; c >= 0; c--) {
var cloud = cloudArray[c];
noStroke();
fill(WHITE);
rect(cloud.xPos, cloud.yPos, cloud.w, cloud.h);
cloud.xPos += random(0.01, 0.09);
if(cloud.xPos >= 400) {
cloud.xPos = 0-cloud.w;
}
}
};
/* Draw a hitbox over the selected position */
var drawHitbox = function(x, y) {
noCursor();
fill(WHITE);
textFont(CURSOR_FONT);
text("+", mouseX-1, mouseY+4);
noFill();
strokeWeight(1);
stroke(WHITE);
rect(x, y, TILE_SIZE, TILE_SIZE);
};
/* Add a block to the screen */
var addBlock = function(x, y) {
drawHitbox(x, y);
if(mouseIsPressed && mouseButton === LEFT) {
if(x >= 20 && y >= 20) {
tileArray.push({
xPos: x,
yPos: y,
colr: COLORS[selectedColor],
});
}
}
};
/* Delete a block from the screen */
var deleteBlock = function(x, y) {
drawHitbox(x, y);
if(mouseIsPressed && mouseButton === RIGHT) {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
if(x === tile.xPos && y === tile.yPos) {
tileArray.splice(t, 1);
}
}
}
};
/* Render the tileArray */
var renderTiles = function() {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
/* Check if tile is off screen, if so, don't render */
if(tile.xPos >= 0 && tile.xPos <= 400 && tile.yPos >= 0 && tile.yPos <= 400) {
noStroke();
fill(tile.colr);
rect(tile.xPos, tile.yPos, TILE_SIZE, TILE_SIZE);
}
}
};
/* Check for specific key actions */
var checkForKeyActions = function() {
keyPressed = function() {
if(keyCode === LEFT || keyCode === 65) {
movingLeft = true;
}
if(keyCode === RIGHT || keyCode === 68) {
movingRight = true;
}
if(keyCode === UP || keyCode === 87) {
movingUp = true;
}
if(keyCode === DOWN || keyCode === 83) {
movingDown = true;
}
if(keyCode === CONTROL) {
selectedColor++;
if(selectedColor >= COLORS.length) {
selectedColor = 0;
}
}
if(keyCode === SHIFT) {
selectedColor--;
if(selectedColor < 0) {
selectedColor = COLORS.length-1;
}
}
};
/* If key released, change keypress variable to false */
keyReleased = function() {
if(keyCode === LEFT || keyCode === 65) {
movingLeft = false;
}
if(keyCode === RIGHT || keyCode === 68) {
movingRight = false;
}
if(keyCode === UP || keyCode === 87) {
movingUp = false;
}
if(keyCode === DOWN || keyCode === 83) {
movingDown = false;
}
};
/* Move up */
if(movingUp) {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
tile.yPos += TILE_SIZE;
}
}
/* Move down */
if(movingDown) {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
tile.yPos -= TILE_SIZE;
}
}
/* Move left */
if(movingLeft) {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
tile.xPos += TILE_SIZE;
}
}
/* Move right */
if(movingRight) {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
tile.xPos -= TILE_SIZE;
}
}
};
/* Draw the current selected tile */
var drawSelectedTile = function() {
strokeWeight(1.5);
stroke(255, 255, 255);
fill(COLORS[selectedColor]);
rect(5, 5, 15, 15);
fill(255, 255, 255);
textFont(CURSOR_FONT);
text(TILE_TYPES[selectedColor], 25, 17.4);
};
/* Load the world, clouds and stars before the draw loop begins */
generateTerrain();
generateClouds();
generateStars();
/* Main draw loop */
draw = function() {
background(0, 0, skyDarkness);
renderDayNightCycle();
drawBackground();
renderTiles();
drawSelectedTile();
checkForKeyActions();
/* Check if the user wants to add a block */
addBlock(ceil(mouseX/TILE_SIZE)*TILE_SIZE-TILE_SIZE,
ceil(mouseY/TILE_SIZE)*TILE_SIZE-TILE_SIZE);
/* Check if the user wants to delete a block */
deleteBlock(ceil(mouseX/TILE_SIZE)*TILE_SIZE-TILE_SIZE,
ceil(mouseY/TILE_SIZE)*TILE_SIZE-TILE_SIZE);
/* Draw a hitbox over the current selected block */
drawHitbox(ceil(mouseX/TILE_SIZE)*TILE_SIZE-TILE_SIZE,
ceil(mouseY/TILE_SIZE)*TILE_SIZE-TILE_SIZE);
};
我主要要找的是如何改进让玩家在世界各地移动的代码。目前,它所做的是遍历包含世界数据的整个数组,并改变每个块的位置。这是不好的,特别是当我想让世界在某一时刻无限。因此,任何解决这一问题的建议或解决方案都是值得欢迎的。谢谢!
发布于 2015-01-28 20:09:16
您的代码风格很好,但首先,我想谈谈代码的两个主要逻辑问题:随机性和相机控制。
你已经提到过,你有一种愿望,在某一时刻使世界变得无限。好吧,如果你想这么做,你需要重新设计你现在处理随机数生成的方式。
为了使世界无限,你必须能够懒洋洋地产生世界的大块。当需要一个新块时,它需要按需生成。实际的块生成和管理本身就是一个完整的主题,但我希望您能够解决这个问题。我只想概述一下您需要做的更改,以使这种机制成为可能。
您的generateTerrain
函数非常大。它为tileArray
提供了游戏所需的所有数据。您将需要将其更改为类似于generateChunk
的内容,它将用新的瓷砖填充chunkArray
。然而,目前,这并不容易,因为你是如何生成随机数。
/* Change the blockY by a random, but rounded amount */
blockY += (ceil(random(-1, 1)/TILE_SIZE)*TILE_SIZE)*round(random(-2, 2));
在这里,您正在使用random
,并且以增量的方式调整迭代的位置。这是有问题的,原因有几个。首先,random
是完全不确定的。没有办法为这种世界留一个“种子”。您可以实现某种可以在块边界上保存状态的系统,但这不仅会造成问题,而且会不必要地困难。
幸运的是,处理提供了一个noise
函数。与noiseSeed
和noiseDetail
一起,您可以为任意给定的x/y坐标创建确定性的连续随机数!这显然是有益的,有几个原因。
blockY
这样的功能,但这有点麻烦。使用noise
,每个单独的块可以通过种子和x/y对的组合来唯一定义--不需要状态。显然,使用noise
是正确的方法。但是,生成像树和其他特征这样的东西呢?这也可以通过noise
来完成,但是在一个块一个块的基础上使用一个不同的调整噪声函数。您还可以将random
与randomSeed
结合使用,种子本身可能是噪声的一个函数!这允许块生成具有确定性,同时能够利用对生成器的特定控制的方便。(请记住,这将产生不连续的数据,因此只能用于生成本地化的地图功能。)
所以这是随机数的产生。接下来是更简单的改变。
周围移动
这就是你目前处理移动的方法。
/* Move up */
if(movingUp) {
for(var t = tileArray.length-1; t >= 0; t--) {
var tile = tileArray[t];
tile.yPos += TILE_SIZE;
}
}
这是个非常可怕的主意。瓷砖的位置永远不应该改变。事实上,它们甚至不需要与瓷砖一起存储-它们应该是它们在平铺数组中的位置的函数(或者最终是块数组)。
不,数据本身不应该移动。相反,你应该有一个“相机”在世界各地移动,改变在视口看到的东西。最简单的方法是使用处理的translate
函数。这将导致所有绘图操作在屏幕上被抵消。只需调用translate
,执行绘图,然后再次调用translate
以撤消转换。
这将改变renderTiles
和checkForKeyActions
的工作方式。与其四处移动瓦片,不如保留cameraX
和cameraY
变量(或camera.x
/camera.y
)。在checkForKeyActions
中修改它们,然后将它们传递给renderTiles
中的translate
。这意味着不再循环通过所有的瓷砖只是为了移动他们,它也意味着你可以连续移动,而不仅仅是在一个网格上!
即使实现了这一点,如果你使世界变得无限,你显然不能把它全部画出来,因为这将花费无限的时间。相反,您需要计算与视口相交的块,并绘制这些块。如果应该绘制一个块,但它还不存在,那么就应该立即生成它了。
除了上面提到的那些要点(当然是主要的)之外,您的代码也相当可靠。以下是其他一些随机的建议。
var COLORS = [
color(180, 120, 20),
color(20, 150, 20),
color(100, 100, 100),
color(240, 200, 10),
color(5, 44, 117),
color(255, 255, 255),
color(110, 70, 10),
color(10, 210, 20),
];
var TILE_TYPES = [
"Dirt",
"Grass",
"Stone",
"Sand",
"Water",
"Snow",
"Wood",
"Leaves",
];
为什么不把这些封装在一起呢?如果TILE_TYPES
是一个包含这两部分信息的结构,就更有意义了。例如,您可以拥有tile.name
和tile.color
。这将允许您以相对轻松的方式向tiles类型添加更多信息。
var generateStars = function() {
for(var d = 0; d <= round(random(100, 150)); d++) {
starArray.push({
xPos: random(0, 400),
yPos: random(0, 400),
});
}
};
虽然这可能不是什么大不了的事情,但是您可以重写这个函数来使用noise
来使它更加清晰。
https://codereview.stackexchange.com/questions/73876
复制相似问题