"OOP" in canvas correct way - javascript

I'm trying to draw something on canvas with sugar code that we received not that long time ago. I'm using babel of course. I have two questions. First of all you can find my code below:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
class Rectangle {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw() {
ctx.beginPath();
ctx.rect(this.x, this.y, 50, 50);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
};
move() {
document.addEventListener('keydown', event => {
if (event.keyCode === 37) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
this.x--
this.draw();
}
if (event.keyCode === 39) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
this.x++
this.draw();
}
})
}
}
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, 10, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
};
}
const rect = new Rectangle(130, 50);
const ball = new Ball(60, 40)
setInterval(() => {
rect.draw();
rect.move();
ball.draw();
}, 100);
I made two classes. One is rectangle, second ball. Rectangle is the one that can move with arrows. When i move, ball disapears for a brief second, then appears on the screen again. What am i doing wrong in this case? How game flow should look like properly?
Second question is: how can i make these two classes interract with each other? For example, i want that when rectangle will touch ball simple console.log will apear. Thanks

Most of the code is yours. Since I prefer using requestAnimationFrame I'm adding an identifier for the request: let requestId = null;.
The move() method of the rectangle deals only with the value of x, and takes the key as an attribute.
Also I've written the frame function that builds your animation frame by frame.
function frame() {
requestId = window.requestAnimationFrame(frame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.move(key);
rect.draw();
ball.draw();
}
On keydown you change the value for the key variable, you cancel the current animation, and you call the frame function to start a new animation.
I've added as well a keyup event to stop the animation on key up.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
let key;
let requestId = null;
class Rectangle {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw() {
ctx.beginPath();
ctx.rect(this.x, this.y, 50, 50);
ctx.fillStyle = "#000";
ctx.fill();
//ctx.closePath();
}
move(key) {
if (key == 37) {
this.x--;
}
if (key == 39) {
this.x++;
}
}
}
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
//ctx.closePath();
}
}
const rect = new Rectangle(130, 50);
const ball = new Ball(60, 40);
rect.draw();
ball.draw();
function frame() {
requestId = window.requestAnimationFrame(frame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.move(key);
rect.draw();
ball.draw();
}
document.addEventListener("keydown", event => {
if (requestId) {
window.cancelAnimationFrame(requestId);
}
key = event.keyCode;
frame();
});
document.addEventListener("keyup", event => {
if (requestId) {
window.cancelAnimationFrame(requestId);
}
});
canvas{border:1px solid;}
<canvas id="myCanvas"></canvas>
PS: Please read this article requestAnimationFrame for Smart Animating

Related

Javascript canvas drawimage() doesn't work after using clearrect() [duplicate]

This question already has answers here:
Flickering images in canvas animation
(2 answers)
Closed 1 year ago.
I try to make an animation using the following code:
var obst = new Obstacle(x, y)
obst.build()
function animate() {
ctx.clearRect(0, 0, innerWidth, innerHeight);
requestAnimationFrame(animate);
obst.update(-1, 0)
player1.build();
}
but obst var doesn't appear (but player1 appears)
class for obst:
class Obstacle {
constructor(x, y) {
this.x = x;
this.y = y;
}
build() {
var img = new Image();
img.src = 'assests/obst.png';
img.onload = () => {
ctx.drawImage(img, this.x, this.y, 60, 60);
}
}
update(x, y) {
this.x += x;
this.y += y;
this.build()
}
}
When I run the code without clearrect() syntax it shows as it should be.
Full code:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, innerWidth, innerHeight);
obst.update(-1, 0)
player1.build();
}
class Player {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
build() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
ctx.fillStyle = this.color;
ctx.fill();
}
update(x, y) {
this.x += x
this.y += y
this.build()
}
}
class Obstacle {
constructor(x, y) {
this.x = x;
this.y = y;
}
build() {
var img = new Image();
img.src = 'assests/obst.png';
img.onload = () => {
ctx.drawImage(img, this.x, this.y, 60, 60);
}
}
update(x, y) {
this.x += x;
this.y += y;
this.build()
}
}
var obst = new Obstacle(canvas.width, canvas.height / 2 - 30)
obst.build()
var player1 = new Player(canvas.width / 2, canvas.height / 2, 30, 'blue');
player1.build();
animate();
The async task of loading the image should be removed from the update loop. Have the objects follow a pattern where building and updating / drawing are independent, and where building is async and happens once...
class Obstacle {
constructor(x, y) {
this.x = x;
this.y = y;
}
// don't use the object until this promise resolves
async build() {
var img = new Image();
img.src = 'assests/obst.png';
return new Promise(resolve => img.onload = resolve)
}
// call the method that draws current state "draw"
draw() {
ctx.drawImage(img, this.x, this.y, 60, 60);
}
// call the method that updates current state and draws "update"
update(x, y) {
this.x += x;
this.y += y;
this.draw()
}
}
class Player {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
// for symmetry, and maybe someday player will need to do async work here
async build() {
return Promise.resolve();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
ctx.fillStyle = this.color;
ctx.fill();
}
update(x, y) {
this.x += x
this.y += y
this.draw()
}
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, innerWidth, innerHeight);
obst.update(-1, 0)
player1.update(0, 1);
}
async function play() {
const obst = new Obstacle(canvas.width, canvas.height / 2 - 30)
await obst.build();
const player1 = new Player(canvas.width / 2, canvas.height / 2, 30, 'blue');
await player1.build();
animate();
}
play()

Deleting other drawings while cleaning a canvas drawing

I am trying to make an application with canvas. But I need to delete a drawing I created in my application. I want the red to stay steady and the blue to reflect as I hold it down. But while cleaning the canvas, the previous blue drawings are also gone. How should I go about deleting a drawing? I also tried throwing it into an array and deleting it with remove() but it didn't work. If I delete it with its coordinates, all the drawings in those coordinates will also disappear.
const canvas = document.getElementById('canvas-pl-c-canvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Player {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
top() {
c.beginPath();
c.arc(this.x, this.y, 10, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update(nx, ny) {
this.x = nx;
this.y = ny;
}
}
const x = canvas.width / 2;
const y = canvas.height / 2;
var player = new Player(x, y, 30,'red');
player.top();
addEventListener('mousemove', (e) => {
c.clearRect(0, 0, canvas.width, canvas.height)
player.update(e.clientX, e.clientY);
player.top();
if(clck == true){
var wd = new Player(e.clientX, e.clientY, 300,'blue');
wd.top();
}
})
var clck=false;
canvas.addEventListener('mouseup', (e) => {
clck=false;
})
canvas.addEventListener('mousedown', (e) => {
clck=true;
})
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#canvas-pl-c-canvas:hover{
cursor: none;
}
<canvas id="canvas-pl-c-canvas"></canvas>
In the above snippet I'm trying to get the blue to draw without the red disappearing, but it doesn't work because the entire area is cleared with c.clearRect(0, 0, canvas.width, canvas.height). Is this the only way to clean it? How can I delete one of two overlapping drawings? How can I save my drawings somewhere and delete them?
const canvas = document.getElementById('canvas-pl-c-canvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// ctx.fillStyle='blue';
// ctx.fillRect(0,0,canvas.width,canvas.height)
class Player {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
top() {
c.beginPath();
c.arc(this.x, this.y, 10, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update(nx, ny) {
this.x = nx;
this.y = ny;
}
}
const x = canvas.width / 2;
const y = canvas.height / 2;
var player = new Player(x, y, 30,'red');
player.top();
addEventListener('mousemove', (e) => {
// c.clearRect(0, 0, canvas.width, canvas.height)
player.update(e.clientX, e.clientY);
player.top();
if(clck == true){
var wd = new Player(e.clientX, e.clientY, 300,'blue');
wd.top();
}
})
var clck=false;
canvas.addEventListener('mouseup', (e) => {
clck=false;
})
canvas.addEventListener('mousedown', (e) => {
clck=true;
})
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#canvas-pl-c-canvas:hover{
cursor: none;
}
<canvas id="canvas-pl-c-canvas"></canvas>
In this piece of code, I can draw blue as long as I hold it down, but it constantly writes red. I specially need to delete a drawing. I'm using completely vanilla js. I would be very grateful if you could help without suggesting a library. Thank you very much to everyone who has replied so far.

line rotation in class javascript canvas

I try to create a class with a function which might be use to change the angle of a line already drawn.
With what I write, the line doesn't move. When I press the right or left key, I have this error :
TypeError: this.changeAngle is not a function
Indeed, I don't have "function" keyword in my code ... I don't know what to use instead.
Could you help me ?
Thank you very much.
window.onload = init;
let canvas, ctx;
let mousePos;
let angle = 0;
class Lanceur {
constructor() {
this.changeAngle(this.angle);
}
update(ctx) {
this.drawAiguille(ctx);
}
drawSocleLanceur(ctx) {
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(w/2, h, 20, 0, 2 * Math.PI);
ctx.stroke();
ctx.restore();
}
drawAiguille(ctx) {
ctx.save();
ctx.rotate(this.angle);
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.lineWidth=3;
ctx.beginPath();
ctx.moveTo(w/2, h-h*0.12);
ctx.lineTo(w/2, h-h*0.035);
ctx.stroke();
ctx.restore();
}
changeAngle(a) {
this.angle = a;
}
getAngle() {
return this.angle;
}
}
function init() {
canvas = document.querySelector("#jeu");
ctx = canvas.getContext("2d");
w = canvas.width;
h = canvas.height;
a = new Lanceur();
requestAnimationFrame(mainloop);
}
function mainloop() {
ctx.clearRect(0, 0, w, h);
a.update(ctx);
requestAnimationFrame(mainloop);
}
document.addEventListener('keypress', function(event){
gereTouches(event);
});
function gereTouches(event) {
if(event.key == "ArrowRight") {
this.changeAngle(this.getAngle - 1);
console.log("ça bouge : " + this.angle);
}else if(event.key == "ArrowLeft") {
this.changeAngle(this.getAngle + 1);
}
}
Main changes:
added this.angle to thr constructor()
using keydown event
the function gereTouches(event) uses a instead of this and a.getAngle() instead of a.getAngle
also 1 for the the angle is way too big (those are radians). I'm using .01 instead.
I hope it helps.
window.onload = init;
let canvas, ctx;
let mousePos;
let angle = 0;
class Lanceur {
constructor() {
this.angle = 0;
this.changeAngle(this.angle);
}
update(ctx) {
this.drawAiguille(ctx);
}
drawSocleLanceur(ctx) {
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(w/2, h, 20, 0, 2 * Math.PI);
ctx.stroke();
ctx.restore();
}
drawAiguille(ctx) {
ctx.save();
ctx.rotate(this.angle);
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.lineWidth=3;
ctx.beginPath();
ctx.moveTo(w/2, h-h*0.12);
ctx.lineTo(w/2, h-h*0.035);
ctx.stroke();
ctx.restore();
}
changeAngle(a) {
this.angle = a;
}
getAngle() {
return this.angle;
}
}
function init() {
canvas = document.querySelector("#jeu");
ctx = canvas.getContext("2d");
w = canvas.width = 500;
h = canvas.height = 500;
a = new Lanceur();
requestAnimationFrame(mainloop);
}
function mainloop() {
ctx.clearRect(0, 0, w, h);
a.update(ctx);
requestAnimationFrame(mainloop);
}
document.addEventListener('keydown', function(event){
gereTouches(event);
});
function gereTouches(event) {
if(event.key == "ArrowRight") {
a.changeAngle(a.getAngle() - .01);
console.log("ça bouge : " + a.angle);
}else if(event.key == "ArrowLeft") {
a.changeAngle(a.getAngle() + .01);
}
}
canvas{border:1px solid}
<canvas id="jeu"></canvas>

Canvas: X value changing in console but not in canvas

I am pulling coordinates from an API and setting them to a class of Rectangle. When looping through the data array, I am able to create each rectangle, but when it comes to moving them, that is where I am having trouble.
The x coordinates are changing in the console at the setInterval of move(), but the squares themselves are not moving across the screen.
When I have c.clearRect(0,0,innerWidth, innerHeight); in move(), all of the rectangles disappear. Without it, they do not move at all.
if(this % 6 === 0){
c.fillStyle = "#000";
} else {
c.fillStyle = "" + this.color + "";
is referring to the data array and if the index is divisible by 6, make that square black. Although, in this context, it is not working.
Here is my code:
<script>
const canvas = document.getElementById("canvas");
const c = canvas.getContext('2d');
let xhReq = new XMLHttpRequest();
xhReq.open("GET", "(api here)", false);
xhReq.send(null);
const data = JSON.parse(xhReq.responseText);
class Rectangle {
constructor(x, y, w, h, vx, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.color = color;
}
draw() {
c.fillRect(this.x, this.y, this.w, this.h);
if(this % 6 === 0){
c.fillStyle = "#000";
} else {
c.fillStyle = "" + this.color + "";
}
}
move(){
c.clearRect(0,0,innerWidth, innerHeight);
if (this.x > 800 || this.x < 0){
this.vx = -this.vx;
}
this.x+= this.vx;
c.beginPath();
console.log("here is the x value:" + this.x);
}
}
for(let i=0; i<data.length; i++){
let info = data[i]
let rec = new Rectangle(info.x, info.y, info.w, info.h, info.vx, info.color);
rec.draw();
setInterval(function() {
rec.move();
}, 50);
}
</script>
There are a lot of issues with your code.
You are only updating the position and not making the draw call again.
Move the rec.draw() call inside your setInterval. Remember, ordering is important. Ideally, you should update() first and draw() later.
setInterval(function() {
rec.move();
rec.draw();
}, 50);
Also, remove these lines from the move() function as you don't want to clear the canvas every time you update the variables. Clear the canvas in the beginning of the draw() function. Plus, since you're dealing with fillRect(), you dont need to include beginPath()
c.clearRect(0,0,innerWidth, innerHeight);
...
c.beginPath();
You first need to specify the fillStyle then proceed with drawing the rectangle using fillRect(), not the other way round. The draw() function should look like this:
draw() {
c.clearRect(0, 0, innerWidth, innerHeight);
if(this % 6 === 0){ // <-- Not really sure what is going on here
c.fillStyle = "#000";
} else {
c.fillStyle = "" + this.color + "";
}
c.fillRect(this.x, this.y, this.w, this.h);
}
In the code block in point number #3 you are using the Modulus Operator % on the the instance of the class itself i.e. this and not a number. I'm assuming you want something like this.x in there.
Apart from the points above there are still a handful of issues with your code. But it's outside the context of this question.
Edited for a final solution below:
const canvas = document.getElementById("canvas");
const context = canvas.getContext('2d');
const xhr = new XMLHttpRequest();
// Usng var as we are making an async request and need to update this variable
var data = null;
// An empty array to store our rectangles
var collection = [];
// Using an asyn request as we don't necessarily need to wait for this step
xhr.open('GET', '<api>', true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
data = JSON.parse(xhr.responseText);
dataLoaded();
} else {
// The request gave an error response
}
}
};
xhr.send();
function dataLoaded(){
for(let i = 0; i < data.length; i++){
let info = data[i];
collection.push(new Rectangle(info.x, info.y, info.w, info.h, info.vx, info.color));
}
setInterval(animate, 50);
}
function animate(){
context.clearRect(0, 0, canvas.width, canvas.height);
for(let i = 0; i < collection.length; i++){
let rect = collection[i];
rect.update();
rect.draw();
}
}
class Rectangle {
constructor(x, y, w, h, vx, color){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.color = color;
}
update(){
if (this.x > 800 || this.x < 0){
this.vx = -this.vx;
}
this.x += this.vx;
}
draw(){
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
}

How do I redraw the background, so it will look like my "player" moving in html5-canvas

This is my JavaScript. I have an animate function, I think this is where the redrawing background code goes. I have tried to use document.getElementById to get the id of the canvas, and give the style rule of background: white. But it didn't work. All help is appreciated!:
function initCanvas(){
var ctx = document.getElementById('my_canvas').getContext('2d');
var cW = ctx.canvas.width, cH = ctx.canvas.height;
var dist = 10;
function Player(){
this.x = 0, this.y = 0, this.w = 50, this.h = 50;
ctx.fillStyle = "orange";
this.render = function(){
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
var player = new Player();
player.x = 100;
player.y = 225;
function animate(){
//This is where I think the background redrawing should go
player.render();
}
var animateInterval = setInterval(animate, 30);
document.addEventListener('keydown', function(event) {
var key_press = String.fromCharCode(event.keyCode);
if(key_press == "W"){
player.y-=dist;
} else if(key_press == "S"){
player.y+=dist;
} else if(key_press == "A"){
player.x-=dist;
} else if(key_press == "D"){
player.x+=dist;
}
});
}
window.addEventListener('load', function(event) {
initCanvas();
});
Here are a couple things I've noticed:
You didn't declare var canvas = document.getElementById('my_canvas');
Your initCanvas() wasn't firing(It wasn't for me)
Your render() function should reset the fillStyle before it draws your character. Having the fillStyle set when initializing the Player constructor is useless(It can be overridden).
Here is what your animate() function should look like:
function animate(){
ctx.fillStyle = '#000';
ctx.fillRect(0,0,canvas.width, canvas.height);
player.render();
}
Notice how fillRect() clears the canvas before drawing the player.
I have created a working JSFiddle with the fixes here.

Categories

Resources