Reputation: 596
I have been thinking about trying to create a game engine in JavaScript to improve my skills. One of the things I was thinking about is the game camera, it would have a certain x and y range and a global position.
Everything else would also have a global coordinate. When it is time to draw the screen, the program would check which objects are within the boundaries of the camera, get the coordinates relative to the camera, and draw them.
My problem is that some objects will be half off and half on the screen, so I was wondering if there is a way to draw these things on a canvas.
Upvotes: 1
Views: 540
Reputation: 570
You can draw them entirely, but the part supposed to be hidden won't be displayed because it will be drawn outside the canvas.
Or you can draw them partially. Determine how much of them should be displayed, then draw this part. May be useful:
https://www.html5canvastutorials.com/advanced/html5-canvas-clipping-region-tutorial/
https://www.html5canvastutorials.com/tutorials/html5-canvas-image-crop/
EDIT: I wrote something that displays grey squares only if they are entirely or partly within the camera's field of view (in red).
You can change the mode (normal: all squares are displayed, exclude off camera: display only the squares within camera's range), and you can change the camera's range's size.
You can also move the red square with the arrow keys. In exclude off camera mode, it will disappear if it exits the camera's range.
let canvas, ctx;
let squares;
const color = {
grey: '#aaa',
black: '#000',
red: '#f00'
};
const squareSize = 10;
const camera = {};
const mode = {
NORMAL: 'NORMAL',
EXCLUDE_OFF_CAMERA: 'EXCLUDE_OFF_CAMERA',
}
let currentMode;
let mainSquare;
let mainSquareSpeed;
const interval = 1000 / 30;
const KEY_LEFT = 37;
const KEY_RIGHT = 39;
const KEY_UP = 38;
const KEY_DOWN = 40;
let keyUpPressede;
let keyDownPressed;
let keyLeftPressed;
let keyRightPressed;
init();
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
camera.width = camera.height = 70;
camera.x = (canvas.width - camera.width) / 2;
camera.y = (canvas.height - camera.height) / 2;
currentMode = mode.EXCLUDE_OFF_CAMERA;
keyUpPressed = false;
keyDownPressed = false;
keyLeftPressed = false;
keyRightPressed = false;
squares = createSquares(canvas, 20);
mainSquare = squares[0];
mainSquare.x = canvas.width / 2;
mainSquare.y = canvas.height / 2;
mainSquare.color = color.red;
mainSquareSpeed = 3;
window.addEventListener("keydown", onKeyDown, false);
window.addEventListener("keyup", onKeyUp, false);
document.getElementById("modeNormal").onclick = function() {
currentMode = mode.NORMAL;
}
document.getElementById("modeExcludeOffCamera").onclick = function() {
currentMode = mode.EXCLUDE_OFF_CAMERA;
}
document.getElementById("cameraSize").addEventListener("change", function(e) {
camera.width = camera.height = parseInt(e.currentTarget.value, 10);
camera.x = (canvas.width - camera.width) / 2;
camera.y = (canvas.height - camera.height) / 2;
}, false);
// requestAnim shim layer by Paul Irish
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 10);
};
})();
step(canvas, ctx, camera, squares, mainSquare, squareSize, mainSquareSpeed);
}
function step(canvas, ctx, camera, squares, mainSquare, squareSize, squareSpeed) {
requestAnimFrame(function() {
step(canvas, ctx, camera, squares, mainSquare, squareSize, squareSpeed);
});
update(canvas, mainSquare, squareSize, squareSpeed);
clear(canvas, ctx);
draw(canvas, ctx, camera, squares, squareSize);
}
function update(canvas, mainSquare, squareSize, squareSpeed) {
if (keyLeftPressed) {
mainSquare.x -= squareSpeed;
} else if (keyRightPressed) {
mainSquare.x += squareSpeed;
}
if (keyUpPressed) {
mainSquare.y -= squareSpeed;
} else if (keyDownPressed) {
mainSquare.y += squareSpeed;
}
if (mainSquare.x < 0) {
mainSquare.x = canvas.width - squareSize;
} else if (mainSquare.x > canvas.width - squareSize) {
mainSquare.x = 0;
}
if (mainSquare.y < 0) {
mainSquare.y = canvas.height - squareSize;
} else if (mainSquare.y > canvas.height - squareSize) {
mainSquare.y = 0;
}
}
function clear(canvas, ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function draw(canvas, ctx, camera, squares, size) {
drawSquares(ctx, squares, size, camera);
drawCamera(canvas, ctx, camera);
}
function drawSquares(ctx, squares, size, camera) {
for (let i = squares.length - 1; i >= 0; i -= 1) {
if (inViewRange(squares[i], camera, size) || currentMode === mode.NORMAL) {
drawSquare(ctx, squares[i], size);
}
}
}
function drawSquare(ctx, square, size) {
ctx.fillStyle = square.color ? square.color : color.grey;
ctx.strokeStyle = color.black;
ctx.fillRect(square.x, square.y, size, size);
ctx.strokeRect(square.x, square.y, size, size);
}
function drawCamera(canvas, ctx, camera) {
ctx.fillStyle = color.black;
ctx.strokeStyle = color.red;
ctx.lineWidth = 3;
ctx.globalAlpha = 0.3;
ctx.fillRect(camera.x, camera.y, camera.width, camera.height);
ctx.globalAlpha = 1;
ctx.strokeRect(camera.x, camera.y, camera.width, camera.height);
}
/**
* Found here: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
*
* Returns a random integer between min (inclusive) and max (inclusive).
* The value is no lower than min (or the next integer greater than min
* if min isn't an integer) and no greater than max (or the next integer
* lower than max if max isn't an integer).
* Using Math.round() will give you a non-uniform distribution!
*/
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function createSquares(canvas, number) {
const squares = [];
for (let i = 0; i < number; i += 1) {
squares.push({
x: getRandomInt(0, canvas.width),
y: getRandomInt(0, canvas.height)
});
}
return squares;
}
function inViewRange(square, camera, squareSize) {
if (square.x + squareSize > camera.x && camera.x + camera.width > square.x) {
if (square.y + squareSize > camera.y && camera.y + camera.height > square.y) {
return true;
}
}
return false;
}
function onKeyDown(e) {
switch (e.keyCode) {
case KEY_LEFT: keyLeftPressed = true; break;
case KEY_RIGHT: keyRightPressed = true; break;
case KEY_UP: keyUpPressed = true; break;
case KEY_DOWN: keyDownPressed = true; break;l
}
}
function onKeyUp(e) {
switch (e.keyCode) {
case KEY_LEFT: keyLeftPressed = false; break;
case KEY_RIGHT: keyRightPressed = false; break;
case KEY_UP: keyUpPressed = false; break;
case KEY_DOWN: keyDownPressed = false; break;
}
}
canvas {
border: 1px solid black;
margin: 1px;
}
<canvas id="canvas" width=100 height=100></canvas>
<br>
<button id="modeNormal">modeNormal</button>
<button id="modeExcludeOffCamera">modeExcludeOffCamera</button>
Camera size: <input id="cameraSize" type="range" min="10" max="100" value="70">
Upvotes: 1