Question:
Actually the task is to implement a camera that will follow the player in the game world with the ability to zoom in / out of the camera from the player.
Making the camera follow the player is quite simple:
ctx.save();
ctx.translate(-camera.leftTopPos.x, -camera.leftTopPos.y);
// Рисуем игрока и игровой мир
ctx.restore();
Where camera
is:
var camera = {
leftTopPos: { x: 0, y: 0 }, // Левый верхний угол камеры в игровом мире.
size: { x: 0, y: 0 }, // Размер камеры (по умолчанию, равен размеру холста)
scale: 1,
};
Is it correct to use ctx.translate
for a large game world? Would it be more efficient, in terms of performance, to keep the player at the same point (0, 0)
or (w/2, h/2)
all the time and move all other objects relative to him?
Problem with adding camera zoom in/out. If you do this:
ctx.translate(-camera.leftTopPos.x, -camera.leftTopPos.y);
ctx.scale(camera.scale.x, camera.scale.y);
then there are problems with changing the size of the camera and it shifts somewhere.
I made an example for working with the camera on jsfiddle .
wasd
– camera movement. zx
– zoom in and out of the camera.
function p(s) { document.body.innerHTML += s + '<br>'; }
var w, h;
var camera = {
leftTopPos: { x: 0, y: 0 },
size: { x: 0, y: 0 },
scale: 1,
};
var canvas = document.querySelector('canvas');
var can = canvas;
var context = canvas.getContext('2d');
var ctx = context;
function updateCameraSize() {
camera.size = {
x: canvas.width,
y: canvas.height,
};
}
function setFullscreenSize() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
updateCameraSize();
}
function makeAlwaysCanvasFullscreen() {
setFullscreenSize();
window.addEventListener('resize', setFullscreenSize);
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function drawGrid() {
var cellSize = 100;
var minX = -1000, maxX = 1000;
var minY = -1000, maxY = 1000;
for (var x = minX; x <= maxX; x += cellSize) {
drawLine(x, minY, x, maxY);
}
for (var y = minY; y <= maxY; y += cellSize) {
drawLine(minX, y, maxX, y);
}
}
function drawAxises() {
var minX = -1000, maxX = 1000;
var minY = -1000, maxY = 1000;
ctx.save();
ctx.lineWidth = 5;
ctx.strokeStyle = 'red';
drawLine(0, 2 * minY, 0, 2 * maxY);
ctx.strokeStyle = 'green';
drawLine(2 * minX, 0, 2 * maxX, 0);
ctx.restore();
}
function update() {
var dir = { x: 0, y: 0 };
var speed = 10;
if (key.isPressed('a')) dir.x -= 1;
if (key.isPressed('d')) dir.x += 1;
if (key.isPressed('w')) dir.y -= 1;
if (key.isPressed('s')) dir.y += 1;
camera.leftTopPos.x += speed * dir.x;
camera.leftTopPos.y += speed * dir.y;
}
function draw() {
ctx.clearRect(0, 0, w, h);
ctx.save();
ctx.translate(-camera.leftTopPos.x, -camera.leftTopPos.y);
ctx.scale(camera.scale, camera.scale);
drawGrid();
drawAxises();
drawViewportRect();
ctx.restore();
}
function drawViewportRect() {
ctx.save();
ctx.lineWidth = 10;
ctx.strokeStyle = 'yellow';
var pos = camera.leftTopPos, sz = camera.size;
ctx.beginPath();
ctx.rect(pos.x, pos.y, sz.x, sz.y);
ctx.stroke();
ctx.restore();
}
function main() {
function go() {
update();
draw();
requestAnimationFrame(go);
}
requestAnimationFrame(go);
}
makeAlwaysCanvasFullscreen();
main();
key('z', () => {
camera.scale += 0.1;
});
key('x', () => {
camera.scale -= 0.1;
});
canvas {
position: fixed;
left: 0;
top: 0;
/* border: 1px solid black; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/keymaster/1.6.1/keymaster.min.js"></script>
<canvas></canvas>
Answer:
Using the method of poking and measuring distances during debugging, I nevertheless found how to implement zooming in and out of the camera:
ctx.save();
ctx.translate(-camera.leftTopPos.x * camera.scale, -camera.leftTopPos.y * camera.scale);
// Рисуем игрока и игровой мир
ctx.restore();
The dimensions of the chamber will be as follows:
camera.size = {
x: canvas.width / camera.scale,
y: canvas.height / camera.scale,
};
function p(s) { document.body.innerHTML += s + '<br>'; }
var w, h;
var camera = {
leftTopPos: { x: 0, y: 0 },
size: { x: 0, y: 0 },
scale: 1,
};
var canvas = document.querySelector('canvas');
var can = canvas;
var context = canvas.getContext('2d');
var ctx = context;
function updateCameraSize() {
camera.size = {
x: canvas.width / camera.scale,
y: canvas.height / camera.scale,
};
}
function setFullscreenSize() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
updateCameraSize();
}
function makeAlwaysCanvasFullscreen() {
setFullscreenSize();
window.addEventListener('resize', setFullscreenSize);
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function drawGrid() {
var cellSize = 100;
var minX = -1000, maxX = 1000;
var minY = -1000, maxY = 1000;
for (var x = minX; x <= maxX; x += cellSize) {
drawLine(x, minY, x, maxY);
}
for (var y = minY; y <= maxY; y += cellSize) {
drawLine(minX, y, maxX, y);
}
}
function drawAxises() {
var minX = -1000, maxX = 1000;
var minY = -1000, maxY = 1000;
ctx.save();
ctx.lineWidth = 5;
ctx.strokeStyle = 'red';
drawLine(0, 2 * minY, 0, 2 * maxY);
ctx.strokeStyle = 'green';
drawLine(2 * minX, 0, 2 * maxX, 0);
ctx.restore();
}
function update() {
var dir = { x: 0, y: 0 };
var speed = 10;
if (key.isPressed('a')) dir.x -= 1;
if (key.isPressed('d')) dir.x += 1;
if (key.isPressed('w')) dir.y -= 1;
if (key.isPressed('s')) dir.y += 1;
camera.leftTopPos.x += speed * dir.x;
camera.leftTopPos.y += speed * dir.y;
}
function draw() {
ctx.clearRect(0, 0, w, h);
ctx.save();
ctx.translate(-camera.leftTopPos.x * camera.scale, -camera.leftTopPos.y * camera.scale);
ctx.scale(camera.scale, camera.scale);
drawGrid();
drawAxises();
drawViewportRect();
ctx.restore();
}
function drawViewportRect() {
ctx.save();
ctx.lineWidth = 10;
ctx.strokeStyle = 'yellow';
var pos = camera.leftTopPos, sz = camera.size;
ctx.beginPath();
ctx.rect(pos.x, pos.y, sz.x, sz.y);
ctx.stroke();
ctx.restore();
}
function main() {
function go() {
update();
draw();
requestAnimationFrame(go);
}
requestAnimationFrame(go);
}
makeAlwaysCanvasFullscreen();
main();
key('z', () => {
camera.scale += 0.1;
updateCameraSize();
});
key('x', () => {
camera.scale -= 0.1;
updateCameraSize();
});
canvas {
position: fixed;
left: 0;
top: 0;
/* border: 1px solid black; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/keymaster/1.6.1/keymaster.min.js"></script>
<canvas></canvas>
But there is still a question about the performance of translate
.