How to make this HTML5 canvas script to full screen? - javascript

I am having trouble trying to set the canvas to fill the whole window. It is currently set to 500x500.. I am new to coding and would really appreciate any help! Thank you.
Here is the code:
<!DOCTYPE html>
<html>
<head>
<title>Starfield effect done in HTML 5</title>
<script>
window.onload = function() {
/* --- config start --- */
var starfieldCanvasId = "starfieldCanvas", // id of the canvas to use
framerate = 60, // frames per second this animation runs at (this is not exact)
numberOfStarsModifier = 0.15, // Number of stars, higher numbers have performance impact
flightSpeed = 0.003; // speed of the flight, the higher this number the faster
/* ---- config end ---- */
var canvas = document.getElementById(starfieldCanvasId),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
numberOfStars = width * height / 1000 * numberOfStarsModifier,
dirX = width / 2,
dirY = height / 2,
stars = [],
TWO_PI = Math.PI * 2;
// initialize starfield
for(var x = 0; x < numberOfStars; x++) {
stars[x] = {
x: range(0, width),
y: range(0, height),
size: range(0, 1)
};
}
// when mouse moves over canvas change middle point of animation
canvas.onmousemove = function(event) {
dirX = event.offsetX,
dirY = event.offsetY;
}
// start tick at specified fps
window.setInterval(tick, Math.floor(1000 / framerate));
// main routine
function tick() {
var oldX,
oldY;
// reset canvas for next frame
context.clearRect(0, 0, width, height);
for(var x = 0; x < numberOfStars; x++) {
// save old status
oldX = stars[x].x;
oldY = stars[x].y;
// calculate changes to star
stars[x].x += (stars[x].x - dirX) * stars[x].size * flightSpeed;
stars[x].y += (stars[x].y - dirY) * stars[x].size * flightSpeed;
stars[x].size += flightSpeed;
// if star is out of bounds, reset
if(stars[x].x < 0 || stars[x].x > width || stars[x].y < 0 || stars[x].y > height) {
stars[x] = {
x: range(0, width),
y: range(0, height),
size: 0
};
}
// draw star
context.strokeStyle = "rgba(255, 255, 255, " + Math.min(stars[x].size, 1) + ")";
context.lineWidth = stars[x].size;
context.beginPath();
context.moveTo(oldX, oldY);
context.lineTo(stars[x].x, stars[x].y);
context.stroke();
}
}
// get a random number inside a range
function range(start, end) {
return Math.random() * (end - start) + start;
}
};
</script>
</head>
<body style="background:#000;">
<canvas width="500" height="500" id="starfieldCanvas"></canvas>
</body>
</html>
Original code and credit to: https://www.timewasters-place.com/starfield-animation-done-in-html-5/

I assume you mean that you want your script to determine the screen size automatically, and then to set the canvas to be the full screen instead of the hardcoded 500 x 500 currently.
You can determine the viewport size programatically using the following where the width and height are as follows respectively. (Source):
var width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
var height = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
Then you can set the dimensions of your canvas as follows:
canvas.width = width;
canvas.height = height;
So, this would be the full code in your case:
<!DOCTYPE html>
<html>
<head>
<title>Starfield effect done in HTML 5</title>
<script>
window.onload = function() {
/* --- config start --- */
var starfieldCanvasId = "starfieldCanvas", // id of the canvas to use
framerate = 60, // frames per second this animation runs at (this is not exact)
numberOfStarsModifier = 0.15, // Number of stars, higher numbers have performance impact
flightSpeed = 0.003; // speed of the flight, the higher this number the faster
var width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
var height = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
/* ---- config end ---- */
var canvas = document.getElementById(starfieldCanvasId),
context = canvas.getContext("2d"),
stars = [],
TWO_PI = Math.PI * 2;
canvas.width = width;
canvas.height = height;
numberOfStars = width * height / 1000 * numberOfStarsModifier;
dirX = width / 2;
dirY = height / 2;
// initialize starfield
for(var x = 0; x < numberOfStars; x++) {
stars[x] = {
x: range(0, width),
y: range(0, height),
size: range(0, 1)
};
}
// when mouse moves over canvas change middle point of animation
canvas.onmousemove = function(event) {
dirX = event.offsetX,
dirY = event.offsetY;
}
// start tick at specified fps
window.setInterval(tick, Math.floor(1000 / framerate));
// main routine
function tick() {
var oldX,
oldY;
// reset canvas for next frame
context.clearRect(0, 0, width, height);
for(var x = 0; x < numberOfStars; x++) {
// save old status
oldX = stars[x].x;
oldY = stars[x].y;
// calculate changes to star
stars[x].x += (stars[x].x - dirX) * stars[x].size * flightSpeed;
stars[x].y += (stars[x].y - dirY) * stars[x].size * flightSpeed;
stars[x].size += flightSpeed;
// if star is out of bounds, reset
if(stars[x].x < 0 || stars[x].x > width || stars[x].y < 0 || stars[x].y > height) {
stars[x] = {
x: range(0, width),
y: range(0, height),
size: 0
};
}
// draw star
context.strokeStyle = "rgba(255, 255, 255, " + Math.min(stars[x].size, 1) + ")";
context.lineWidth = stars[x].size;
context.beginPath();
context.moveTo(oldX, oldY);
context.lineTo(stars[x].x, stars[x].y);
context.stroke();
}
}
// get a random number inside a range
function range(start, end) {
return Math.random() * (end - start) + start;
}
};
</script>
</head>
<body style="background:#000;">
<canvas id="starfieldCanvas"></canvas>
</body>
</html>
I also tested this on Fiddle. Here's the link: Fiddle
Hope this helps! Let me know if it works.

Related

Stop HTML5 Javascript animation after a given time

Am very new to canvas and javascript. I found a snippet for a starfield animation effect but it loops indefinitely.
How do I get the animation to stop after say, 30 seconds? I believe it has something to do with clearInterval or setTimeout but I have no idea where in the code this should be implemented.
Any help would be greatly appreciated.
window.onload = function() {
var starfieldCanvasId = "starfieldCanvas",
framerate = 60,
numberOfStarsModifier = 1.0,
flightSpeed = 0.02;
var canvas = document.getElementById(starfieldCanvasId),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
numberOfStars = width * height / 1000 * numberOfStarsModifier,
dirX = width / 2,
dirY = height / 4,
stars = [],
TWO_PI = Math.PI * 2;
for(var x = 0; x < numberOfStars; x++) {
stars[x] = {
x: range(0, width),
y: range(0, height),
size: range(0, 1)
};
}
window.setInterval(tick, Math.floor(1000 / framerate));
function tick() {
var oldX,
oldY;
context.clearRect(0, 0, width, height);
for(var x = 0; x < numberOfStars; x++) {
oldX = stars[x].x;
oldY = stars[x].y;
stars[x].x += (stars[x].x - dirX) * stars[x].size * flightSpeed ;
stars[x].y += (stars[x].y - dirY) * stars[x].size * flightSpeed ;
stars[x].size += flightSpeed;
if(stars[x].x < 0 || stars[x].x > width || stars[x].y < 0 || stars[x].y > height) {
stars[x] = {
x: range(0, width),
y: range(0, height),
size: 0
};
}
context.strokeStyle = "rgba(160, 160, 230, " + Math.min(stars[x].size, 2) + ")";
context.lineWidth = stars[x].size;
context.beginPath();
context.moveTo(oldX, oldY);
context.lineTo(stars[x].x, stars[x].y);
context.stroke();
}
}
function range(start, end) {
return Math.random() * (end - start) + start;
}
};
It appears that tick() is the animation loop, so change the line;
window.setInterval(tick, Math.floor(1000 / framerate));
to
window.animLoop = window.setInterval(tick, Math.floor(1000 / framerate));
window.setTimeout( function() { window.clearInterval( window.animLoop ) }, 30000 );
where 30000 is the time to end in milliseconds.
This will stop the animation from repeating by ending the interval from looping.

trying to draw a sprite onto canvas but I am missing something

window.onload = function(){
theVideo();
playVideo();
Move();
Draw();
};
let objectInfo = {
canvas: null,
context: null,
// Number of sprites
numberOfFrames: 16,
image: null,
imageWidth: 128,
imageHeight: 192,
frameIndex: 0,
frameWidth: 0,
// Animation interval (ms)
msInterval: 1000,
x: 10,
y: 10,
};
const imageFile = "shaggy.png";
function Draw(){
objectInfo.context.drawImage(myImage, shift, 0, frameWidth, frameHeight, 120, 25, frameWidth, frameHeight);
}
//image setup
window.onload= function () {
// Canvas setup
objectInfo.canvas = document.querySelector("#myCanvas");
objectInfo.context = objectInfo.canvas.getContext("2d");
// Image setup
objectInfo.image = new Image();
objectInfo.image.onload = function() {
// The this object refers to image because within image onload event handler
objectInfo.imageWidth = this.width;
objectInfo.imageHeight = this.height;
// Calculate framewidth (size of each sprite)
objectInfo.frameWidth = objectInfo.imageWidth / objectInfo.numberOfFrames;
};
// Load image
objectInfo.image.src = imageFile;
};
var xPos = 0;
var yPos = 0;
//move image
function Move(e){
//right
if(e.keyCode==39){
xPos+=5;
}
//left
if(e.keyCode==37){
xPos-=5;
}
//up
if(e.keyCode==38){
yPos-=5;
}
//down
if(e.keyCode==40){
yPos+=5;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sprite</title>
<meta charset="utf-8">
<meta name="author" content="Peyton">
<meta name="description" content="115">
<link rel= 'stylesheet' href="p4.css">
<script src="p4.js"> </script>
<style>canvas { border: 1px solid black; }</style>
<body>
<canvas width= "1300" height= "600" id= "myCanvas">
<video id="video" controls >
<source src="ScoobyDooV.mp4"/>
<source src="ScoobyDooV.ogv"/>
</video>
</canvas>
</body>
</html>
<nav>
</nav>
<main id="#wrapper"><br>
</main>
</body>
</html>
I'm really new to coding and am not sure what I am missing to call my sprite and draw the first image onto the canvas. I later have to call each measurement of my sprite and assign to a function keydown event to make it look like its walking each direction so if I could get any guidance on that too that would be great.
It looks like you're calling Draw before the image is loaded. Try placing the Draw() call within the image.onload method.
You are defining window.onload twice, only one out of your two callbacks will be executed
Your code is completely not suited to the task you are attempting. Animations require regular rendering. There is a huge amount of missing code in your example so i can really solve any problems directly.
So just thought I would give an example of how to load, animate, and render sprites sheets.
Sprite sheets
There are many ways to handle sprite sheets, though I find that using a standard method for all sprite sheets makes life easy.
Some sprite sheets have a regular layout and evenly spaced sprites, other sprite sheets, have been packed together to conserve pixels and memory.
Each sprite has a location on the sheet, the top left corner and the size as width and height.
You can attach an array of these locations to an image
For example the next function creates sprites for a regular layout (like image in your question)
function createSprites(width, height, columns, rows, image) {
const sprites = [];
var w = width / columns;
var h = height / rows;
var ix, iy;
for (iy = 0; iy < rows; iy++) {
for (ix = 0; ix < columns; ix++) {
const x = ix * w;
const y = iy * h;
sprites.push({ x, y, w, h });
}
}
image.sprites = sprites;
}
The array is added to the img so you don't have to add additional management
You can then draw the sprite by creating a custom draw function.
function drawSprite(img, sprIndex, x, y) {
const spr = img.sprites[sprIndex];
ctx.drawImage(img,
spr.x, spr.y, spr.w, spr.h, // location on sprite sheet
x , y , // location on canvas
spr.w, spr.h, // size on canvas;
);
}
You pass the sprite sheet image, the sprite index in the sprite array, and the location you want to draw the sprite.
Easy as
Because you likely know the size of the sprite sheet, and the location of the sprites you don't have to wait for the image to load to attach the sprite data.
const ctx = canvas.getContext("2d");
const spriteSheet = new Image;
spriteSheet.src = "https://i.stack.imgur.com/hOrC1.png";
// The image size is known so you dont have to wait for it to load
createSprites(128, 192, 4, 4, spriteSheet); // add a array of sprite locations
// It is important that the sprite sizes are integers
// width must be divisible by columns and height by rows
function createSprites(width, height, columns, rows, image) {
const sprites = [];
var w = width / columns;
var h = height / rows;
var ix, iy;
for (iy = 0; iy < rows; iy++) {
for (ix = 0; ix < columns; ix++) {
const x = ix * w;
const y = iy * h;
sprites.push({ x, y, w, h });
}
}
image.sprites = sprites;
}
function drawSprite(img, sprIndex, x, y) {
const spr = img.sprites[sprIndex];
ctx.drawImage(img,
spr.x, spr.y, spr.w, spr.h, // location on sprite sheet
x , y , // location on canvas
spr.w, spr.h, // size on canvas;
);
}
const walkerInfo = {
framesPerDir: 4,
movements: [{x: 0,y: 3 },{ x: -5, y: 0 }, { x: 5, y: 0 }, { x: 0, y: -3 } ],
}
const walker = {
dir: 0, // 0,1,2,3
time: 0, // time in Frames
rate: 0, // steps per frame
x: 0, // position
y: 0, //
update() {
this.time += 1;
// only move when sprite frame changes
if ((this.time % this.rate) === 0) {
this.x += walkerInfo.movements[this.dir].x;
this.y += walkerInfo.movements[this.dir].y;
if(this.x < -128 || this.x > canvas.width ||
this.y < -192 || this.y > canvas.height) {
this.x = randI(canvas.width);
this.y = randI(canvas.height);
this.dir = randI(4)
this.rate = randI(6, 12)
}
}
if(randI(1000) === 0){
this.dir = (this.dir + (randI(2) ? 2 : 1)) % 4;
this.rate = randI(6, 12)
}
},
draw() {
var index = this.dir * walkerInfo.framesPerDir;
index += (this.time / this.rate | 0) % walkerInfo.framesPerDir;
drawSprite(
spriteSheet, index,
this.x, this.y
);
}
}
function createWalker(x = randI(w), y = randI(h), dir = randI(4), rate = randI(6, 18)) {
return { ...walker, x, y, dir, rate, time: randI(100) };
}
const walkers = [];
// main update function
function update(timer) {
globalTime = timer;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
if (spriteSheet.complete) { // has the image loaded
if (randI(walkers.length) === 0) { // odd 1/100 to create a walker
walkers.push(createWalker());
}
walkers.sort((a,b)=>a.y - b.y);
eachOf(walkers, walk => walk.update());
eachOf(walkers, walk => walk.draw());
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
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 eachOf = (array, cb) => {
var i = 0;
const len = array.length;
while (i < len && cb(array[i], i++, len) !== true);
};
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

How to draw only visible part of the tilemap on js canvas?

I created simple tilemap using Tiled (3200 x 3200 pixels). I loaded it on my canvas using this library
I draw entire tilemap 3200 x 3200 60 times per seocnd.
I tried to move around and it works fine. Btw, I move around canvas using ctx.translate. I included this in my own function
But when I created bigger map in Tiled ( 32000 x 32000 pixels ) - I got a very freezing page. I couldn't move around fast, I think there was about 10 fps
So how to fix it? I have to call drawTiles() function 60 times per second. But is there any way to draw only visible part of the tile? Like draw only what I see on my screen (0, 0, monitorWidth, monitorHeight I guess)
Thank you
##Drawing a large tileset
If you have a large tile set and only see part of it in the canvas you just need to calculate the tile at the top left of the canvas and the number of tiles across and down that will fit the canvas.
Then draw the square array of tiles that fit the canvas.
In the example the tile set is 1024 by 1024 tiles (worldTileCount = 1024), each tile is 64 by 64 pixels tileSize = 64, making the total playfield 65536 pixels square
The position of the top left tile is set by the variables worldX, worldY
###Function to draw tiles
// val | 0 is the same as Math.floor(val)
var worldX = 512 * tileSize; // pixel position of playfield
var worldY = 512 * tileSize;
function drawWorld(){
const c = worldTileCount; // get the width of the tile array
const s = tileSize; // get the tile size in pixels
// get the tile position
const tx = worldX / s | 0; // get the top left tile
const ty = worldY / s | 0;
// get the number of tiles that will fit the canvas
const tW = (canvas.width / s | 0) + 2;
const tH = (canvas.height / s | 0) + 2;
// set the location. Must floor to pixel boundary or you get holes
ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);
// Draw the tiles across and down
for(var y = 0; y < tH; y += 1){
for(var x = 0; x < tW; x += 1){
// get the index into the tile array for the tile at x,y plus the topleft tile
const i = tx + x + (ty + y) * c;
// get the tile id from the tileMap. If outside map default to tile 6
const tindx = tileMap[i] === undefined ? 6 : tileMap[i];
// draw the tile at its location. last 2 args are x,y pixel location
imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
}
}
}
###setTransform and absolute coordinates.
Use absolute coordinates makes everything simple.
Use the canvas context setTransform to set the world position and then each tile can be drawn at its own coordinate.
// set the world location. The | 0 floors the values and ensures no holes
ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);
That way if you have a character at position 51023, 34256 you can just draw it at that location.
playerX = 51023;
playerY = 34256;
ctx.drawImage(myPlayerImage,playerX,playerY);
If you want the tile map relative to the player then just set the world position to be half the canvas size up and to the left plus one tile to ensure overlap
playerX = 51023;
playerY = 34256;
worldX = playerX - canvas.width / 2 - tileWidth;
worldY = playerY - canvas.height / 2 - tileHeight;
###Demo of large 65536 by 65536 pixel tile map.
At 60fps if you have the horses and can handle much much bigger without any frame rate loss. (map size limit using this method is approx 4,000,000,000 by 4,000,000,000pixels (32 bit integers coordinates))
#UPDATE 15/5/2019 re Jitter
The comments have pointed out that there is some jitter as the map scrolls.
I have made changes to smooth out the random path with a strong ease in out turn every 240 frame (4 seconds at 60fps) Also added a frame rate reducer, if you click and hold the mouse button on the canvas the frame rate will be slowed to 1/8th normal so that the jitter is easier to see.
There are two reasons for the jitter.
###Time error
The first and least is the time passed to the update function by requestAnimationFrame, the interval is not perfect and rounding errors due to the time is compounding the alignment problems.
To reduce the time error I have set the move speed to a constant interval to minimize the rounding error drift between frames.
###Aligning tiles to pixels
The main reason for the jitter is that the tiles must be rendered on pixel boundaries. If not then aliasing errors will create visible seams between tiles.
To see the difference click the button top left to toggle pixel alignment on and off.
To get smooth scrolling (sub pixel positioning) draw the map to an offscreen canvas aligning to the pixels, then render that canvas to the display canvas adding the sub pixel offset. That will give the best possible result using the canvas. For better you will need to use webGL
###End of update
var refereshSkip = false; // when true drops frame rate by 4
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);
// wait for code under this to setup
setTimeout(() => {
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// create tile map
const worldTileCount = 1024;
const tileMap = new Uint8Array(worldTileCount * worldTileCount);
// add random tiles
doFor(worldTileCount * worldTileCount, i => {
tileMap[i] = randI(1, tileCount);
});
// this is the movement direction of the map
var worldDir = Math.PI / 4;
/* =======================================================================
Drawing the tileMap
========================================================================*/
var worldX = 512 * tileSize;
var worldY = 512 * tileSize;
function drawWorld() {
const c = worldTileCount; // get the width of the tile array
const s = tileSize; // get the tile size in pixels
const tx = worldX / s | 0; // get the top left tile
const ty = worldY / s | 0;
const tW = (canvas.width / s | 0) + 2; // get the number of tiles to fit canvas
const tH = (canvas.height / s | 0) + 2;
// set the location
if(dontAlignToPixel) {
ctx.setTransform(1, 0, 0, 1, -worldX,-worldY);
} else {
ctx.setTransform(1, 0, 0, 1, Math.floor(-worldX),Math.floor(-worldY));
}
// Draw the tiles
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);
//==============================================================
// main render function
function update() {
refreshFrames ++;
if(!refereshSkip || (refereshSkip && refreshFrames % 8 === 0)){
timer += 1000 / 60;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
// Move the map
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>

Get cursor location of a rectangle inside a canvas

i have a canvas, inside of which i have a board/grid. When a user highlights their mouse over an intersection of the grid, i want it to show where their game peice will go. This worked perfectly fine when the board was the exact size of the canvas. I made it abit smaller by x all the way round.
So as you can see in the picture below, the green shows the canvas and the grid is the board. I put my cursor at the very bottom right corner of the green to show when it triggers. The only one that works fine is the middle one because regardless how big i make the board, the middle will always be the middle.
Any easy fix would just be to make the area with the mouseover event, the dimensions of the board instead of the canvas but the event listener is on the canvas. My code is below the image
Variables:
var canvas = document.getElementById("game-canvas");
var context = canvas.getContext("2d");
var boardSize = 13;
var border = canvas.width / 20;
var boardWidth = canvas.width - (border * 2);
var boardHeight = canvas.height - (border * 2);
var cellWidth = boardWidth / (boardSize - 1);
var cellHeight = boardHeight / (boardSize - 1);
var lastX;
var lastY;
Mouse over event:
canvas.addEventListener('mousemove', function(evt)
{
var position = getGridPoint(evt);
if ((position.x != lastX) || (position.y != lastY))
{
placeStone((position.x * cellWidth) + border, (position.y * cellWidth) + border, 'rgba(0, 0, 0, 0.2)');
}
lastX = position.x;
lastY = position.y;
});
Gets the point on the grid and converts that into a number 0 - 13 (in this case)
function getGridPoint(evt)
{
var rect = canvas.getBoundingClientRect();
var x = Math.round((evt.clientX-rect.left)/(rect.right-rect.left)*boardWidth);
var y = Math.round((evt.clientY-rect.top)/(rect.bottom-rect.top)*boardHeight);
var roundX = Math.round(x / cellWidth);
var roundY = Math.round(y / cellHeight);
return {
x: roundX,
y: roundY
};
}
And finally draws the piece on the board:
function placeStone(x, y, color)
{
var radius = cellWidth / 2;
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fillStyle = color;
context.fill();
context.lineWidth = 5;
}
I left a couple bits out like how the grid refreshs so its not a string of circles following your mouse and stuff, to keep it as short as i can, im hoping its just a simple asnwer and nobody needs to recreate it but if you do i can include the function that refreshes the grid and draws everything. Thankyou for any advice
To get the position relative to a box
// just as an example w,h are width and height
const box = { x : 10, y : 10, w : 100, h : 100 };
// mouse is the mouse coords and relative to the topleft of canvas (0,0);
var mouse.box = {}
mouse.box.x = mouse.x - box.x;
mouse.box.y = mouse.y - box.y;
Negative values for mouse.box x,y and values greater than box width and height have mouse outside.
For more convenience you can get the mouse normalize pos in the box
mouse.box.nx = mouse.box.x / box.w;
mouse.box.ny = mouse.box.y / box.h;
The coords for nx,ny are in the range 0-1 when inside or on the edge of the box;
If you want to have grid positions then define the grid
box.gridW = 10; // grid divisions width
box.gridH = 10; // grid divisions height
Then getting the grid pos of mouse
mouse.box.gx = Math.floor(mouse.box.nx * box.gridW);
mouse.box.gy = Math.floor(mouse.box.ny * box.gridH);
const ctx = canvas.getContext("2d");
const box = { x : 50,y : 10, w : 200, h : 200, gridW : 10, gridH : 10}
function drawGrid(){
var sx = box.w / box.gridW;
var sy = box.h / box.gridH;
var bx = box.x;
var by = box.y;
for(var y = 0; y < box.gridH; y ++){
for(var x = 0; x < box.gridW; x ++){
ctx.strokeRect(x * sx + bx, y * sx + by,sx,sy);
}
}
if(mouse.box){
if(mouse.box.nx >= 0 && mouse.box.nx <= 1 &&
mouse.box.ny >= 0 && mouse.box.ny <= 1){
ctx.fillRect(mouse.box.gx * sx + bx, mouse.box.gy * sx + by,sx,sy);
}
}
}
const mouse = {};
canvas.addEventListener("mousemove",(e)=>{
mouse.x = e.pageX;
mouse.y = e.pageY;
});
function updateMouse(){
if(!mouse.box){
mouse.box = {};
}
mouse.box.x = mouse.x - box.x;
mouse.box.y = mouse.y - box.y;
mouse.box.nx = mouse.box.x / box.w;
mouse.box.ny = mouse.box.y / box.h;
mouse.box.gx = Math.floor(mouse.box.nx * box.gridW);
mouse.box.gy = Math.floor(mouse.box.ny * box.gridH);
var p = 20;
ctx.fillText("x : " + mouse.x,box.x+box.w+10,p); p+= 14;
ctx.fillText("y : " + mouse.y,box.x+box.w+10,p); p+= 20;
ctx.fillText("Box relative",box.x+box.w+10,p); p+= 14;
ctx.fillText("x : " + mouse.box.x,box.x+box.w+10,p); p+= 14;
ctx.fillText("y : " + mouse.box.y,box.x+box.w+10,p); p+= 14;
ctx.fillText("nx : " + mouse.box.nx,box.x+box.w+10,p); p+= 14;
ctx.fillText("ny : " + mouse.box.ny,box.x+box.w+10,p); p+= 14;
ctx.fillText("gx : " + mouse.box.gx,box.x+box.w+10,p); p+= 14;
ctx.fillText("gy : " + mouse.box.gy,box.x+box.w+10,p); p+= 14;
}
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){ // resize canvas if window size has changed
canvas.width = innerWidth;
canvas.height = innerHeight;
}
ctx.setTransform(1,0,0,1,0,0); // set default transform
ctx.clearRect(0,0,canvas.width,canvas.height); // clear the canvas
updateMouse();
drawGrid();
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=canvas><canvas>

How do I allow a 2D shape to wrap around a canvas using WebGL?

I have created a simple animation in WebGL (html & javascript) where a 2D shape is animated and manipulated on a canvas. The default animation is shape moving to the right at a set speed and then using "WASD" changes its direction. The shape moves in the given direction indefinitely, even after it is off of the canvas and out of the clip-space. I would like to have the shape wrap around the canvas instead of just continuing even after it is unseen. For example, if the shape is moving to the right and moves completely off of the canvas, I would like it to appear on left side still moving to the right and continue cycling. Same goes for if it is moving left or up or down.
Any suggestions on how to implement this?
You have to draw it 2 to 4 times depending on if you want to wrap both left/right and top/bottom
Assume we only want to wrap around horizontally. If the player is near the left edge we need to also draw the player 1 screen width to the right. If the player is near the right edge we need to draw the player again one screen to the left. Same with up and down
Here's an example using canvas 2D but the only difference for WebGL is you'd draw using WebGL. The concept is the same.
Example:
var x = 150;
var y = 100;
var vx = 0;
var vy = 0;
const maxSpeed = 250;
const acceleration = 1000;
const ctx = document.querySelector("canvas").getContext("2d");
const keys = {};
const LEFT = 65;
const RIGHT = 68;
const DOWN = 83;
const UP = 87;
const width = ctx.canvas.width;
const height = ctx.canvas.height;
var then = 0;
function render(now) {
now *= 0.001; // seconds
const deltaTime = now - then;
then = now;
ctx.clearRect(0, 0, width, height);
if (keys[UP]) { vy -= acceleration * deltaTime; }
if (keys[DOWN]) { vy += acceleration * deltaTime; }
if (keys[LEFT]) { vx -= acceleration * deltaTime; }
if (keys[RIGHT]) { vx += acceleration * deltaTime; }
// keep speed under max
vx = absmin(vx, maxSpeed);
vy = absmin(vy, maxSpeed);
// move based on velociy
x += vx * deltaTime;
y += vy * deltaTime;
// wrap
x = euclideanModulo(x, width);
y = euclideanModulo(y, height);
// draw player 4 times
const xoff = x < width / 2 ? width : -width;
const yoff = y < height / 2 ? height : -height;
drawPlayer(x, y);
drawPlayer(x + xoff, y);
drawPlayer(x, y + yoff);
drawPlayer(x + xoff, y + yoff);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawPlayer(x, y) {
ctx.fillStyle = "blue";
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2, false);
ctx.fill();
ctx.stroke();
}
function absmin(v, max) {
return Math.min(Math.abs(v), max) * Math.sign(v);
}
function euclideanModulo(n, m) {
return ((n % m) + m) % m;
}
window.addEventListener('keydown', e => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', e => {
keys[e.keyCode] = false;
});
canvas {
display: block;
border: 1px solid black;
}
<canvas></canvas>
<p><span style="color:red;">click here</span> then use ASWD to move</p>
A WebGL version changes no code related to wrapping.
var x = 150;
var y = 100;
var vx = 0;
var vy = 0;
const maxSpeed = 250;
const acceleration = 1000;
const gl = document.querySelector("canvas").getContext("webgl");
const keys = {};
const LEFT = 65;
const RIGHT = 68;
const DOWN = 83;
const UP = 87;
const width = gl.canvas.width;
const height = gl.canvas.height;
var program = setupWebGL();
var positionLoc = gl.getAttribLocation(program, "position");
var then = 0;
function render(now) {
now *= 0.001; // seconds
const deltaTime = now - then;
then = now;
if (keys[UP]) { vy -= acceleration * deltaTime; }
if (keys[DOWN]) { vy += acceleration * deltaTime; }
if (keys[LEFT]) { vx -= acceleration * deltaTime; }
if (keys[RIGHT]) { vx += acceleration * deltaTime; }
// keep speed under max
vx = absmin(vx, maxSpeed);
vy = absmin(vy, maxSpeed);
// move based on velociy
x += vx * deltaTime;
y += vy * deltaTime;
// wrap
x = euclideanModulo(x, width);
y = euclideanModulo(y, height);
// draw player 4 times
const xoff = x < width / 2 ? width : -width;
const yoff = y < height / 2 ? height : -height;
drawPlayer(x, y);
drawPlayer(x + xoff, y);
drawPlayer(x, y + yoff);
drawPlayer(x + xoff, y + yoff);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawPlayer(x, y) {
gl.useProgram(program);
// only drawing a single point so no need to use a buffer
gl.vertexAttrib2f(
positionLoc,
x / width * 2 - 1,
y / height * -2 + 1);
gl.drawArrays(gl.POINTS, 0, 1);
}
function absmin(v, max) {
return Math.min(Math.abs(v), max) * Math.sign(v);
}
function euclideanModulo(n, m) {
return ((n % m) + m) % m;
}
window.addEventListener('keydown', e => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', e => {
keys[e.keyCode] = false;
});
function setupWebGL() {
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 40.;
}
`;
const fs = `
void main() {
gl_FragColor = vec4(1,0,1,1);
}
`;
// compiles and links shaders and assigns position to location 0
return twgl.createProgramFromSources(gl, [vs, fs]);
}
canvas {
display: block;
border: 1px solid black;
}
<canvas></canvas>
<p><span style="color:red;">click here</span> then use ASWD to move</p>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
If you don't want the player appear on both sides then your question has nothing to do with graphics, you just wait until the player's x position is at least screenWidth + haflPlayerWidth which means they're completely off the right side and then you set their x position to -halfPlayerWidth which will put them just off the left and visa versa
var x = 150;
var y = 100;
var vx = 0;
var vy = 0;
const maxSpeed = 250;
const acceleration = 1000;
const ctx = document.querySelector("canvas").getContext("2d");
const keys = {};
const LEFT = 65;
const RIGHT = 68;
const DOWN = 83;
const UP = 87;
const width = ctx.canvas.width;
const height = ctx.canvas.height;
const playerSize = 40;
const halfPlayerSize = playerSize / 2;
var then = 0;
function render(now) {
now *= 0.001; // seconds
const deltaTime = now - then;
then = now;
ctx.clearRect(0, 0, width, height);
if (keys[UP]) { vy -= acceleration * deltaTime; }
if (keys[DOWN]) { vy += acceleration * deltaTime; }
if (keys[LEFT]) { vx -= acceleration * deltaTime; }
if (keys[RIGHT]) { vx += acceleration * deltaTime; }
// keep speed under max
vx = absmin(vx, maxSpeed);
vy = absmin(vy, maxSpeed);
// move based on velociy
x += vx * deltaTime;
y += vy * deltaTime;
// wrap
x = euclideanModulo(x + halfPlayerSize, width + playerSize) - halfPlayerSize;
y = euclideanModulo(y + halfPlayerSize, height + playerSize) - halfPlayerSize;
// draw player
drawPlayer(x, y);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawPlayer(x, y) {
ctx.fillStyle = "blue";
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, halfPlayerSize, 0, Math.PI * 2, false);
ctx.fill();
ctx.stroke();
}
function absmin(v, max) {
return Math.min(Math.abs(v), max) * Math.sign(v);
}
function euclideanModulo(n, m) {
return ((n % m) + m) % m;
}
window.addEventListener('keydown', e => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', e => {
keys[e.keyCode] = false;
});
canvas {
display: block;
border: 1px solid black;
}
<canvas></canvas>
<p><span style="color:red;">click here</span> then use ASWD to move</p>
this code probably needs an explanation
x = euclideanModulo(x + haflPlayerSize, width + playerSize) - haflPlayerSize;
y = euclideanModulo(y + haflPlayerSize, height + playerSize) - haflPlayerSize;
First off euclideanModulo is just like normal % modulo operator, it returns the remainder after division, except euclidean modulo keeps the same remainder even for negative numbers. In other words
3 % 5 = 3
8 % 5 = 3
13 % 5 = 3
-2 % 5 = -2
-7 % 5 = -2
-12 % 5 = -2
but
3 euclideanMod 5 = 3
8 euclideanMod 5 = 3
13 euclideanMod 5 = 3
-2 euclideanMod 5 = 3
-7 euclideanMod 5 = 3
-12 euclideanMod 5 = 3
So it's a super easy way to wrap things.
x = euclideanModulo(x, screenWidth)
Is similar to
if (x < 0) x += screenWidth;
if (x >= screenWidth) x -= screenWidth;
Except those would fail if x > screenWidth * 2 for example where as the one using euclideanModulo would not.
So, back to
x = euclideanModulo(x + haflPlayerSize, width + playerSize) - haflPlayerSize;
y = euclideanModulo(y + haflPlayerSize, height + playerSize) - haflPlayerSize;
We know the player (in this case a circle) has its position at its center. So, we know when its center is half the playerSize off the left or right of the screen it's completely off the screen and we therefore want to move it to the other side. That means we can imagine the screen is really width + halfPlayerSize + halfPlayerSize wide. The first halfPlayerSize is for the stepping off the left side, the second halfPlayerSize is for stepping off the right side. In other words it's just width + playerSize wide. We then want the player to wrap from left to right when x < -halfPlayerSize. So we add halfPlayerSize to the player's position, then do the euclideanModulo which will wrap the position, then subtract that halfPlayerSize back out.

Categories