javascript dissapearing object on canvas - javascript

I am writing a script to draw an image of the sun, and have an image of the earth orbit around it.
I have defined a planet class as so:
function planet(name, size, rotateRate, startx, starty, colour, scale, oRad, oAng, oSpd){//container class for planets
this.name = name;
this.rotateRate = rotateRate;
this.x = startx;
this.y = starty;
this.colour = colour;
this.scale = scale;
this.size = size;
this.orbitRadius= oRad;
this.orbitAngle = oAng;
this.orbitSpeed = oSpd;
this.drawByArc =
function drawArcCircle(){//draws circles using the arc method
context.save();
context.strokeStyle = '#000000';
context.fillStyle = this.colour;
context.lineWidth=3;
context.beginPath();
context.arc(this.x,this.y,this.size*this.scale,0,Math.PI * 2,false)
context.stroke();
context.fill();
context.closePath();
context.restore();
}
}
Now I have created two instances of the class in the program and drawn them fine using the following functions:
function gameLoop(){//The Game Loop
var thisTime = Date.now();//current time
var deltaTime = thisTime - lastTime;//find difference, not yet used
update();
draw();
lastTime = thisTime;
setTimeout(gameLoop, 1000/60);
}
function draw(){// Draws all the objects
drawBackground();
Sun.drawByArc();
Earth.drawByArc();
}
function update(){// Updates for animation
//var newRotation = this.getCurrantRotation() + (this.getRotationRate()*deltaTime);
var gSegments;
gScale = 4;
simSpeed = 10;
Sun.scale = gScale;
Earth.scale = gScale;
Earth.orbitSpeed = 360/simSpeed;
//Earth.x = Sun.x + Earth.orbitRadius * Math.cos(Earth.orbitAngle * Math.pi / 180);
//Earth.y = Sun.y - Earth.orbitRadius * Math.sin(Earth.orbitAngle * Math.pi / 180);
}
When i have the last two lines of the update method commented out, both circles draw fine, however when i add the final two lines in to attempt to update the earths position in orbit, when i attempt to run the code in chrome the Earth sphere vanishes!
Chrome debugger shows no errors so i'm at a loss as to why it occurs.
EDITED::
Well, I found that thanks to a small typing error (math.pi instead of Math.PI) my planet x and y values were becoming NaN.
however now my earth is stuck at at 90 degree point in its orbit and simply doesnt move, at least it draws, any ideas?

Solved it.
Most of the issues came from using math.pi instead of Math.PI
on top of that, i had not set a value to change the angle of orbit, meaning the orbit always remained at 90, making there be no orbit.
Chrome debug very much helped me in figuring all this out, so thanks a lot user1816548

Related

Splice (index,1) is removing more than one element from array

I am making a simple game in HTML Canvas. As a part of it, in the background i want to make falling stars which create the illusion of travelling. After the star reaches the end of the canvas i want to remove it. Each star is an instance of Star class, and depending on it's radius it has a different velocity. This is where problems start. When using constant velocity for every star, they disappear one by one like they should be. When velocity is changed, stars that "overtake" slower stars, when reached the end of the canvas, do not only dissapear themselves but also remove every star that was in array before them.
I have tried many solutions described below:
let canvas = document.querySelector("canvas");
let c = canvas.getContext('2d');
Star Class declaration:
class Star{
constructor(x, y, radius, color, velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity
}
draw(){
c.globalCompositeOperation='destination-over'
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
c.fillStyle = this.color;
c.shadowColor= "white"
c.shadowBlur=12
c.fill();
c.shadowBlur=0
}
update(){
this.draw();
this.y = this.y + this.radius/2;
}
}
Creating stars and adding it to array
let stars = [];
function createStar(){
setInterval(()=>{
//Create random radius and X position
let randomRadius = Math.floor((Math.random()*5))
let randomXPosition = Math.floor((Math.random()*780)+15)
let velocity = 1;
stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white",velocity));
console.log("stars:"+ stars.length);
},300)
}
Below here I use a function calling itself to clear and refresh the star drawing. I have tried looping through stars array with forEach method, reversed loop (like in example below), I tried putting the if statement with splice function in seperate setTimeout(()=>{},0) or setTimeout(()=>{},10). I tried using the condition like
(forEach method removes stars, however the number of active stars do not remain more or less the same. It constantly slowly increases)
function animate(){
c.clearRect(0, 0, canvas.width, canvas.height);
for(let i = stars.length-1; i>=0; i--){
stars[i].update()
if (stars[i].y > canvas.height +stars[i].radius ){
stars.splice(stars[i], 1)
}
}
requestAnimationFrame(animate);
}
animate();
createStar();
I tried using the condition like:
if (stars[i].y > 5000 ){
stars.splice(stars[i], 1)
}
But it's not how it's supposed to be solved, because stars live for 5000 pixels longer,what makes game laggy.
Just to be clear on the problem.
If i generate 5 stars every 300ms and push the into the array called "stars" I get e.g.
[star1, star2, star3, star4, star5]
Lets say star1 and star 2 have small radius and they move slowly. Star3 is bigger therefore it moves faster and overtakes first two. When star3 reaches canvas.height + star[i].radius it disappear just over the canvas, but it also makes every star in array that was before star3 disappear (in this case it's star1 and star2).
This is my first post on stackoverflow. I apologise for all understatements in advance.
HTML Canvas
<body>
<div class="container">
<button class="logOutButton">Log Out</button>
<button class="topScores">Top 10</button>
<header>
<p class="hello">Hello <span id="spanName"></span></p>
<p class="topScore">Your TOP score: <span id="score"></span></p>
</header>
<div class="gameTitle">
<h2 class="title">Space Warrior</h2>
</div>
<canvas class="canvas" width="800" height="500"></canvas>
</div>
<script src="User.js"></script>
</body>
EDIT
I changed stars.splice(stars[i], 1) to stars.splice(i, 1) - not working
I tried adding another removal array like below but array stars just slowly gets bigger (even though some elements get removed)
var removeStar = [];
// stars.forEach((star,starIndex)=>{
let total = stars.length-1;
for(let i = total; i>=0; i--){
stars[i].update();
if (stars[i].y > canvas.height + stars[i].radius){
removeStar.push(i);
}
};
for(let i = removeStar.length; i>0; i--){
stars.splice(removeStar[i-1],1)
}
you wrote:
stars.splice(stars[i], 1)
first argument should be index which you want to remove ...just index stars.splice(i, 1)
..another problem is that you changing the array while looping within it, which is bad idea.
see this answer to very similar question:
https://stackoverflow.com/a/65725703/3054380
After fixing the bug mentioned above and adding minVelocity and star limiting condition (because you adding with interval and removing when star goes off the canvas - we need to limit in case interval goes faster) ...now everything looks good - working snippet below (added maxStars minVelocity)
let canvas = document.querySelector(".mycanvas");
let c = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 150;
let maxStars = 60;
let minVelocity = 0.5;
let stars = [];
class Star {
constructor(x, y, radius, color, velocity) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity
}
draw() {
c.globalCompositeOperation = 'destination-over'
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.shadowColor = "white"
c.shadowBlur = 12
c.fill();
c.shadowBlur = 0
}
update() {
this.draw();
this.y = this.y + Math.max(minVelocity, this.velocity * this.radius / 2);
}
}
function createStar() {
if (stars.length < maxStars) {
//Create random radius and X position
let randomRadius = Math.floor((Math.random() * 5));
let randomXPosition = Math.floor((Math.random() * (canvas.width - 20)) + 10);
let velocity = 1;
stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white", velocity));
console.log("stars:" + stars.length);
}
}
function animate() {
c.clearRect(0, 0, canvas.width, canvas.height);
for (let i = stars.length - 1; i >= 0; i--) {
stars[i].update()
if (stars[i].y > canvas.height) {
stars.splice(i, 1)
}
}
requestAnimationFrame(animate);
}
setInterval(createStar, 50);
animate();
.mycanvas {
width: 300px;
height: 150px;
border: 1px solid #f00;
}
<body style="background-color:#000;color:#fff;">
<div class="container">
<canvas class="mycanvas"></canvas>
</div>
<script src="User.js"></script>
</body>
I think I found the issue causing your everexpanding array. It has nothing to do with your splice function, which is implemented incorrectly as webdev-dan mentioned in his answer.
follow this logic
The radius has been set with Math.floor((Math.random()*5)).This means the radius can be 0.
In your update method you are increasing y based on the radius in this.y = this.y + this.radius/2. So this is possibly changing y with 0.
In if (stars[i].y > 5000 ) you are removing values of y over 5000.
So if the radius is 0, y doesn't change, stars are never removed. Nor can you see them visually.
solution
You could guarantee a minimal speed of 1. this.y += Math.max(1, this.radius/2).
PS: I had to do quite a bit of refactoring to figure this out. You are writing your code in a too complex way. Several types of logic are mixed up.
You really want to separate out your rendering logic from management of the stars object.
It is also quite hard to mentally keep track of the stars array because you are modifying it from anywhere; inside for loops, with async code ( the interval ).
This is what I ended up while trying to clean up your code a bit: https://jsfiddle.net/5hk0vscg/1/ Hope it's useful. Note that it is not a full cleanup, but it's an improvement over your current code.

Issues with circle task in Canvas

I have been given the following task, but I am getting errors that can be seen when the code snippet is run. I would like some help figuring out what exactly I am doing wrong.
Basically, I need to draw a circle, make it so that it moves and changes the direction/color when touching the walls of the screen.
Task: create a Circle class with the following properties:
x - the initial value of the coordinate x
y is the initial value of the y coordinate
radius - values ​​of width and height
color - fill color Describe the methods:
draw () - marks off on the screen an element that is described by the given properties
setColor (newColor) - Changes the fill color to newColor
move ({x = 0, y = 0}) - moves the captured object by the vector (x, y) - each time period (for example, 100 ms) changes (adds \ subtracts)
to the values ​​x and y, respectively. When a circle collides with any
edge of the screen it is necessary to realize its mirror reflection
(change the value of the corresponding coordinate of the vector on the
opposite of the value of the sign, and call this method with the new
vector) and generate the collision event, collision, which is captured
at the document level.Hang on this event a handler that will change
the color of the pouring of the circle into another (random) value.
Movement occurs until the stop method is called.
stop () - stops the circle movement
If the Escape button on the keyboard was pressed, the movement should stop.
I created a canvas and set the frame to move. I drew a circle and tried to move it using setInterval(), but it seems like I'm losing the context.
let c = document.getElementById("mycanvas");
let ctx = c.getContext("2d");
let xinc = 1;
let yinc = 1;
class Circle {
constructor(xpos, ypos, radius, color) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.color = color;
}
draw() {
ctx.beginPath();
ctx.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
}
move(xpos, ypos) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
this.draw();
xpos += xinc;
ypos += yinc;
console.log(xpos, ypos);
if ((this.xpos > c.width - this.radius) || (this.xpos < 0 + this.radius)) {
xinc = -xinc;
}
if ((this.ypos > c.height - this.radius) || (this.ypos < 0 + this.radius)) {
yinc = -yinc;
}
setInterval(this.move, 10);
//this.draw();
}
}
let circle = new Circle(200, 300, 50, "red");
circle.draw();
circle.move(200, 300);
<canvas id="mycanvas" width="1335" height="650" style="border: 1px solid"> </canvas>
I am just starting to learn events and DOMs, please help me correctly implement this task
You are passing this.move to setInterval with no context - just a function, with no this to call it in. You can pass in this.move.bind(this) to create a bound function. You can also do it once in the constructor: this.move = this.move.bind(this).
Also, the call to beginPath in move seems unnecessary.

HTML5 Canvas atan2 off by 90 degrees

I was trying to get the green triangle to rotate about its center and orient itself towards the mouse position. I was able to accomplish this, and you can view the full code and result here:
https://codepen.io/Carpetfizz/project/editor/DQbEVe
Consider the following lines of code:
r = Math.atan2(mouseY - centerY, mouseX - centerX)
ctx.rotate(r + Math.PI/2)
I arbitrarily added Math.PI/2 to my angle calculation because without it, the rotations seemed to be 90 degrees off (by inspection). I want a better understanding of the coordinate system which atan2 is being calculated with respect to so I can justify the reason for offsetting the angle by 90 degrees (and hopefully simplify the code).
EDIT:
To my understanding, Math.atan2 is measuring the angle illustrated in blue. Shouldn't rotating both triangles that blue angle orient it towards the mouse mouse pointer (orange dot) ? Well - obviously not since it's the same angle and they are two different orientations, but I cannot seem to prove this to myself.
This is because of how the Math.atan2 works.
From MDN:
This is the counterclockwise angle, measured in radians, between the positive X axis, and the point (x, y).
In above figure, the positive X axis is the horizontal segment going from the junction to the right-most position.
To make it clearer, here is an interactive version of this diagram, where x, y values are converted to [-1 ~ 1] values.
const ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
radius = 0.3;
ctx.textAlign = 'center';
canvas.onmousemove = canvas.onclick = e => {
// offset mouse values so they are relative to the center of our canvas
draw(as(e.offsetX), as(e.offsetY));
}
draw(0, 0);
function draw(x, y) {
clear();
drawCross();
drawLineToPoint(x, y);
drawPoint(x, y);
const angle = Math.atan2(y, x);
drawAngle(angle);
writeAngle(angle);
}
function clear() {
ctx.clearRect(0, 0, w, h);
}
function drawCross() {
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(s(0), s(-1));
ctx.lineTo(s(0), s(1));
ctx.moveTo(s(-1), s(0));
ctx.lineTo(s(0), s(0));
ctx.strokeStyle = ctx.fillStyle = '#2e404f';
ctx.stroke();
// positive X axis
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(s(0), s(0));
ctx.lineTo(s(1), s(0));
ctx.stroke();
ctx.lineWidth = 1;
ctx.font = '20px/1 sans-serif';
ctx.fillText('+X', s(1) - 20, s(0) - 10);
}
function drawPoint(x, y) {
ctx.beginPath();
ctx.arc(s(x), s(y), 10, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
ctx.font = '12px/1 sans-serif';
ctx.fillText(`x: ${x.toFixed(2)} y: ${y.toFixed(2)}`, s(x), s(y) - 15);
}
function drawLineToPoint(x, y) {
ctx.beginPath();
ctx.moveTo(s(0), s(0));
ctx.lineTo(s(x), s(y));
ctx.strokeStyle = 'red';
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.setLineDash([0]);
}
function drawAngle(angle) {
ctx.beginPath();
ctx.moveTo(s(radius), s(0));
ctx.arc(s(0), s(0), radius * w / 2,
0, // 'arc' method also starts from positive X axis (3 o'clock)
angle,
true // Math.atan2 returns the anti-clockwise angle
);
ctx.strokeStyle = ctx.fillStyle = 'blue';
ctx.stroke();
ctx.font = '20px/1 sans-serif';
ctx.fillText('∂: ' + angle.toFixed(2), s(0), s(0));
}
// below methods will add the w / 2 offset
// because canvas coords set 0, 0 at top-left corner
// converts from [-1 ~ 1] to px
function s(value) {
return value * w / 2 + (w / 2);
}
// converts from px to [-1 ~ 1]
function as(value) {
return (value - w / 2) / (w / 2);
}
<canvas id="canvas" width="500" height="500"></canvas>
So now, if we go back to your image, it currently points to the top (positive Y axis), while the angle you just measured is realtive to the x axis, so it doesn't point where you intended.
Now we know the problem, the solution is quite easy:
either apply the + Math.PI / 2 offset to your angle like you did,
either modify your original image so that it points to the positive X axis directly.
The coordinate system on canvas works with 0° pointing right. This means anything you want to point "up" must be initially drawn right.
All you need to do in this case is to change this drawing:
to
pointing "up" 0°
and you can strip the math back to what you'd expect it to be.
var ctx = c.getContext("2d"), img = new Image;
img.onload = go; img.src = "https://i.stack.imgur.com/Yj9DU.jpg";
function draw(pos) {
var cx = c.width>>1,
cy = c.height>>1,
angle = Math.atan2(pos.y - cy, pos.x - cx);
ctx.setTransform(1,0,0,1,cx, cy);
ctx.rotate(angle);
ctx.drawImage(img, -img.width>>1, -img.height>>1);
}
function go() {
ctx.globalCompositeOperation = "copy";
window.onmousemove = function(e) {draw({x: e.clientX, y: e.clientY})}
}
html, body {margin:0;background:#ccc}
#c {background:#fff}
<canvas id=c width=600 height=600></canvas>
When you do arctangents in math class, you're generally dealing with an y-axis that increases going upwards. In most computer graphics systems, however, including canvas graphics, y increases going downward. [erroneous statement deleted]
Edit: I have to admit what I wrote before was wrong for two reasons:
A change in the direction of the axis would be compensated for by adding π, not π/2.
The canvas context rotate function rotates clockwise for positive angles, and that alone should compensate for the flip of the y-axis.
I played around with a copy of your code in Plunker, and now I realize the 90° rotation simply compensates for the starting orientation of the graphic image you're drawing. If the arrowhead pointed right to start with, instead of straight up, you wouldn't need to add π/2.
I encountered the same problem and was able to achieve the desired result with a following axis 'trick':
// Default usage (works fine if your image / shape points to the RIGHT)
let angle = Math.atan2(delta_y, delta_x);
// 'Tricky' usage (works fine if your image / shape points to the LEFT)
let angle = Math.atan2(delta_y, -delta_x);
// 'Tricky' usage (works fine if your image / shape points to the BOTTOM)
let angle = Math.atan2(delta_x, delta_y);
// 'Tricky' usage (works fine if your image / shape points to the TOP)
let angle = Math.atan2(delta_x, -delta_y);

In HTML5 Canvas and JS games, how might you check for collision between two objects when one's been rotated and the other hasn't?

I've been working on a game that's sort of a Worms clone. In it, the player rotates a cannon with the up up and down keys (it's a 2D game) to fire at enemies coming from above. I use the context.rotate() and context.translate() methods when drawing the cannon, then immediately context.restore() back to the default canvas.The cannon is the only thing (for now) that's rotated.
The problem is, I want to accurately show projectiles coming from the top of the cannon. For this, I need to know the top of the cannon's coordinates at all times. Normally, this is something I could easily calculate. However, because the canvas is rotated only before the cannon is drawn, it's not as simple.
Just use simple trigonometry to track the top:
var canonTopX = pivotX + Math.cos(angleInRadians) * canonLength;
var canonTopY = pivotY + Math.sin(angleInRadians) * canonLength;
You can choose to render the canon using transformations of course, or share the math.
ctx.translate(pivotX, pivotY);
ctx.rotate(angleInRadians);
//render canon from (0,0) pointing right (0°)
ctx.setTransform(1,0,0,1,0,0); // instead of save/restore
// calc canon top for projectiles here
var ctx = c.getContext("2d");
var canonLength = 70;
var angleInRadians = 0;
var angleStep = 0.03;
var pivotX = ctx.canvas.width>>1;
var pivotY = ctx.canvas.height>>1;
ctx.fillStyle = "#000";
ctx.strokeStyle = "#c00";
(function loop() {
angleInRadians += angleStep;
render();
requestAnimationFrame(loop);
})();
function render() {
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
ctx.translate(pivotX, pivotY);
ctx.rotate(angleInRadians);
ctx.fillRect(0, -5, canonLength, 10);
ctx.setTransform(1,0,0,1,0,0); // instead of save/restore
var canonTopX = pivotX + Math.cos(angleInRadians) * canonLength;
var canonTopY = pivotY + Math.sin(angleInRadians) * canonLength;
ctx.beginPath();
ctx.arc(canonTopX, canonTopY, 9, 0, 6.3);
ctx.stroke();
}
<canvas id=c width=600 height=180></canvas>

How can i draw a Square in HTML5 Canvas at run time?

I am working on a HTML5 Project.There is a drawing graphics API to draw Rectangle (fillRectStrokeRect).But how can i draw a SQUARE. I have tried the following way to draw it
CODE
getMouse(e);
x2=mx; y2=my;
var width=endX-startX;
var height=endY-startY;
annCanvasContext.beginPath();
annCanvasContext.lineWidth=borderWidth;
var centerX=width/2;
var centerY=width/2;
var radius=width/2;
annCanvasContext.arc(centerX+5, centerY+5, radius, 0, 2 * Math.PI, false);
annCanvasContext.stroke();
Use fillRect or strokeRect with the width and height being equal.
var x = 0, y = 0,
side = 10;
ctx.fillRect(x, y, side, side);
Demo
As you say in the comments, if you want to fit the largest square in a circle, it's more Math related than about code. I'll trying explaining it to you, but you'll probably find better, more visual explanations elsewhere on the Internet.
Draw the diameter of the circle in a way that it divides your square into two equal parts. Now one part is a right angled triangle, which has two of its sides equal. We know the diameter. Using the Pythogorean theorem, you get this equation:
side^2 + side^2 = diameter^2.
Let's find the side now.
2(side^2) = diameter^2
side^2 = (diameter^2)/2
side = Math.sqrt( (diameter^2)/2 )
Now, to turn this into code.
var ctx = document.getElementById('canvas').getContext('2d'),
radius = 20;
ctx.canvas.addEventListener('click', function (e){
ctx.fillStyle = 'black';
ctx.arc(e.pageX, e.pageY, radius, 0, Math.PI*2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = 'red';
var diameter = radius * 2;
var side = Math.sqrt( (diameter * diameter)/2 );
ctx.fillRect(e.pageX - side/2, e.pageY - side/2, side, side);
ctx.closePath();
}, false);
This would draw a square inside a circle wherever you click on the canvas.
Demo

Categories

Resources