问题
I wish to create a simple webgame that involves a tiled board. I have a collection of svg's for the background of each square (i.e one for grass, one for stone, one for dirt etc). I also have svg's for items that will be displayed on the layer above the background (such as trees, wood, sword).
I have an in memory database of what the background for each square is and if and which item it contains.
I wish to be able to:
* Zoom in or out
* Scroll left or right
* Scolll up or down
* Have items displayed above the background for that square
Only needs to work in recent versions of modern browsers
What is the best approach for this:
1. Have a canvas object. Get the current zoom, top most XY, canvas width and canvas height. Loop though the squares in the in memory database and print the corresponding SVG's in the correct locations. Each time it is scrolled or zoomed reprint the entire board.
2. Have a div. Get the current zoom, top most XY, canvas width and canvas height. Loop though the squares in the in memory database and create SVG's in the correct locations.
Each time the board scrolls add new SVGs as they become visible, delete SVGs as they move of the board. Translate all the existing SVGs by the appropriate amount.
Each time the board zooms enlarge or shrink all the existing SVGs based on the new zoom level.
3. Some third approach that I am unaware of.
回答1:
The example below uses two modules svg to load images (Any image format will work) & board which handles panning, zooming & rendering. It also provides an onClick event which will give you an object that describes the tile that has been clicked on.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
display: block;
margin: 30px auto 0px auto;
border: solid 1px white;
border-radius: 10px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
var svg = function() {
"use strict";
var svgImages = [];
var areImagesLoaded = true;
var isPageLoaded = false;
addEventListener("load",function() { isPageLoaded = true; isFinished(); });
var exports = {
onload: function() {},
request: null,
get: null
};
function isFinished() {
if (isPageLoaded && areImagesLoaded) {
exports.onload();
}
}
function onSvgError() {
this.isDone = true;
console.warn("SVG " + this.src + " failed to load.");
}
function onSvgLoaded() {
this.isDone = true;
for (var id in svgImages) {
if (!svgImages[id].isDone) {
return;
}
}
areImagesLoaded = true;
isFinished();
}
function request(id,path) {
if (svgImages[id]) {
return;
}
areImagesLoaded = false;
var img = document.createElement("img");
img.onerror = onSvgError;
img.onload = onSvgLoaded;
img.isDone = false;
img.id = id;
img.src = path;
svgImages[id] = img;
}
function get(id) {
return svgImages[id];
}
exports.request = request;
exports.get = get;
return exports;
}();
var board = function() {
"use strict";
var canvasWidth = 0;
var canvasHeight = 0;
var canvas = null;
var ctx = null;
var frameRequested = false;
var tileWidth = 0;
var tileHeight = 0;
var tileTypes = [];
var boardWidth = 0;
var boardHeight = 0;
var board = [];
var hasInitialized = false;
var camera = {
x: 0.0,
y: 0.0,
zoom: 1.0
};
function mapToBoard(x,y) {
var invZoom = 1.0 / camera.zoom;
return [
(x - (canvasWidth >> 1)) * invZoom - camera.x,
(y - (canvasHeight >> 1)) * invZoom - camera.y
];
}
var isMouseDragging = false;
var mouseStartX = 0;
var mouseStartY = 0;
var mouseLastX = 0;
var mouseLastY = 0;
var tileEvent = {
background: "",
foreground: "",
x: 0,
y: 0
};
function onTileSelected(e) {
}
function onMouseDown(e) {
isMouseDragging = true;
var bounds = canvas.getBoundingClientRect();
mouseStartX = mouseLastX = e.clientX - bounds.left;
mouseStartY = mouseLastY = e.clientY - bounds.top;
}
function onMouseUp(e) {
isMouseDragging = false;
var bounds = canvas.getBoundingClientRect()
var x = e.clientX - bounds.left - mouseStartX;
var y = e.clientY - bounds.top - mouseStartY;
var l = Math.sqrt(x * x + y * y);
if (l < 2.0) {
[x,y] = mapToBoard(e.clientX - bounds.left,e.clientY - bounds.top);
if (x > 0 && y > 0 && x < boardWidth * tileWidth && y < boardHeight * tileHeight) {
x = (x / tileWidth) | 0;
y = (y / tileHeight) | 0;
var tile = board[x + y * boardWidth];
tileEvent.background = tile.background;
tileEvent.foreground = tile.foreground;
tileEvent.x = x;
tileEvent.y = y;
} else {
tileEvent.background = "";
tileEvent.foreground = "";
tileEvent.x = -1;
tileEvent.y = -1;
}
onTileSelected(tileEvent);
}
}
function onMouseMove(e) {
if (hasInitialized && isMouseDragging) {
var bounds = canvas.getBoundingClientRect();
var x = e.clientX - bounds.left;
var y = e.clientY - bounds.top;
camera.x += (x - mouseLastX) / camera.zoom;
camera.y += (y - mouseLastY) / camera.zoom;
mouseLastX = x;
mouseLastY = y;
requestDraw();
}
}
function onWheel(e) {
if (Math.sign(e.deltaY) === -1) {
camera.zoom *= 1.1;
} else {
camera.zoom *= 0.9;
}
requestDraw();
}
function draw() {
ctx.fillStyle = "gray";
ctx.fillRect(-canvasWidth >> 1,-canvasHeight >> 1,canvasWidth,canvasHeight);
var _tileWidth = tileWidth * camera.zoom;
var _tileHeight = tileHeight * camera.zoom;
var _ox = camera.x * camera.zoom;
var _oy = camera.y * camera.zoom;
var _x = _ox;
var _y = _oy;
for (var x = 0; x <boardWidth; ++x) {
for (var y = 0; y < boardHeight; ++y) {
var index = x + y * boardWidth;
var tile = board[index];
var background = tileTypes[tile.background];
var foreground = tileTypes[tile.foreground];
if (background) {
ctx.drawImage(
background,
_x,
_y,
_tileWidth,
_tileHeight
);
}
if (foreground) {
ctx.drawImage(
foreground,
_x,
_y,
_tileWidth,
_tileHeight
);
}
_y += _tileHeight;
}
_y = _oy;
_x += _tileWidth;
}
frameRequested = false;
}
function requestDraw() {
if (!frameRequested) {
frameRequested = true;
requestAnimationFrame(draw);
}
}
return {
BACKGROUND: 0,
FOREGROUND: 1,
set canvas(canvasID) {
if (!hasInitialized) {
canvas = document.getElementById(canvasID);
canvas.onmousedown = onMouseDown;
canvas.onmouseup = onMouseUp;
canvas.onmousemove = onMouseMove;
canvas.onwheel = onWheel;
ctx = canvas.getContext("2d");
}
},
set canvasWidth(w) {
if (!hasInitialized && canvas) {
canvasWidth = canvas.width = w;
}
},
set canvasHeight(h) {
if (!hasInitialized && canvas) {
canvasHeight = canvas.height = h;
}
},
set tileWidth(w) {
if (!hasInitialized) {
tileWidth = w;
}
},
set tileHeight(h) {
if (!hasInitialized) {
tileHeight = h;
}
},
set width(w) {
if (!hasInitialized) {
boardWidth = w;
}
},
set height(h) {
if (!hasInitialized) {
boardHeight = h;
}
},
set onTileSelected(callback) {
onTileSelected = callback;
},
get width() {
return boardWidth;
},
get height() {
return boardHeight;
},
get onTileSelected() {
return onTileSelected;
},
defineTileTypes: function(types) {
if (types.length % 2 !== 0) {
return;
}
for (var i = 0; i < types.length; i += 2) {
var id = types[i];
var img = types[i + 1];
tileTypes[id] = img;
}
},
init: function() {
camera.x = -(boardWidth >> 1) * tileWidth;
camera.y = -(boardHeight >> 1) * tileHeight;
ctx.translate(canvasWidth >> 1,canvasHeight >> 1);
board.length = boardWidth * boardHeight;
for (var i = 0; i < board.length; ++i) {
board[i] = {
background: "",
foreground: ""
};
}
hasInitialized = true;
requestAnimationFrame(draw);
},
set: function(type,id,x,y) {
if (hasInitialized
&& tileTypes[id]
&& x > -1
&& x < boardWidth
&& y > -1
&& y < boardHeight) {
var index = x + y * boardWidth;
if (type === this.BACKGROUND) {
board[index].background = id;
} else {
board[index].foreground = id;
}
requestDraw();
}
}
};
}();
void function() {
"use strict";
svg.request("grass","https://i.stack.imgur.com/CkvU7.png");
svg.request("water","https://i.stack.imgur.com/an6a5.png");
svg.onload = function() {
board.canvas = "canvas";
board.canvasWidth = 180;
board.canvasHeight = 160;
board.tileWidth = 25;
board.tileHeight = 25;
board.width = 20;
board.height = 20;
board.defineTileTypes([
"GRASS",svg.get("grass"),
"WATER",svg.get("water")
]);
board.init();
for (var x = 0; x < board.width; ++x) {
for (var y = 0; y < board.height; ++y) {
board.set(board.BACKGROUND,"WATER",x,y);
if (Math.random() > 0.2) {
board.set(board.BACKGROUND,"GRASS",x,y);
} else {
board.set(board.BACKGROUND,"WATER",x,y);
}
}
}
}
board.onTileSelected = function(e) {
if (e.background === "GRASS") {
board.set(board.BACKGROUND,"WATER",e.x,e.y);
} else {
board.set(board.BACKGROUND,"GRASS",e.x,e.y);
}
}
}();
</script>
</body>
</html>
来源:https://stackoverflow.com/questions/50721799/scrollable-board-for-html5-game-should-use-svgs-or-canvas