I am trying to make an object move to click position, like in rpgs like diablo or modern moba games, i needed to find a way to animate to click coordinates and i have found a method that uses pythagorean theorem, i understood and custimized the code to a certain degree, but there is a bug where the ball keeps bouncing in the very end of animation. I know this happens because of the "moves" variable and the loop, but can't understand exactly why.
here's the function that draws the object
function drawPlayer() {
//
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(player.x, player.y, 12, 0, Math.PI*2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
//Calculate variables
let dx = player.newx - player.x;
let dy = player.newy - player.y;
let distance = Math.sqrt(dx*dx + dy*dy);
let moves = distance/player.speed;
let xunits = (player.newx - player.x)/moves;
let yunits = (player.newy - player.y)/moves;
if (moves > 0 ) {
moves--;
player.x += xunits;
player.y += yunits;
console.log(moves);
}
}
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.addEventListener('click', e => {
setClickCoords(e);
drawPlayer();
})
canvas.width = 300;
canvas.height = 300;
const player = {
x: 0,
y: 0,
newx: 0,
newy: 0,
speed: 1,
radius: 15,
}
function drawPlayer() {
//Draw
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(player.x, player.y, 12, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
//Calculate variables
let dx = player.newx - player.x;
let dy = player.newy - player.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let moves = distance / player.speed;
let xunits = (player.newx - player.x) / moves;
let yunits = (player.newy - player.y) / moves;
console.log(moves);
if (moves > 0) {
moves--;
player.x += xunits;
player.y += yunits;
console.log(moves);
}
}
function setClickCoords(e) {
player.newx = e.offsetX;
player.newy = e.offsetY;
document.getElementById('oldcoords').innerHTML = "old Coords " + player.x + " " + player.y;
document.getElementById('newcoords').innerHTML = "new Coords " + player.newx + " " + player.newy;
}
function gameLoop() {
window.setTimeout(gameLoop, 24);
drawPlayer()
}
gameLoop();
body {
background: black;
display: flex;
flex-direction: column;
}
#canvas1 {
border: 3px solid white;
top: 50%;
left: 50%;
position: absolute;
width: 300px;
height: 300px;
transform: translate(-50%, -50%);
}
#oldcoords,
#newcoords {
color: white;
font-size: 18px;
}
<span id="oldcoords"></span>
<span id="newcoords"></span>
<canvas id="canvas1"> </canvas>
Check if moves is between 0 and 1. If it's between, move the ball to the desired position. Currently, the ball goes too far in the direction and has to return in the next animation.
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.addEventListener('click', e => {
setClickCoords(e);
drawPlayer();
})
canvas.width = 300;
canvas.height = 300;
const player = {
x: 0,
y: 0,
newx: 0,
newy: 0,
speed: 1,
radius: 15,
}
function drawPlayer() {
//Draw
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(player.x, player.y, 12, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
//Calculate variables
let dx = player.newx - player.x;
let dy = player.newy - player.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let moves = distance / player.speed;
console.log(moves);
if (moves > 1) {
let xunits = (player.newx - player.x) / moves;
let yunits = (player.newy - player.y) / moves;
moves--;
player.x += xunits;
player.y += yunits;
console.log(moves);
} else if (moves > 0) {
moves = 0;
player.x = player.newx;
player.y = player.newy;
console.log(moves);
}
}
function setClickCoords(e) {
player.newx = e.offsetX;
player.newy = e.offsetY;
document.getElementById('oldcoords').innerHTML = "old Coords " + player.x + " " + player.y;
document.getElementById('newcoords').innerHTML = "new Coords " + player.newx + " " + player.newy;
}
function gameLoop() {
window.setTimeout(gameLoop, 24);
drawPlayer()
}
gameLoop();
body {
background: black;
display: flex;
flex-direction: column;
}
#canvas1 {
border: 3px solid white;
top: 50%;
left: 50%;
position: absolute;
width: 300px;
height: 300px;
transform: translate(-50%, -50%);
}
#oldcoords,
#newcoords {
color: white;
font-size: 18px;
}
<span id="oldcoords"></span>
<span id="newcoords"></span>
<canvas id="canvas1"> </canvas>
Related
I have made a simple rocket with controls and I want it to stop when it hits the ground but for whatever reason the only number that I can use to test it against is 0. I have tried context.canvas.height - this.size.height because that is what I want it to be, but when I use that in the if statement the code does not work as desired. The goal is to be able to stop all of the rocket's movement when it hits the ground.
Here is my JavaScript code
(function () {
// the canvas constants
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
// setting the canvas size
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
// the game constants
const ROCKET_SIZE = { width: 20, height: 30 };
const ROCKET_POSITION = { x: 200, y: 200 };
const GRAVITY = 2;
const THRUST = 5;
const AFTERBURNER_THRUST = 20;
const INERTIAL_DAMPING = 0.98;
// the rocket ship class
class RocketShip {
constructor(size, position) {
this.color = 'white';
this.size = size;
this.position = position;
this.angle = 0;
this.engineOn = false;
this.afterburner = false;
this.inertialDampeners = false;
this.rotatingLeft = false;
this.rotatingRight = false;
this.velocity = {
x: 0,
y: 0,
};
}
draw() {
// the center of the rocket
const triangleCenterX = this.position.x + 0.5 * this.size.width;
const triangleCenterY = this.position.y + 0.5 * this.size.height;
context.save();
context.translate(triangleCenterX, triangleCenterY);
context.rotate(this.angle);
context.lineWidth = 1;
context.beginPath();
// drawing the delta rocket
context.moveTo(0, -this.size.height / 2);
context.lineTo(-this.size.width / 2, this.size.height / 2);
context.lineTo(this.size.width / 2, this.size.height / 2);
context.closePath();
context.strokeStyle = this.color;
context.stroke();
// drawing the engine if it is active
if (this.engineOn) {
const fireYPos = this.size.height / 2 + 5;
const fireXPos = this.size.width * 0.25;
context.beginPath();
context.moveTo(-fireXPos, fireYPos);
context.lineTo(fireXPos, fireYPos);
context.lineTo(0, fireYPos + Math.random() * 50);
context.lineTo(-fireXPos, fireYPos);
context.closePath();
context.fillStyle = 'orange';
context.fill();
}
// drawing the afterburner if it is active
if (this.afterburner) {
const fireYPos = this.size.height / 2 + 5;
const fireXPos = this.size.width * 0.25;
context.beginPath();
context.moveTo(-fireXPos, fireYPos);
context.lineTo(fireXPos, fireYPos);
context.lineTo(0, fireYPos + Math.random() * 100);
context.lineTo(-fireXPos, fireYPos);
context.closePath();
context.fillStyle = 'red';
context.fill();
}
context.restore();
}
moveRocket() {
// convert angle from degrees to radians
const degToRad = Math.PI / 180;
// changing the position of the rocket
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
// moving the rocket to the other side of the screen
if (this.position.x > context.canvas.width) {
this.position.x = 0;
}
if (this.position.x < 0) {
this.position.x = context.canvas.width;
}
if (this.position.y > context.canvas.height) {
this.position.y = 0;
}
if (this.position.y < 0) {
this.position.y = context.canvas.height;
}
// stopping the rocket if it hits the ground
if (this.position.y === context.canvas.height - this.size.height) {
this.velocity.y = -GRAVITY / 100;
this.velocity.x = 0;
}
// turning the rocket
if (this.rotatingLeft) {
this.angle -= degToRad;
}
if (this.rotatingRight) {
this.angle += degToRad;
}
// accelerating the rocket if the thrust is on
if (this.engineOn) {
this.velocity.x += (THRUST / 100) * Math.sin(this.angle);
this.velocity.y -= (THRUST / 100) * Math.cos(this.angle);
}
// accelerating the rocket if the afterburner is on
if (this.afterburner) {
this.velocity.x += (AFTERBURNER_THRUST / 100) * Math.sin(this.angle);
this.velocity.y -= (AFTERBURNER_THRUST / 100) * Math.cos(this.angle);
}
// decelerating the rocket if the inertial dampeners are on
if (this.inertialDampeners) {
this.velocity.x *= INERTIAL_DAMPING;
this.velocity.y *= INERTIAL_DAMPING;
}
// updating the rocket's velocity due to gravity
this.velocity.y += GRAVITY / 100;
}
}
const rocket = new RocketShip(ROCKET_SIZE, ROCKET_POSITION);
// handling the keyboard events
function handleKeyInput(event) {
const { keyCode, type } = event;
const isKeyDown = type === 'keydown';
if (keyCode === 37) {
rocket.rotatingLeft = isKeyDown;
}
if (keyCode === 39) {
rocket.rotatingRight = isKeyDown;
}
if (keyCode === 38) {
rocket.engineOn = isKeyDown;
}
if (keyCode === 32) {
rocket.afterburner = isKeyDown;
}
if (keyCode === 40) {
rocket.inertialDampeners = isKeyDown;
}
}
// resizing the screen
function onResize() {
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
}
function draw() {
// drawing space
context.fillStyle = '#111';
context.fillRect(0, 0, canvas.width, canvas.height);
rocket.moveRocket();
// drawing the rocket
rocket.draw();
// repeating the draw function
requestAnimationFrame(draw);
}
// adding event listeners to the webpage
document.addEventListener('keydown', handleKeyInput);
document.addEventListener('keyup', handleKeyInput);
document.addEventListener('resize', onResize);
// starting the game
draw();
})();
My HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rocket</title>
<link rel=stylesheet href="styles/rocket2.css" >
</head>
<body id="body">
<canvas id="game"></canvas>
<script src="pscripts/rocket/rocket.js"></script>
<button class="info-btn">
Hello World
</button>
</body>
</html>
My CSS:
#body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #111;
}
#game {
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.info-btn {
opacity: 1;
position: absolute;
top: 2vh;
right: 2vw;
color: white;
background-color: dimgrey;
font-family: "Agency FB";
font-size: 2vh;
cursor: pointer;
}
.info-btn:hover {
background-color: #4f4f4f;
color: #c7c7c7;
}
.info {
background-color: #4f4f4f;
padding: 2%;
width: 98%;
height: 98%;
}
The code that I have been trying to use to stop the rocket when it reaches context.canvas.height - this.size.height is:
if (this.position.y === context.canvas.height - this.size.height) {
this.velocity.y = -GRAVITY / 100;
this.velocity.x = 0;
}
I am trying to check if the y position of the rocket is equal to the canvas height - the height of the rocket and if so I set the y velocity to the opposite of gravity over 100 so that it has no movement and I set the x velocity to 0.
Thank you to anyone who reads this and tries to help me.
Because you are using === your code will only work when they are equal which is almost never going to happen in an animation. The object could pass the given point between frames. You should use >=
if (this.position.y >= context.canvas.height - this.size.height) {
this.velocity.y = -GRAVITY / 100;
this.velocity.x = 0;
}
I'm making a menu for my Sudoku game to choose numbers for selected cells.
I managed to do a circular animated menu with X canvas elements each representing one number (+ one is empty). Then I rotate all these elements with CSS transform to create a perfect circle.
And here the problem appears - there are ugly transparent strokes between each element that I can't manage to remove.
This is how I draw it:
My app is a bit complicated but I managed to create a demo:
let parent = document.getElementsByClassName('menu')[0];
let elSize = parent.getBoundingClientRect().width;
let upscale = 2;
let total = 10;
let length = elSize / 2;
for (let i = 0; i < total; i++) {
// create new canvas
let val = document.createElement('canvas');
val.classList.add("value");
let deg = 360 / total;
//set sizes and rotation
val.height = length * upscale;
val.width = elSize * upscale;
val.style.width = elSize + "px";
val.style.height = length + "px";
val.style.setProperty("--rotation", (i / total * 360) + "deg");
// get context
let ctx = val.getContext("2d");
ctx.fillStyle = "blue";
ctx.imageSmoothingEnabled = true;
// full circle center
let center = {
x: length * upscale,
y: length * upscale
}
//function to fill the circle part (step 1 and 2 on the image)
const fillWedge = (cx, cy, radius, startAngle, endAngle, fillcolor, stroke = false) => {
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
if (stroke) {
ctx.lineWidth = 1;
ctx.strokeStyle = fillcolor;
ctx.stroke();
} else {
ctx.fillStyle = fillcolor;
ctx.fill();
}
}
const degToAngle = (deg) => {
let start = -Math.PI / 2;
let fullCircle = Math.PI * 2;
return (start + fullCircle * (deg / 360));
}
ctx.save();
ctx.imageSmoothingEnabled = false;
ctx.globalCompositeOperation = "source-out"; {
//smaller circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale / 3;
let startAngle = -((deg + 1) / 2) % 360;
let endAngle = ((deg + 1) / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
//make it semi-transparent
ctx.globalAlpha = 0.8; {
//bigger circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale;
let startAngle = -(deg / 2) % 360;
let endAngle = (deg / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
ctx.restore();
//draw text
if (i !== 0) {
ctx.save();
ctx.translate(length * upscale, length / 3 * upscale);
ctx.rotate(-(i / total * 360) / 180 * Math.PI);
ctx.font = "600 " + (18 * upscale) + 'px Consolas';
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText((i) + "", 0, 5 * upscale);
ctx.restore()
}
//add element to menu
parent.appendChild(val);
}
html {
background: url(https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.cienradios.com%2Fwp-content%2Fuploads%2Fsites%2F13%2F2020%2F06%2FShrek-portada.jpg&f=1&nofb=1) no-repeat center;
display: flex;
justify-content: center;
align-content: center;
}
.menu {
width: 400px;
aspect-ratio: 1;
position: relative;
border-radius: 50%;
}
.menu .value {
--rotation: 0deg;
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
transform-origin: bottom;
transform: translate(-50%, 0) rotate(var(--rotation));
}
p {
color: white;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sudoku Test</title>
</head>
<body>
<div class="menu">
</div>
<p>
See, it's semi-transparent and you can clearly see lines between elements
<br>
<b>How to fix it?!</b>
</p>
</body>
</html>
Any way to remove those spaces? I know it's a bit too much detailed question, but I really can't manage to fix it. Thanks.
These are caused by antialiasing. Since you do draw on diagonals, the shape doesn't fall on full pixel boundaries. So to smooth the lines, the browser will make those pixels that should have been painted only partially, more transparent. When stacked these transparent pixels won't add up exactly to full opacity (0.5 opacity + 0.5 opacity = 0.75 opacity, not 1). So you'll see these lines.
Removing the smoothing here won't help, because the alternative would be to fill in a marching-square fashion, but that would result in either complete holes in some places, either in overlapping pixels, which would be visible since your shapes aren't fully opaque.
Usually the cheap trick for that issue is to stroke a couple of pixels around the shape in the same color as it's filled. But once again since your shapes are filled with semi-transparent colors this trick won't do it.
You could hack something around by drawing all your shapes at full opacity, and applying the transparency on a common container. But this means that your texts would need their own canvas, and their own container (otherwise they'd be transparent too).
let parent = document.getElementsByClassName('menu')[0];
const textsParent = document.getElementsByClassName('texts')[0];
let elSize = parent.getBoundingClientRect().width;
let upscale = 2;
let total = 10;
let length = elSize / 2;
for (let i = 0; i < total; i++) {
// create new canvas
let val = document.createElement('canvas');
val.classList.add("value");
let deg = 360 / total;
//set sizes and rotation
val.height = length * upscale;
val.width = elSize * upscale;
val.style.width = elSize + "px";
val.style.height = length + "px";
val.style.setProperty("--rotation", (i / total * 360) + "deg");
// get context
let ctx = val.getContext("2d");
ctx.fillStyle = "blue";
// full circle center
let center = {
x: length * upscale,
y: length * upscale
}
//function to fill the circle part (step 1 and 2 on the image)
const fillWedge = (cx, cy, radius, startAngle, endAngle, fillcolor, stroke = false) => {
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
ctx.lineWidth = 2;
ctx.strokeStyle = fillcolor;
ctx.stroke();
ctx.fillStyle = fillcolor;
ctx.fill();
}
const degToAngle = (deg) => {
let start = -Math.PI / 2;
let fullCircle = Math.PI * 2;
return (start + fullCircle * (deg / 360));
}
ctx.save();
ctx.imageSmoothingEnabled = false;
{
//smaller circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale / 3;
let startAngle = -((deg + 1) / 2) % 360;
let endAngle = ((deg + 1) / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
{
//bigger circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale;
let startAngle = -(deg / 2) % 360;
let endAngle = (deg / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
ctx.restore();
//draw text
if (i !== 0) {
// we need a new canvas just for the text
const val2 = val.cloneNode();
const ctx = val2.getContext("2d");
ctx.save();
ctx.translate(length * upscale, length / 3 * upscale);
ctx.rotate(-(i / total * 360) / 180 * Math.PI);
ctx.font = "600 " + (18 * upscale) + 'px Consolas';
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText((i) + "", 0, 5 * upscale);
ctx.restore()
textsParent.appendChild(val2);
}
//add element to menu
parent.appendChild(val);
}
html {
background: url(https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.cienradios.com%2Fwp-content%2Fuploads%2Fsites%2F13%2F2020%2F06%2FShrek-portada.jpg&f=1&nofb=1) no-repeat center;
display: flex;
justify-content: center;
align-content: center;
}
.menu, .texts {
width: 400px;
aspect-ratio: 1;
position: relative;
border-radius: 50%;
}
.menu .value, .texts canvas {
--rotation: 0deg;
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
transform-origin: bottom;
transform: translate(-50%, 0) rotate(var(--rotation));
}
.menu { opacity: 0.8 }
.text-container {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-content: center;
}
<div class="menu">
</div>
<div class="text-container">
<div class="texts">
</div>
</div>
So instead the best is probably to refactor your code entirely to use a single canvas instead. If you tell the browser to draw all your shapes in a single subpath, it will be able to place the tracing perfectly where it should be and will be able to draw all this without any line in between:
const canvas = document.querySelector("canvas");
canvas.width = canvas.height = 500;
const ctx = canvas.getContext("2d");
const parts = 10;
const theta = Math.PI / (parts / 2);
const trace = (cx, cy, r1, r2, t) => {
ctx.moveTo(cx + r2, cy);
ctx.lineTo(cx + r1, cy);
ctx.arc(cx, cy, r1, 0, t);
ctx.arc(cx, cy, r2, t, 0, true);
};
ctx.translate(250, 250);
ctx.rotate(-Math.PI / 2 - theta);
// draw all but the last part
for (let i = 0; i < parts - 1; i++) {
ctx.rotate(theta);
trace(0, 0, 50, 200, theta);
}
ctx.globalAlpha = 0.8;
ctx.fillStyle = "blue";
ctx.fill(); // in a single pass
// draw the last part in red
ctx.fillStyle = "red";
ctx.rotate(theta);
ctx.beginPath();
trace(0, 0, 50, 200, theta);
ctx.fill();
canvas {
/* checkered effect from https://stackoverflow.com/a/51054396/3702797 */
--tint:rgba(255,255,255,0.9);background-image:linear-gradient(to right,var(--tint),var(--tint)),linear-gradient(to right,black 50%,white 50%),linear-gradient(to bottom,black 50%,white 50%);background-blend-mode:normal,difference,normal;background-size:2em 2em;
}
<canvas></canvas>
I want the triangle to aim towards the center at all time while it is spinning as it is right now
var element = document.getElementById('background');
var ctx = element.getContext("2d");
var camera = {};
camera.x = 0;
camera.y = 0;
var scale = 1.0;
var obj = [];
var t = {};
t.angle = Math.random() * Math.PI * 2; //start angle
t.radius = 200;
t.x = Math.cos(t.angle) * t.radius; // start position x
t.y = Math.sin(t.angle) * t.radius; //start position y
t.duration = 10000; //10 seconds per rotation
t.rotSpeed = 2 * Math.PI / t.duration; // Rotational speed (in radian/ms)
t.start = Date.now();
obj.push(t);
function update() {
for (var i = 0; i < obj.length; i++) {
var delta = Date.now() - obj[i].start;
obj.start = Date.now();
var angle = obj[i].rotSpeed * delta;
// The angle is now already in radian, no longer need to convert from degree to radian.
obj[i].x = obj[i].radius * Math.cos(angle);
obj[i].y = obj[i].radius * Math.sin(angle);
}
}
function draw() {
update();
ctx.clearRect(0, 0, element.width, element.height);
ctx.save();
ctx.translate(0 - (camera.x - element.width / 2), 0 - (camera.y - element.height / 2));
ctx.scale(scale, scale);
ctx.fillStyle = 'blue';
for (var i = 0; i < obj.length; i++) {
/*Style circle*/
ctx.beginPath();
ctx.arc(0, 0, obj[i].radius, 0, Math.PI * 2);
ctx.lineWidth = 60;
ctx.strokeStyle = "black";
ctx.stroke();
//Dot style
ctx.beginPath();
ctx.arc(obj[i].x,obj[i].y,10,0,Math.PI*2);
ctx.moveTo(0 + obj[i].x, 0 + obj[i].y);
ctx.lineTo(75 + obj[i].x, 25 + obj[i].y);
ctx.lineTo(75 + obj[i].x, -25 + + obj[i].y);
ctx.lineWidth = 1.5;
ctx.strokeStyle = "red";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
}
ctx.restore();
requestAnimationFrame(draw);
}
draw();
<canvas id="background" width="500" height="500"></canvas>
Here is a quick fiddle that has all the code already in it just for convenience :)
https://jsfiddle.net/4xwmo5tj/5/
I have already made it spin (the dot is there for now but will be removed later so that can be ignored) the only thing that I still need to do is aim it towards the center at all time. I think I have to use css transform translate for it. but I don't know how to
Use CSS animations.
.outer-circle {
height: 200px;
width: 200px;
background-color: black;
padding: 25px;
position: relative;
border-radius: 50%;
-webkit-animation:spin 4s linear infinite;
-moz-animation:spin 4s linear infinite;
animation:spin 4s linear infinite;
}
.inner-cricle {
width: 150px;
height: 150px;
background: white;
margin: auto;
margin-top: 25px;
border-radius: 50%;
}
.triangle {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid #f00;
position: absolute;
left: 40%;
top: 6%;
}
#-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
#-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
#keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
<div class="outer-circle">
<div class="triangle"></div><div class="inner-cricle"></div>
</div>
The triangle is not spinning at all. There is just a red dot circle in circumference and triangle inside black circle which is still. So what do you want to do?
The block of the code responsible for the triangle is under // Dot Style. The lines ctx.lineTo plot two dots at (x,y) coordinates. I fiddle around with the two coordinates until the triangle was facing down. Below is the end result:
var element = document.getElementById('background');
var ctx = element.getContext("2d");
var camera = {};
camera.x = 0;
camera.y = 0;
var scale = 1.0;
var obj = [];
var t = {};
t.angle = Math.random() * Math.PI * 2; //start angle
t.radius = 200;
t.x = Math.cos(t.angle) * t.radius; // start position x
t.y = Math.sin(t.angle) * t.radius; //start position y
t.duration = 10000; //10 seconds per rotation
t.rotSpeed = 2 * Math.PI / t.duration; // Rotational speed (in radian/ms)
t.start = Date.now();
obj.push(t);
function update() {
for (var i = 0; i < obj.length; i++) {
var delta = Date.now() - obj[i].start;
obj.start = Date.now();
var angle = obj[i].rotSpeed * delta;
// The angle is now already in radian, no longer need to convert from degree to radian.
obj[i].x = obj[i].radius * Math.cos(angle);
obj[i].y = obj[i].radius * Math.sin(angle);
}
}
function draw() {
update();
ctx.clearRect(0, 0, element.width, element.height);
ctx.save();
ctx.translate(0 - (camera.x - element.width / 2), 0 - (camera.y - element.height / 2));
ctx.scale(scale, scale);
ctx.fillStyle = 'blue';
for (var i = 0; i < obj.length; i++) {
/*Style circle*/
ctx.beginPath();
ctx.arc(0, 0, obj[i].radius, 0, Math.PI * 2);
ctx.lineWidth = 60;
ctx.strokeStyle = "white";
ctx.stroke();
//Dot style
ctx.beginPath();
ctx.arc(obj[i].x, obj[i].y, 10, 0, Math.PI * 2);
ctx.moveTo(0 + obj[i].x, 0 + obj[i].y);
ctx.lineTo(25 + obj[i].x, -75 + obj[i].y);
ctx.lineTo(-25 + obj[i].x, -75 + obj[i].y);
ctx.lineWidth = 1.5;
ctx.strokeStyle = "red";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
}
ctx.restore();
requestAnimationFrame(draw);
}
draw();
#background {
background: #000000;
border: 1px solid black;
}
<canvas id="background" width="500" height="500"></canvas>
Sorry, I can't provide any more specific details. The Cavas API and drawing are not my turfs.
I am trying to change the number of bouncing balls in a simulation. I am passing the required number using Socket.IO, but I'm struggling to change the number of balls. Here is the JavaScript:
var width = 100,
height = 200,
numBalls,
balls;
$(document).ready(function() {
var socket = io();
socket.on('message', function (data) {
console.log(data.count);
numBalls = data.count
});
$('#myCanvas').click(bounce);
// create an array of balls
balls = new Array(numBalls);
for(i = 0 ; i < numBalls ; i++){
balls[i] = new Ball();
}
});
function Ball(){
// random radius
this.radius = Math.floor(Math.random()*(10-5+1))+5;
// random x and y
this.x = Math.floor(Math.random()*(width-this.radius+1))+this.radius;
this.y = Math.floor(Math.random()*(width-this.radius+1))+this.radius;
// random direction, +1 or -1
this.dx = Math.floor(Math.random()*2) * 2 - 1;
this.dy = Math.floor(Math.random()*2) * 2 - 1;
//random colour, r, g or b
var rcol = Math.floor(Math.random()*3);
this.col = rcol==0 ? "red" :
rcol==1 ? "blue" : "green";
}
// draw the balls on the canvas
function draw(){
var canvas = document.getElementById("myCanvas");
// check if supported
if(canvas.getContext){
var ctx=canvas.getContext("2d");
//clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 0.5;
ctx.strokeStyle="black";
// draw each ball
for(i = 0; i < numBalls ; i++){
var ball = balls[i];
ctx.fillStyle=ball.col;
ctx.beginPath();
// check bounds
// change direction if hitting border
if(ball.x<=ball.radius ||
ball.x >= (width-ball.radius)){
ball.dx *= -1;
}
if(ball.y<=ball.radius ||
ball.y >= (height-ball.radius)){
ball.dy *= -1;
}
// move ball
ball.x += ball.dx;
ball.y += ball.dy;
// draw it
ctx.arc(ball.x, ball.y, ball.radius, 0, 2*Math.PI, false);
ctx.stroke();
ctx.fill();
}
}
else{
//canvas not supported
}
}
// calls draw every 10 millis
function bounce(){
setInterval(draw, 10);
}
Let's say newNumBalls is the new number of balls.
If newNumBalls is less than numBalls, you want to remove elements from balls. You can do that by taking a slice of balls and assigning it to balls.
If newNumBalls is greater than numBalls, you want to make new balls and add them to balls. You can do that with push.
The complete logic is this:
if (newNumBalls < numBalls) {
balls = balls.slice(0, newNumBalls);
} else {
for (var i = numBalls; i < newNumBalls; ++i) {
balls.push(new Ball());
}
}
numBalls = newNumBalls;
Below is a snippet that implements this logic.
var width,
height,
numBalls = 10,
balls;
$('#setNumBalls').click(function () {
var newNumBalls = parseInt($('#inputNumBalls').val(), 10);
if (newNumBalls < numBalls) {
balls = balls.slice(0, newNumBalls);
//$('#display').html('Removed ' + (numBalls - newNumBalls) + ' balls');
} else {
for (var i = numBalls; i < newNumBalls; ++i) {
balls.push(new Ball());
}
//$('#display').html('Added ' + (newNumBalls - numBalls) + ' new balls');
}
numBalls = newNumBalls;
});
$(document).ready(function() {
width = $('#myCanvas').width();
height = $('#myCanvas').height();
var canvas = $('#myCanvas')[0];
canvas.width = width;
canvas.height = height;
$('#inputNumBalls').val(numBalls);
// create an array of balls
balls = new Array(numBalls);
for(i = 0 ; i < numBalls ; i++){
balls[i] = new Ball();
}
bounce();
});
function Ball(){
// random radius
this.radius = Math.floor(Math.random()*(10-5+1))+5;
// random x and y
var margin = 2 * this.radius;
this.x = Math.floor(Math.random()*(width-margin))+margin/2;
this.y = Math.floor(Math.random()*(width-margin+1))+margin/2;
// random direction, +1 or -1
this.dx = Math.floor(Math.random()*2) * 2 - 1;
this.dy = Math.floor(Math.random()*2) * 2 - 1;
//random colour, r, g or b
var rcol = Math.floor(Math.random()*3);
this.col = rcol==0 ? "red" :
rcol==1 ? "blue" : "green";
}
// draw the balls on the canvas
function draw(){
var canvas = $('#myCanvas')[0];
// check if supported
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
//clear canvas
ctx.clearRect(0, 0, width, height);
ctx.globalAlpha = 0.5;
ctx.strokeStyle="black";
// draw each ball
for(var i = 0; i < numBalls ; i++){
var ball = balls[i];
ctx.fillStyle = ball.col;
ctx.beginPath();
// check bounds
// change direction if hitting border
if(ball.x <= ball.radius ||
ball.x >= (width - ball.radius)) {
ball.dx *= -1;
}
if(ball.y <= ball.radius ||
ball.y >= (height - ball.radius)) {
ball.dy *= -1;
}
// move ball
ball.x += ball.dx;
ball.y += ball.dy;
// draw it
ctx.arc(ball.x, ball.y, ball.radius, 0, 2*Math.PI, false);
ctx.stroke();
ctx.fill();
}
}
else{
//canvas not supported
}
}
// Calls draw frameRate times a second.
function bounce() {
var frameRate = 60;
setInterval(draw, 1000 / frameRate);
}
body {
font-family: sans-serif;
}
#myCanvas {
float: left;
margin: 0 10px 0 0;
width: 160px;
height: 160px;
border: 1px solid #888;
}
#inputNumBalls {
font-size: 18px;
padding: 5px 8px;
margin: 5px;
text-align: center;
outline: none;
}
.button {
display: inline;
cursor: pointer;
padding: 2px 8px;
border-radius: 5px;
border: 2px solid #888;
}
.button:hover {
background: #ffd;
border-color: #000;
}
#display {
width: 200px;
height: 50px;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<canvas id="myCanvas"> Canvas not supported. </canvas>
<div>
Number of balls:
<input type="text" id="inputNumBalls" size="3" />
<div class="button" id="setNumBalls">Set</div>
<div id="display"></div>
</div>
I am having issues trying to place an image as an object for an "asteroid avoidance" game. I was able to get the game itself to work based on what was written in my book "Foundation: HTML5 Canvas for Games and Entertainment". I want to go one step beyond what was written in the book. I want to replace the triangle shaped ship with that of an image. However, I am unable to figure out where to put the code for this.draw. My professor showed us a way to do it. When I try to implement it into my code it doesn't want to work properly. May I ask for some advice on how to place the image as the ship?
Here is my working code from the book, before I made any this.draw edits:(http://jsbin.com/tukejopofo/1/)
$(document).ready(function() {
var canvas = $("#gameCanvas");
var context = canvas.get(0).getContext("2d");
//canvas dimensions
var canvasWidth = canvas.width();
var canvasHeight = canvas.height();
var playGame;
var asteroids;
var numAsteroids;
var player;
var score;
var scoreTimeout;
var arrowUp = 38;
var arrowRight = 39;
var arrowDown = 40;
var arrowLeft = 37;
//game UI
var ui = $("#gameUI");
var uiIntro = $("#gameIntro");
var uiStats = $("#gameStats");
var uiComplete = $("#gameComplete");
var uiPlay = $("#gamePlay");
var uiReset = $(".gameReset");
var uiScore = $(".gameScore");
var soundBackground = $("#gameSoundBackground").get(0);
var soundThrust = $("#gameSoundThrust").get(0);
var soundDeath = $("#gameSoundDeath").get(0);
var Asteroid = function(x, y, radius, vX) {
this.x = x;
this.y = y;
this.radius = radius;
this.vX = vX;
};
var Player = function(x, y) {
this.x = x;
this.y = y;
this.width = 24;
this.height = 24;
this.halfWidth = this.width / 2;
this.halfHeight = this.height / 2;
this.flameLength1 = 20;
this.flameLength2 = 20;
this.vX = 0;
this.vY = 0;
this.moveRight = false;
this.moveUp = false;
this.moveDown = false;
this.moveLeft = false;
};
//Reset and start the game
function startGame() {
//Reset game stats
uiScore.html("0");
uiStats.show();
//set up initial game settings
playGame = false;
asteroids = new Array();
numAsteroids = 10;
score = 0;
player = new Player(150, canvasHeight / 2, 50, 50);
for (var i = 0; i < numAsteroids; i++) {
var radius = 5 + (Math.random() * 10);
var x = canvasWidth + radius + Math.floor(Math.random() * canvasWidth);
var y = Math.floor(Math.random() * canvasHeight);
var vX = -5 - (Math.random() * 5);
asteroids.push(new Asteroid(x, y, radius, vX));
};
$(window).keydown(function(e) {
var keyCode = e.keyCode;
if (!playGame) {
playGame = true;
soundBackground.currentTime = 0;
soundBackground.play();
animate();
timer();
};
if (keyCode == arrowRight) {
player.moveRight = true;
if (soundThrust.paused) {
soundThrust.currentTime = 0;
soundThrust.play();
}
} else if (keyCode == arrowLeft) {
player.moveLeft = true;
} else if (keyCode == arrowUp) {
player.moveUp = true;
} else if (keyCode == arrowDown) {
player.moveDown = true;
}
});
$(window).keyup(function(e) {
var keyCode = e.keyCode;
if (!playGame) {
playGame = true;
animate();
};
if (keyCode == arrowRight) {
player.moveRight = false;
if (keyCode == arrowRight) {
player.moveRight = false;
soundThrust.pause();
}
} else if (keyCode == arrowUp) {
player.moveUp = false;
} else if (keyCode == arrowDown) {
player.moveDown = false;
} else if (keyCode == arrowLeft) {
player.moveLeft = false;
}
});
//start the animation loop
animate();
};
//initialize the game environment
function init() {
uiStats.hide();
uiComplete.hide();
uiPlay.click(function(e) {
e.preventDefault();
uiIntro.hide();
startGame();
});
uiReset.click(function(e) {
e.preventDefault();
uiComplete.hide();
$(window).unbind("keyup");
$(window).unbind("keydown");
soundThrust.pause();
soundBackground.pause();
clearTimeout(scoreTimeout);
startGame();
});
};
function timer() {
if (playGame) {
scoreTimeout = setTimeout(function() {
uiScore.html(++score);
if (score % 5 == 0) {
numAsteroids += 5;
}
timer();
}, 1000);
};
};
//Animation loop that does all the fun stuff
function animate() {
//Clear
context.clearRect(0, 0, canvasWidth, canvasHeight);
var asteroidsLength = asteroids.length;
for (var i = 0; i < asteroidsLength; i++) {
var tmpAsteroid = asteroids[i];
tmpAsteroid.x += tmpAsteroid.vX;
if (tmpAsteroid.x + tmpAsteroid.radius < 0) { //creates bounderies to prevent player from leaving the canvas
tmpAsteroid.radius = 5 + (Math.random() * 10);
tmpAsteroid.x = canvasWidth + tmpAsteroid.radius;
tmpAsteroid.y = Math.floor(Math.random() * canvasHeight);
tmpAsteroid.vX = -5 - (Math.random() * 5);
}
var dX = player.x - tmpAsteroid.x;
var dY = player.y - tmpAsteroid.y;
var distance = Math.sqrt((dX * dX) + (dY * dY));
if (distance < player.halfWidth + tmpAsteroid.radius) { //checks for collision
soundThrust.pause()
soundDeath.currentTime = 0;
soundDeath.play();
//Game over
playGame = false;
clearTimeout(scoreTimeout);
uiStats.hide();
uiComplete.show();
soundBackground.pause();
$(window).unbind("keyup"); //unbinds keys to stop player movement at the end of the game
$(window).unbind("keydown");
};
context.fillStyle = "rgb(255, 255, 255)";
context.beginPath();
context.arc(tmpAsteroid.x, tmpAsteroid.y, tmpAsteroid.radius, 0, Math.PI * 2, true);
context.fill();
};
player.vX = 0;
player.vY = 0;
if (player.moveRight) {
player.vX = 3;
};
if (player.moveLeft) {
player.vX = -3;
};
if (player.moveUp) {
player.vY = -3;
};
if (player.moveDown) {
player.vY = 3;
};
player.x += player.vX;
player.y += player.vY;
if (player.x - player.halfWidth < 20) {
player.x = 20 + player.halfWidth;
} else if (player.x + player.halfWidth > canvasWidth - 20) {
player.x = canvasWidth - 20 - player.halfWidth;
}
if (player.y - player.halfHeight < 20) {
player.y = 20 + player.halfHeight;
} else if (player.y + player.halfHeight > canvasHeight - 20) {
player.y = canvasHeight - 20 - player.halfHeight;
}
if (player.moveRight) {
context.save();
context.translate(player.x - player.halfWidth, player.y);
if (player.flameLength1 == 20) {
player.flameLength1 = 15;
(player.flameLength2 == 20)
player.flameLength2 = 15;
} else {
player.flameLength1 = 20;
player.flameLength2 = 20;
};
context.fillStyle = "orange";
context.beginPath();
context.moveTo(0, -12);
context.lineTo(-player.flameLength1, -7);
context.lineTo(0, -5);
context.closePath();
context.fill();
context.fillStyle = "orange";
context.beginPath();
context.moveTo(0, 12);
context.lineTo(-player.flameLength2, 7);
context.lineTo(0, 5);
context.closePath();
context.fill();
context.restore();
};
//draw ship
context.fillStyle = "rgb(255, 0, 0)";
context.beginPath();
context.moveTo(player.x + player.halfWidth, player.y);
context.lineTo(player.x - player.halfWidth, player.y - player.halfHeight);
context.lineTo(player.x - player.halfWidth, player.y + player.halfHeight);
context.closePath();
context.fill();
while (asteroids.length < numAsteroids) { //adds asteroids as the difficulty increases
var radius = 5 + (Math.random() * 10)
var x = Math.floor(Math.random() * canvasWidth) + canvasWidth + radius;
var y = Math.floor(Math.random() * canvasHeight);
var vX = -5 - (Math.random() * 5);
asteroids.push(new Asteroid(x, y, radius, vX));
}
if (playGame) {
//run the animation loop again in 33 milliseconds
setTimeout(animate, 24);
};
};
init();
});
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
width: 100%;
}
canvas {
display: block;
}
body {
background: #000;
color: #fff;
font-family: Verdana, Arial, sans-serif;
font-size: 18px;
}
h1 {
font-size: 30px;
}
h6 {
font-size: 15px;
}
p {
margin: 0 20px;
}
a {
color: #fff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a.button {
background: #185da8;
border-radius: 5px;
display: block;
font-size: 30px;
margin: 40px 0 0 350px;
padding: 10px;
width: 200px;
text-align: center;
}
a.button:hover {
background: #2488f5;
color: #fff;
text-decoration: none;
}
#game {
height: 600px;
left: 50%;
margin: -250px 0 0 -500px;
position: relative;
top: 50%;
width: 980px;
}
#gameCanvas {
background: #001022;
border: 5px solid green;
background-image: url(../images/space.jpg);
background-position: center top;
background-repeat: no-repeat;
background-size: cover;
}
#gameUI {
height: 600px;
position: absolute;
width: 980px;
}
#gameIntro,
#gameComplete {
background: rgba(0, 0, 0, 0.5);
margin: 100px 0 0 10px;
padding: 40px 0;
text-align: center;
}
#gameStats {
font-size: 14px;
margin: 20px 0;
}
#gameStats .gameReset {
margin: 20px 20px 0 0;
position: absolute;
right: 0;
top: 0;
}
<body>
<div id="game">
<div id="gameUI">
<div id="gameIntro">
<h1>Debris Fields of Spiral Galaxy</h1>
<h6>A <i>Galaxy Smuggler's Run</i> Game</h6>
<hr>
<p>You are Captain Amadaeus delivering goods to a dependent planet on the other side of a debris field</p>
<p>Click <i>"Play"</i> and then press any key to start.</p>
<p><a id="gamePlay" class="button" href="">Play!</a>
</p>
</div>
<div id="gameStats">
<p><b>Time: </b><span class="gameScore"></span> seconds</p>
<p><a class="gameReset" href="">Reset</a>
</p>
</div>
<div id="gameComplete">
<h1>Game Over!</h1>
<p>You survived for <span class="gameScore"></span> seconds.</p>
<p>Would you like to give it another go?</p>
<p><a class="gameReset button" href="">Play Again?</a>
</p>
</div>
</div>
<canvas id="gameCanvas" width="980" height="600">
</canvas>
<audio id="gameSoundBackground" loop>
<source src="sounds/background.ogg">
<source src="sounds/background.mp3">
</audio>
<audio id="gameSoundThrust" loop>
<source src="sounds/thrust.ogg">
<source src="sounds/thrust.mp3">
</audio>
<audio id="gameSoundDeath">
<source src="sounds/death.ogg">
<source src="sounds/death.mp3">
</audio>
</div>
</body>
and here is my Professor's code for drawing an image:(http://jsbin.com/rapayufafe/1/)
// JS file for the ship
function Ship() {
this.x = 100;
this.y = 100;
this.color = "yellow";
this.fillStyle = "white";
this.vx = 0;
this.vy = 0;
this.ax = 1;
this.ay = 1;
//function "move" that will add velocity to the position of the ship
this.move = function() {
this.x += this.vx;
this.y += this.vy;
}//end move function
//draw the ship
this.draw=function () {
//ship var
var imageObj = new Image();
imageObj.src = "images/ship.png";
//save the current state of the canvas
context.save();
//moving the point of origin (0,0) to the ships x and y coordinates
context.translate(this.x,this.y);
context.lineStyle = this.color;
context.fillStyle = this.fillStyle;
/*context.beginPath();
context.moveTo(25,0);
context.lineTo(-25,25)
context.lineTo(-25,-25)*/
//draw ship
context.drawImage(imageObj,-25,-25,50,50);
context.closePath();
context.stroke();
context.fill();
context.restore();
}//end of draw ship
}//end ship function
/*var asteroidsLength = asteroids.length;
for (var i = 0; i < asteroidsLength; i++) {
var tmpAsteroid = asteroids[i];
context.fillStyle = "gray";
context.beginPath();
context.arc(tmpAsteroid.x, tmpAsteroid.y, tmpAsteroid.radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
};*/
As you can see in your starting code, you have a section looking like this:
//draw ship
context.fillStyle = "rgb(255, 0, 0)";
context.beginPath();
context.moveTo(player.x + player.halfWidth, player.y);
context.lineTo(player.x - player.halfWidth, player.y - player.halfHeight);
context.lineTo(player.x - player.halfWidth, player.y + player.halfHeight);
context.closePath();
context.fill();
Just replace that code with the code for drawing an image.
var imageObj = new Image();
imageObj.src = "images/ship.png";
context.drawImage(imageObj,player.x,player.y);
Although, I'd recommend declaring the imageObj and setting the source at the top of your code where you declare the rest of your variables so that you don't load the image every time you want to draw the ship.