I am trying to make a game, where I draw images to a canvas. Here is a small code snippet:
class Sprite {
constructor(x, y, width, height, src, xDirection, yDirection) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.src = src;
this.xDirection = xDirection;
this.yDirection = yDirection;
}
update() {
var image = new Image();
image.src = this.src;
context.imageSmoothingEnabled = false;
context.drawImage(image, this.x, this.y, this.width, this.height);
this.x += this.xDirection;
this.y += this.yDirection;
}
}
const player = new Sprite(0, 0, 100, 100, "Images/Game/sprite-right.png", 0, 0);
const mainInterval = setInterval(() => {
player.draw();
}, 1);
This is only a small code snippet and I am also drawing many more images to the screen in the main interval. In Google Chrome and in Firefox, this works perfectly, as all of the images are correctly drawn to the screen. However, in Safari, the image is not drawn. I tried changing the 'update' method to this...
update() {
var image = new Image();
image.src = this.src;
image.onload = () => {
context.imageSmoothingEnabled = false;
context.drawImage(image, this.x, this.y, this.width, this.height);
this.x += this.xDirection;
this.y += this.yDirection;
};
}
...this works, but since I am drawing lots of things to the screen in the main interval, all of the images appear to sort of 'flash' and flicker. I need help with coming up with a way of drawing lots of images to the screen, without them flickering (bearing in mind that the method at the top only doesn't work in Safari, as far as I know). Thanks very much.
PrimeCubed
Not exactly sure why it isn't working on Safari but here are a few things that can be improved:
It's better to load all the images before you actually start drawing them.
You should use requestAnimationFrame for drawing frames.
Call the draw function only when all the resources have loaded. window.onload = draw;
Here is a snippet with these changes and this works on Safari too.
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
var image = new Image();
image.src = "https://placekitten.com/100/100";
class Sprite {
constructor(x, y, width, height, image, xDirection, yDirection) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.image = image;
this.xDirection = xDirection;
this.yDirection = yDirection;
}
update() {
context.imageSmoothingEnabled = false;
context.drawImage(this.image, this.x, this.y, this.width, this.height);
this.x += this.xDirection;
this.y += this.yDirection;
}
}
const player = new Sprite(0, 0, 100, 100, image, 0, 0);
function draw() {
player.update();
requestAnimationFrame(draw);
}
window.onload = draw;
<canvas />
Related
I am trying to create a 2D game in JavaScript, I want the BackgroundRight() BackgroundLeft() BackgroundStop() functions to run on clicking different keys, like on click A the background should go Right, and my character go to left, but when I press A or any key the the Background Functions are not drawing anything, but when I console.log() some random text in the Background functions, it normally prints in the console, it shows that its working but still it's not drawing anything, and if I run any Background Function Specifically(calling the function any where in the code like how we normally call them) then its working totally fine but not on key press. Please Help me out I am struggling on this from 1 week ðŸ˜
The Main code is the Layer class and the document.onKeyDown Function at the last of the code. please help me out.
/** #type {HTMLCanvas Element} */
// Collecting Elements from HTML code
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
// Giving Height Width to the Canvas
const CANVAS_HEIGHT = canvas.height = 324;
const CANVAS_WIDTH = canvas.width = 576;
// GameSpeed
let gameSpeed = 2;
let gameFrame = 0;
staggeredFrame = 8;
// Scrolling Background Images declaration
const backgroundLayer1 = new Image();
backgroundLayer1.src = 'IMG/2 Background/1.png';
const backgroundLayer2 = new Image();
backgroundLayer2.src = 'IMG/2 Background/2.png';
const backgroundLayer3 = new Image();
backgroundLayer3.src = 'IMG/2 Background/3.png';
const backgroundLayer4 = new Image();
backgroundLayer4.src = 'IMG/2 Background/4.png';
const backgroundLayer5 = new Image();
backgroundLayer5.src = 'IMG/2 Background/5.png';
const backgroundLayer6 = new Image();
backgroundLayer6.src = 'IMG/layer_02_1920 x 1080.png';
// Scrolling Background Class
class Layer { // The Class Which Have the Background functions
constructor(image, speedModifier) {
this.x = 0;
this.y = 0;
this.height = 324;
this.width = 576;
this.image = image;
this.speedModifier = speedModifier;
}
// To Move Background Left
backgroundLeft() {// on going through this function it should draw something but not drawing.
this.speed = this.speedModifier * gameSpeed;
if (this.x <= -this.width) this.x = 0;
else this.x -= this.speed;
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
ctx.drawImage(this.image, this.x + this.width, this.y, this.width, this.height);
// at this above line it should draw but its not working, but if I console.log some text after
// this line it will be printed out but nothing drawing
}
// To Move Background To Right
backgroundRight() { // same as the backgroundLeft() function it's not working
this.speed = this.speedModifier * gameSpeed;
if (this.x >= this.width) this.x = 0;
else this.x += this.speed;
this.draw();
}
// To Stop Background
backgroundStop() { // same as others this is also not working
this.draw();
}
// To draw Background Images
draw() {
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
ctx.drawImage(this.image, this.x - this.width, this.y, this.width, this.height);
}
}
// Scrolling Background objects
const layer1 = new Layer(backgroundLayer1, 0.2);
const layer2 = new Layer(backgroundLayer2, 0.3);
const layer3 = new Layer(backgroundLayer3, 0.4);
const layer4 = new Layer(backgroundLayer4, 0.6);
const layer5 = new Layer(backgroundLayer5, 0.8);
const layer6 = new Layer(backgroundLayer6, 0.8);
layer6.height = 420;
backLayer = [layer1, layer2, layer3, layer4, layer5, layer6];
// Player Class
class Player {
constructor() {
this.x = 0;
this.y = CANVAS_HEIGHT - 60;
this.height = 44;
this.width = 44;
this.image = new Image();
this.image.src = 'IMG/1 Characters/1 Biker/Walk1.png';
this.playerHeight = 48;
this.playerWidth = 48;
this.frameX = 0;
this.frameY = 0;
this.frames = 6;
}
// Update Class For Player
playerUpdate() {
this.position = Math.floor(gameFrame / staggeredFrame) % this.frames;
this.frameX = this.position * this.playerWidth;
ctx.drawImage(this.image, this.frameX, this.frameY, this.playerHeight, this.playerWidth, this.x, this.y, this.width, this.height);
}
}
// Player Object
const player = new Player();
player.image.src = 'IMG/1 Characters/1 Biker/Run_attack.png';
// Main Function
function animate() {
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
drawBackground();
player.playerUpdate();
gameFrame++;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// Drawing Background
function drawBackground() {
backLayer.forEach(element => {
element.backgroundRight();
});
}
document.onkeydown = (e) => { // here it should work on key press but it's not working.
const keyPress = e.keyCode;
if (keyPress === 65) {
console.log(e.keyCode, "A pressed"); // it's printing in console and showing key Pressed
backLayer.forEach((layer, i) => { // but still its not drawing anything
layer.backgroundLeft();
console.log("background should run to left")
})
}
}
I am making a clicker game where you click on a potato for stonks. I have this code:
var canvas, canvasContext, imgaeAssets;
window.onload = function() {
canvas = document.getElementById('gaming');
canvasContext = canvas.getContext('2d');
canvasContext.canvas.width = window.innerWidth;
canvasContext.canvas.height = window.innerHeight * 0.95;
setInterval(mainloop, 1000 / 50);
}
var potatoes = 0;
var potatoClick = new Image();
potatoClick.src = 'images/potato.jfif'
function mainloop() { // runs once every second
colorRect(0, 0, canvas.width, canvas.height, 'black')
canvasContext.font = "15px Comic Sans MS";
canvasContext.fillStyle = "white";
canvas.style.backgroundColor = 'rgba(255, 255, 255, 0)';
drawImg(potatoClick, 0, (canvas.height / 2) - 125, 500, 250)
if (potatoes > 0) {
canvasContext.fillText(`you have ${potatoes} potatoes`, 0, 30);
}
}
function colorRect(x, y, w, h, c) {
canvasContext.fillStyle = c;
canvasContext.fillRect(x, y, w, h);
}
function drawImg(src, x, y, w, h) {
canvasContext.drawImage(src, x, y, w, h);
}
function printMousePos(event) {}
I want to make the white in potatoClick transparent, but I can't figure out how. I've already tried assigning an alpha to it, and have also tried changing the opacity of the image, but neither have worked. If you could figure out how to make any color transparent, I would appreciate it. I wouldn't mind needing to change the way to draw the potato, but I think it would be cleaner and easier the way I have it now.
edit: I think my question was fundamentally flawed, sorry for the confusion
After drawing things at canvas, you can't edit them anymore.
Because, Image in canvas doesn't have any parameters after draw it.
It means if you want to edit image on canvas, you need to clear your canvas when the difference is occurred.
and if you want to change the image opacity you should set globalAlpha of ctx (draw image with opacity on to a canvas)
Usually, I use Fabric.js(http://fabricjs.com/) for solve that problem.
but If you want, you can make a your own renderer by defining simple class.
like this!
<body>
<img id="dog" src="https://cdn.pixabay.com/photo/2022/07/19/10/35/puppies-7331739__340.jpg">
<img id="woman" src="https://cdn.pixabay.com/photo/2021/05/04/20/57/woman-6229693__340.jpg">
<canvas id="canvas" width="500" height="500" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
var canvas;
var ctx;
var img;
window.onload = function () {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
dog = document.getElementById("dog");
woman = document.getElementById("woman");
const exam1 = new Image(dog);
const exam2 = new Image(woman);
images = [];
images.push(exam1);
images.push(exam2);
render(images);
dog.onclick = function () {
exam1.opacity = 0.7;
exam2.opacity = 0.3;
exam1.y += 10;
exam2.x += 10;
render(images);
}
}
function render(images) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < images.length; i++) {
images[i].render();
}
}
function Image(original) {
this.image = original;
this.width = this.image.width;
this.height = this.image.height;
this.opacity = 1.0;
this.x = 0;
this.y = 0;
}
Image.prototype.render = function () {
this.image.width = this.width;
this.image.height = this.height;
ctx.globalAlpha = this.opacity;
ctx.drawImage(this.image, this.x, this.y);
ctx.globalAlpha = 1.0;
}
</script>
</body>
I have the following segment of code intented to draw an image onto the canvas. This code works:
var ctx = canvas.getContext('2d');
class Rectangle {
constructor(x, y, scale, source) {
this.scale = scale;
this.source = source;
this.x = x;
this.y = y;
}
update() {
var sprite = new Image();
sprite.src = this.source;
sprite.onload = function () {
ctx.drawImage(sprite, this.x, this.y, sprite.width/3, sprite.height/3);
}
}
}
const rect = new Rectangle(0, 0, 1, 'circle.jpeg');
rect.update();
But, when I replace this line:
ctx.drawImage(sprite, this.x, this.y, sprite.width/3, sprite.height/3);
with this:
ctx.drawImage(sprite, this.x, this.y, sprite.width/this.scale, sprite.height/this.scale);
the canvas doesn't show anything at all. All this should be doing is replacing the 3 from before with the value from the constructor (which I set to 1 when I create a new instance). Why isn't it drawing anything?
I would move the sprite to the constructor that way when we call the update we don't have to create a new image, the idea behind that is the update function could be called multiple times efficiently.
See this sample below:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
class Rectangle {
constructor(x, y, scale, source) {
this.scale = scale;
this.x = x;
this.y = y;
this.sprite = new Image();
this.sprite.src = source;
this.sprite.onload = () => {
this.update()
};
}
update() {
if (this.sprite) {
ctx.drawImage(this.sprite, this.x, this.y, this.sprite.width / this.scale, this.sprite.height / this.scale);
}
}
}
const rect = new Rectangle(0, 0, 3, 'http://i.stack.imgur.com/UFBxY.png');
canvas.addEventListener("click", () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.x += 5;
rect.update();
});
<canvas id="canvas"></canvas>
You can see that as suggested in the comments by #skara9 I'm using the () => instead of function() and I'm calling the update there.
On this sample I also added canvas.addEventListener("click" that shows how when the user clicks on the canvas the drawing move a little to the right, the logic is simple we increase the value of rect.x and then we call the rect.update(); that will draw the image on the new location.
So I'm working on a project where I have a canvas filled with moving balls. Its an extension/inspired by this codepen project : https://codepen.io/zetyler/pen/LergVR .
It essentially runs with the same physics in place as the codepen, but now I'm trying to draw the moving and colliding balls with images instead of random colors.
The original draw() method looks like this :
var pen = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
var numBalls = 30;
var grav = [0,-0.1];
function Ball(x,y,dx,dy,r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
this.color = 'hsl('+(Math.random()*360)+',90%,50%)';
this.draw = function() {
pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.fill();
}
I'm refactoring the draw method to try and work with an image instead of a random color fill, and so far I can't even get an image to show up. Currently my draw method looks like this:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
let numBalls = 1;
let grav = [0,-0.1];
//try feeding the ball function an object
//and destructuring the inputs
class Ball {
constructor (x, y, dx, dy, r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
//probably won't need this
//this.color = 'hsl(' + (Math.random() * 360) + ', 90%, 50%)';
}
draw() {
var thumbImg = document.createElement('img');
thumbImg.src = './svgs/javascriptIcon.svg';
thumbImg.onload = function() {
context.save();
context.beginPath();
context.arc(25, 25, 25, 0, Math.PI * 2, true);
context.closePath();
context.clip();
context.drawImage(thumbImg, 0, 0, 50, 50);
context.beginPath();
context.arc(0, 0, 25, 0, Math.PI * 2, true);
context.clip();
context.closePath();
context.restore();
};
}
It's been so long since I've used the html canvas. I can't figure out what I'm doing wrong. I thought I would at least be able to get the image to show up, but no such luck.
Thanks for checking it out! Please let me know what you think.
I am not sure why you are using ctx.clip,
but if you just want to replace the coloured balls with images try this in your draw method
this.draw = function() {
// make sure the img is loaded
//pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.drawImage(img,this.x, this.y,this.r, this.r)
pen.fill();
}
after that play with IMG x and y positions for example pen.drawImage(img,this.x + somefactor, this.y - somefactor,this.r + somefactor, this.r + somefatcor)
just to make sure that img is perfectly cover the coloured ball so it behavies just like it
I have a pong game that I made 6 months ago when I was much worse at programming. The only thing is it flickers like CRAZY, which ruins it for me.
Here is some code which is involved in the drawing and clearing of the canvas, so may be behind the flickering:
canvas: document.getElementById( "canvas" ),
// Get our 2D context for drawing
ctx: canvas.getContext( "2d" ),
// Frames-per-second
FPS: 30,
draw: function() {
if (preGameContent.isStartScreen === false && endOfGame.isEndOfGame === false) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.Banana1.draw();
this.Banana2.draw();
this.Banana3.draw();
this.displayScore();
this.player1Paddle.draw();
this.player2Paddle.draw();
this.ball.draw();
}
}
update: function() {
if (preGameContent.isStartScreen === false && endOfGame.isEndOfGame === false) {
this.player1Paddle.updatePaddle();
this.player2Paddle.updatePaddle();
this.ball.updateBall();
if (this.ball.x < 0) {
if (this.is2PlayerGame === true) {
this.pointScored.startNextSet("player2");
this.setUp("right");
}
else {
this.pointScored.startNextSet("computer");
this.setUp("right");
}
}
if (this.ball.x + this.ball.width > this.canvas.width) {
gameController.pointScored.startNextSet("player1");
this.setUp("left");
}
}
}
tick: function() {
if (preGameContent.isStartScreen === false
&& endOfGame.isEndOfGame === false) {
gameController.draw();
gameController.update();
}
}
setInterval( gameController.tick, 1000 / gameController.FPS );
Do you see anything that can be done to this to reduce flicker? Thanks.
EDIT
Check out how i was redrawing each image every interval by creating a NEW image in its draw method:
//This class is to construct anything with an image
//vx and vy are for the velocity along the x and y axis
function Item(xStartPos, yStartPos, vx, vy, width, height, imgSrc) {
this.x = xStartPos;
this.y = yStartPos;
this.vx = vx;
this.vy = vy;
this.width = width;
this.height = height;
this.imgSrc = imgSrc;
//this function draws the image to the canvas
this.draw = function() {
var self = this;
var img = new Image();
img.src = self.imgSrc;
img.onload = function(){
gameController.ctx.drawImage(img, self.x, self.y);
};
};
//this function updates the position of the object on the canvas
this.update = function() {
// Divide velocity by gameController.FPS before adding it
// onto the position.
this.x += this.vx / gameController.FPS;
this.y += this.vy / gameController.FPS;
// wall collision detection
//stop the object from going through the top and bottom walls,
//but not the side walls, so the ball can go through them
if ( (this.y) < 0 ) {
this.y = 0;
}
if ( (this.y + this.height) > gameController.canvas.height) {
this.y = gameController.canvas.height - this.height;
}
};
};
EDIT
So i did this:
function Item(xStartPos, yStartPos, vx, vy, width, height, imgSrc) {
this.x = xStartPos;
this.y = yStartPos;
this.vx = vx;
this.vy = vy;
this.width = width;
this.height = height;
this.img = new Image();
this.imgSrc = imgSrc;
this.img.src = imgSrc;
//this.loaded = false;
//img.onload = function() { this.loaded = true; }
//this function draws the image to the canvas
this.draw = function() {
//if (this.loaded)
gameController.ctx.drawImage(this.img, this.x, this.y);
};
Which makes it draw all items nicely except the paddles and ball which do not draw at all
It is hard to say where exactly it flickers without looking at the whole solution, but I would blame setInterval(). The way to go is to use requestAnimationFrame technique described here: http://www.html5canvastutorials.com/advanced/html5-canvas-animation-stage/
I was able to achieve very smooth animations by using this code template:
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function animate() {
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
// update
// clear
context.clearRect(0, 0, canvas.width, canvas.height);
// draw stuff
// request new frame
requestAnimFrame(function() {
animate();
});
}
animate();
In general, setInterval() is fairly bad, use setTimeout() after each re-draw, since interval might come at the worst timing possible.
Note, depending on required browser versions support, you might want to drop setTimeout alltogether, as requestAnimationFrame is universally supported by now.
Update
A simple fix for the image loading without using extra tools within the code would be this, although it might run some animation cycles until all images will be loaded:
function Item(.. imgSrc) {
...
this.img = new Image();
this.imgSrc = imgSrc;
var self = this;
this.loaded = false;
img.onload = function() { self.loaded = true; }
//this function draws the image to the canvas
this.draw = function() {
if (this.loaded)
gameController.ctx.drawImage(this.img, this.x, this.y);
};