|
|
1
Blindman67
3 年前
##绘制大平铺集
如果您有一个大的平铺集,并且只在画布中看到它的一部分,那么您只需要计算画布左上角的平铺,以及适合画布的横下平铺数。
然后绘制适合画布的方形瓷砖阵列。
worldTileCount = 1024
),每个图块为64 x 64像素
tileSize = 64
,使整个操场65536像素为正方形
左上角平铺的位置由变量设置
worldX
,
worldY
###用于绘制平铺的函数
var worldX = 512 * tileSize;
var worldY = 512 * tileSize;
function drawWorld(){
const c = worldTileCount;
const s = tileSize;
const tx = worldX / s | 0;
const ty = worldY / s | 0;
const tW = (canvas.width / s | 0) + 2;
const tH = (canvas.height / s | 0) + 2;
ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);
for(var y = 0; y < tH; y += 1){
for(var x = 0; x < tW; x += 1){
const i = tx + x + (ty + y) * c;
const tindx = tileMap[i] === undefined ? 6 : tileMap[i];
imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
}
}
}
###setTransform和绝对坐标。
使用画布上下文
setTransform
设置世界位置,然后可以在其自己的坐标处绘制每个瓷砖。
// set the world location. The | 0 floors the values and ensures no holes
ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);
这样,如果你在51023、34256位置有一个角色,你可以在那个位置画它。
playerX = 51023;
playerY = 34256;
ctx.drawImage(myPlayerImage,playerX,playerY);
如果您想要相对于玩家的平铺贴图,则只需将世界位置设置为画布大小的一半,并向左加一个平铺,以确保重叠
playerX = 51023;
playerY = 34256;
worldX = playerX - canvas.width / 2 - tileWidth;
worldY = playerY - canvas.height / 2 - tileHeight;
###演示大65536×65536像素的瓷砖地图。
如果你有马匹,可以以60fps的速度处理更大的数据,而不会损失任何帧速率。(使用此方法的地图大小限制约为4000000000x 4000000000像素(32位整数坐标))
#2019年5月15日更新
抖动
评论指出,有一些
抖动
当地图滚动时。
我做了一些改变,以平滑随机路径,每240帧(60fps时4秒)进行一次轻松的输入输出,还添加了一个帧速率降低器,如果在画布上单击并按住鼠标按钮,帧速率将降低到正常的1/8,以便
抖动
更容易看到。
这有两个原因
抖动
###时间误差
第一个也是最短的一个是通过
requestAnimationFrame
,间隔不是完美的,时间导致的舍入误差加剧了对齐问题。
为了减少时间误差,我将移动速度设置为恒定间隔,以最小化帧之间的舍入误差漂移。
###将瓷砖与像素对齐
主要原因是
抖动
瓷砖必须在像素边界上渲染。否则,锯齿错误将在瓷砖之间创建可见接缝。
要获得平滑滚动(子像素定位),请将地图绘制到与像素对齐的屏幕外画布,然后将该画布渲染到显示画布,添加子像素偏移。这将提供使用画布的最佳结果。为了更好,您需要使用webGL
###更新结束
var refereshSkip = false;
var dontAlignToPixel = false;
var ctx = canvas.getContext("2d");
function mouseEvent(e) {
if(e.type === "click") {
dontAlignToPixel = !dontAlignToPixel;
pixAlignInfo.textContent = dontAlignToPixel ? "Pixel Align is OFF" : "Pixel Align is ON";
} else {
refereshSkip = e.type === "mousedown";
}
}
pixAlignInfo.addEventListener("click",mouseEvent);
canvas.addEventListener("mousedown",mouseEvent);
canvas.addEventListener("mouseup",mouseEvent);
setTimeout(() => {
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
const worldTileCount = 1024;
const tileMap = new Uint8Array(worldTileCount * worldTileCount);
doFor(worldTileCount * worldTileCount, i => {
tileMap[i] = randI(1, tileCount);
});
var worldDir = Math.PI / 4;
var worldX = 512 * tileSize;
var worldY = 512 * tileSize;
function drawWorld() {
const c = worldTileCount;
const s = tileSize;
const tx = worldX / s | 0;
const ty = worldY / s | 0;
const tW = (canvas.width / s | 0) + 2;
const tH = (canvas.height / s | 0) + 2;
if(dontAlignToPixel) {
ctx.setTransform(1, 0, 0, 1, -worldX,-worldY);
} else {
ctx.setTransform(1, 0, 0, 1, Math.floor(-worldX),Math.floor(-worldY));
}
for (var y = 0; y < tH; y += 1) {
for (var x = 0; x < tW; x += 1) {
const i = tx + x + (ty + y) * c;
const tindx = tileMap[i] === undefined ? 6 : tileMap[i];
imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
}
}
}
var timer = 0;
var refreshFrames = 0;
const dirChangeMax = 3.5;
const framesBetweenDirChange = 240;
var dirChangeDelay = 1;
var dirChange = 0;
var prevDir = worldDir;
const eCurve = (v, p = 2) => v < 0 ? 0 : v > 1 ? 1 : v ** p / (v ** p + (1 - v) ** p);
function update() {
refreshFrames ++;
if(!refereshSkip || (refereshSkip && refreshFrames % 8 === 0)){
timer += 1000 / 60;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
var speed = Math.sin(timer / 10000) * 8;
worldX += Math.cos(worldDir) * speed;
worldY += Math.sin(worldDir) * speed;
if(dirChangeDelay-- <= 0) {
dirChangeDelay = framesBetweenDirChange;
prevDir = worldDir = prevDir + dirChange;
dirChange = rand(-dirChangeMax , dirChangeMax);
}
worldDir = prevDir + (1-eCurve(dirChangeDelay / framesBetweenDirChange,3)) * dirChange;
// Draw the map
drawWorld();
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
}, 0);
/*===========================================================================
CODE FROM HERE DOWN UNRELATED TO THE ANSWER
===========================================================================*/
const imageTools = (function() {
// This interface is as is. No warenties no garenties, and NOT to be used comercialy
var workImg, workImg1, keep; // for internal use
keep = false;
var tools = {
canvas(width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage: function(width, height) {
var i = this.canvas(width, height);
i.ctx = i.getContext("2d");
return i;
},
drawSpriteQuick: function(image, spriteIndex, x, y) {
var w, h, spr;
spr = image.sprites[spriteIndex];
w = spr.w;
h = spr.h;
ctx.drawImage(image, spr.x, spr.y, w, h, x, y, w, h);
},
line(x1, y1, x2, y2) {
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
},
circle(x, y, r) {
ctx.moveTo(x + r, y);
ctx.arc(x, y, r, 0, Math.PI * 2);
},
};
return tools;
})();
const doFor = (count, cb) => {
var i = 0;
while (i < count && cb(i++) !== true);
}; // the ; after while loop is important don't remove
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const seededRandom = (() => {
var seed = 1;
return {
max: 2576436549074795,
reseed(s) {
seed = s
},
random() {
return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max
}
}
})();
const randSeed = (seed) => seededRandom.reseed(seed | 0);
const randSI = (min, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const tileSize = 64;
const tileCount = 7;
function drawGrass(ctx, c1, c2, c3) {
const s = tileSize;
const gs = s / (8 * c3);
ctx.fillStyle = c1;
ctx.fillRect(0, 0, s, s);
ctx.strokeStyle = c2;
ctx.lineWidth = 2;
ctx.lineCap = "round";
ctx.beginPath();
doFor(s, i => {
const x = rand(-gs, s + gs);
const y = rand(-gs, s + gs);
const x1 = rand(x - gs, x + gs);
const y1 = rand(y - gs, y + gs);
imageTools.line(x, y, x1, y1);
imageTools.line(x + s, y, x1 + s, y1);
imageTools.line(x - s, y, x1 - s, y1);
imageTools.line(x, y + s, x1, y1 + s);
imageTools.line(x, y - s, x1, y1 - s);
})
ctx.stroke();
}
function drawTree(ctx, c1, c2, c3) {
const seed = Date.now();
const s = tileSize;
const gs = s / 2;
const gh = gs / 2;
ctx.fillStyle = c1;
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 8;
randSeed(seed);
ctx.beginPath();
doFor(18, i => {
const ss = 1 - i / 18;
imageTools.circle(randS(gs - gh * ss, gs + gh * ss), randS(gs - gh * ss, gs + gh * ss), randS(gh / 4, gh / 2));
})
ctx.stroke();
ctx.fill();
ctx.restore();
ctx.fillStyle = c2;
ctx.strokeStyle = c3;
ctx.lineWidth = 2;
ctx.save();
randSeed(seed);
ctx.beginPath();
doFor(18, i => {
const ss = 1 - i / 18;
imageTools.circle(randS(gs - gh * ss, gs + gh * ss) - 2, randS(gs - gh * ss, gs + gh * ss) - 2, randS(gh / 4, gh / 2) / 1.6);
})
ctx.stroke();
ctx.fill();
ctx.restore();
}
const tileRenders = [
(ctx) => {
drawGrass(ctx, "#4C4", "#4F4", 1)
},
(ctx) => {
drawGrass(ctx, "#644", "#844", 2)
},
(ctx) => {
tileRenders[0](ctx);
drawTree(ctx, "#480", "#8E0", "#7C0")
},
(ctx) => {
tileRenders[1](ctx);
drawTree(ctx, "#680", "#AE0", "#8C0")
},
(ctx) => {
drawGrass(ctx, "#008", "#00A", 4)
},
(ctx) => {
drawGrass(ctx, "#009", "#00C", 4)
},
(ctx) => {
drawGrass(ctx, "#00B", "#00D", 4)
},
]
const tileSet = imageTools.createImage(tileSize * tileCount, tileSize);
const ctxMain = ctx;
ctx = tileSet.ctx;
tileSet.sprites = [];
doFor(tileCount, i => {
x = i * tileSize;
ctx.save();
ctx.setTransform(1, 0, 0, 1, x, 0);
ctx.beginPath();
ctx.rect(0, 0, tileSize, tileSize);
ctx.clip()
if (tileRenders[i]) {
tileRenders[i](ctx)
}
tileSet.sprites.push({
x,
y: 0,
w: tileSize,
h: tileSize
});
ctx.restore();
});
ctx = ctxMain;
canvas {
position: absolute;
top: 0px;
left: 0px;
}
div {
position: absolute;
top: 8px;
left: 8px;
color: white;
}
#pixAlignInfo {
color: yellow;
cursor: pointer;
border: 2px solid green;
margin: 4px;
}
#pixAlignInfo:hover {
color: white;
background: #0008;
cursor: pointer;
}
body {
background: #49c;
}
<canvas id="canvas"></canvas>
<div>Hold left button to slow to 1/8th<br>
<span id="pixAlignInfo">Click this button to toggle pixel alignment. Alignment is ON</span></div>
|