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>
Related
I'm making a simple brick breaker game using Java Script, and my console is showing me error while displaying blocks on to the canvas, they are being drawn onto the canvas but all the other objects are not working and console is showing
index.js:173 Uncaught TypeError: Cannot read property 'show' of undefined
at update (index.js:173)
at index.js:177
if you comment out from line no:172 and 173, which is a for loop that tells the canvas to draw them on it
everything's is working fine.
one more thing: that canvasRendering...rundedRectangle is a function that draws rounded edge rectangles
someone please find a solution!
CanvasRenderingContext2D.prototype.roundedRectangle = function(x, y, width, height, rounded) {
const radiansInCircle = 2 * Math.PI
const halfRadians = (2 * Math.PI)/2
const quarterRadians = (2 * Math.PI)/4
// top left arc
this.arc(rounded + x, rounded + y, rounded, -quarterRadians, halfRadians, true)
// line from top left to bottom left
this.lineTo(x, y + height - rounded)
// bottom left arc
this.arc(rounded + x, height - rounded + y, rounded, halfRadians, quarterRadians, true)
// line from bottom left to bottom right
this.lineTo(x + width - rounded, y + height)
// bottom right arc
this.arc(x + width - rounded, y + height - rounded, rounded, quarterRadians, 0, true)
// line from bottom right to top right
this.lineTo(x + width, y + rounded)
// top right arc
this.arc(x + width - rounded, y + rounded, rounded, 0, -quarterRadians, true)
// line from top right to top left
this.lineTo(x + rounded, y)
}
var canvas= document.getElementById("gameCanvas");
var ctx = canvas.getContext("2d");
function Player(x,y,w,h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.show = function(){
ctx.beginPath();
ctx.rect(this.x, this.y, this.w, this.h);
ctx.fillStyle = "#ffff";
ctx.fill();
ctx.closePath();
}
this.move = function(speed){
this.x += speed;
}
}
function Ball(x,y,r){
this.x = x;
this.y = y;
this.r = r;
this.show = function(){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,2* Math.PI);
ctx.fillStyle = "tomato";
ctx.fill();
ctx.closePath();
}
this.move= function(speedX,speedY){
this.show();
this.speed = 2;
this.x += speedX;
this.y += speedY;
}
}
function Block(x,y,w,h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.show= function(color){
ctx.beginPath();
ctx.roundedRectangle(this.x,this.y,this.w,this.h,7);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
};
};
var player = new Player(canvas.width/2-50,canvas.height*0.95,100,20);
var ball = new Ball(canvas.width/2-5, player.y-20,15);
var rigthPressed = false;
var leftPressed = false;
var blocks = [];
var rowCount = 5;
var columnCount = 6;
var noInRow = 6;
var blockCount = (rowCount*columnCount)+1;
var blockRow = 0;
var blockCol = 0;
var ballSpeedX = 5;
var ballSpeedY = -10;
for(let i = 1; i < blockCount; i++){
blocks.push(new Block(blockCol*60+25,blockRow*60+30,50,50));
blockCol++;
if(i % noInRow == 0){
blockRow++;
blockCol = 0;
}
}
window.addEventListener("keydown", function(e){
if(e.keyCode == 39){
rigthPressed = true;
}
if(e.keyCode == 37){
leftPressed = true;
}
});
window.addEventListener("keyup", function(e){
if(e.keyCode == 39){
rigthPressed = false;
}
if(e.keyCode == 37){
leftPressed = false;
}
});
function objMovement(){
if(rigthPressed){
player.move(5);
if (player.x > canvas.width-player.w){
player.x = canvas.width-player.w;
}
}
if(leftPressed){
player.move(-5);
if(player.x < 0){
player.x = 0;
}
}
if(ball.x > canvas.width-ball.r || ball.x < 0+ball.r){
ballSpeedX = -ballSpeedX;
}
if (ball.y > canvas.height-ball.r || ball.y < 0+ball.r){
ballSpeedY = -ballSpeedY;
}
if(ball.x<player.x+player.w &&ball.x>player.x && ball.y>player.y){
ballSpeedY = -ballSpeedY;
ballSpeedX= ballSpeedX;
}
function Bump(){
if (ball.x>player.x && ball.x<player.x+player.w/2){
if (ball.y >= player.y){
ballSpeedX = -5;
}
}
if(ball.x>player.x+player.w/2 && ball.x<player.x+player.w){
if(ball.y >= player.y){
ballSpeedX = 5;
}
}
}
//Bump();
}
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ball.show();
ball.move(ballSpeedX,ballSpeedY);
player.show();
objMovement();
for(let i=0;i<blockCount;i++){
blocks[i].show("violet");
}
window.requestAnimationFrame(update);
}
update();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🌝🩲🩲🌝</title>
<style>
#body{
background-color: rgb(31, 30, 30);
}
#gameCanvas{
border: 15px solid rgb(44, 44, 44);
border-radius: 20px;
background-color:rgb(19, 18, 18);
margin: 250px;
}
</style>
</head>
<body id="body">
<canvas id="gameCanvas" width=400 height=800></canvas>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>
When you create blocks you start from 1
for (let i = 1; i < blockCount; i++) {
blocks.push(new Block(blockCol * 60 + 25, blockRow * 60 + 30, 50, 50));
...
so when you update you need to consider that
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ball.show();
ball.move(ballSpeedX, ballSpeedY);
player.show();
objMovement();
for (let i = 0; i < blockCount -1; i++) { // or for (let i = 0; i < blocks.length; i++) {
blocks[i].show("violet");
}
window.requestAnimationFrame(update);
}
I'm making an AI navigation system based on polar coordinates. The purpose is to move an actor to a position, while also moving away from a possible obstacle on its path.
The code works perfectly most of the time but after testing, I discovered this: when the player, obstacle and actor are all perfectly aligned in either the X or Y direction or diagonally, the actor gets stuck in the obstacle. It's mostly noticeable when the player is "hugging" a wall because the actor's movement vector is clipped by the walls, making them aligned.
Click the buttons in the snippet to see what I'm on about.
Is there a way to prevent this?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let playerX = 100;
let playerY = 200;
let obstacleX = 200;
let obstacleY = 200;
let actorX = 300;
let actorY = 201;
function loop() {
// Wall clipping
if (actorX > 490) {
actorX = 490;
} else if (actorX < 10) {
actorX = 10;
}
if (actorY > 490) {
actorY = 490;
} else if (actorY < 10) {
actorY = 10;
}
// Vector between player and actor
let vectorPlayerX = playerX - actorX;
let vectorPlayerY = playerY - actorY;
// Vector between obstacle and actor
let vectorObstacleX = obstacleX - actorX;
let vectorObstacleY = obstacleY - actorY;
// Where to move, defaults to player's position
const anglePlayer = Math.atan2(vectorPlayerY, vectorPlayerX);
let moveAngle = anglePlayer;
// If near obstacle, adjust course and try to avoid it
if (Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY) < 50) {
const angleObstacle = Math.atan2(vectorObstacleY, vectorObstacleX);
moveAngle += anglePlayer - angleObstacle;
}
// Move the vector to desired location
actorX += Math.cos(moveAngle);
actorY += Math.sin(moveAngle);
//Drawing
ctx.clearRect(0, 0, 500, 500);
ctx.beginPath();
ctx.fillStyle = "gray";
ctx.arc(actorX, actorY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "orange";
ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(playerX, playerY, 10, 0, Math.PI * 2, true);
ctx.fill();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function nonAligned() {
playerX = 100;
playerY = 200;
obstacleX = 200;
obstacleY = 200;
actorX = 300;
actorY = 201;
}
function alignedY() {
playerX = 100;
playerY = 490;
obstacleX = 200;
obstacleY = 490;
actorX = 300;
actorY = 490;
}
function alignedBoth() {
playerX = 200;
playerY = 200;
obstacleX = 300;
obstacleY = 300;
actorX = 400;
actorY = 400;
}
#options {
position: fixed;
top: 5px;
left: 5px;
}
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<div id="options">
<button onclick="nonAligned()">Spawn non-aligned</button>
<button onclick="alignedY()">Spawn Y aligned</button>
<button onclick="alignedBoth()">Spawn diagonally aligned</button>
</div>
</body>
</html>
If the angle to the player and the obstacle are the same then we continue the course, as the variables cancel each other out.
moveAngle += anglePlayer - angleObstacle;
If anglePlayer is 117 and angleObstacle is 117 and your moveAngle is 117 you get
117 + 117 -117 = 117 ...
You might want something like this (pseudo code)
moveAngle += anglePlayer + random(90)-45;
Or if hitting an obstacle move left or right
moveAngle += anglePlayer - 90;
if (random(2)==1 moveAngle += 180
The issue is indeed that moveAngle is unchanged when it is pointed directly at the obstacle. A small modification checks whether the moveAngle is clockwise or counter-clockwise from the obstacle, and veers further away (note: my code breaks the wall hugging and behaves badly in the "aligned in Y" case for that reason, which is fixable but I don't care to):
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let playerX = 100;
let playerY = 200;
let obstacleX = 200;
let obstacleY = 200;
let actorX = 300;
let actorY = 201;
function loop() {
// Wall clipping
if (actorX > 490) {
actorX = 490;
} else if (actorX < 10) {
actorX = 10;
}
if (actorY > 490) {
actorY = 490;
} else if (actorY < 10) {
actorY = 10;
}
// Vector between player and actor
let vectorPlayerX = playerX - actorX;
let vectorPlayerY = playerY - actorY;
// Vector between obstacle and actor
let vectorObstacleX = obstacleX - actorX;
let vectorObstacleY = obstacleY - actorY;
// Where to move, defaults to player's position
const anglePlayer = Math.atan2(vectorPlayerY, vectorPlayerX);
let moveAngle = anglePlayer;
// If near obstacle, adjust course and try to avoid it
obs_distance = Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY);
if (obs_distance < 100) {
const angleObstacle = Math.atan2(vectorObstacleY, vectorObstacleX);
delta = Math.PI/2.0;
if (obs_distance > 100.0/32.0) { delta = (100.0/32.0)*Math.PI/obs_distance; }
cross = Math.sin(moveAngle-angleObstacle);
if (cross>0) { moveAngle += delta; }
if (cross<0) { moveAngle -= delta; }
if (cross==0) {
if (Math.random(2)==1) {
moveAngle += delta;
} else {
moveAngle -= delta;
}
}
}
// Move the vector to desired location
actorX += Math.cos(moveAngle);
actorY += Math.sin(moveAngle);
//Drawing
ctx.clearRect(0, 0, 500, 500);
ctx.beginPath();
ctx.fillStyle = "gray";
ctx.arc(actorX, actorY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "orange";
ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(playerX, playerY, 10, 0, Math.PI * 2, true);
ctx.fill();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function nonAligned() {
playerX = 100;
playerY = 200;
obstacleX = 200;
obstacleY = 200;
actorX = 300;
actorY = 201;
}
function alignedY() {
playerX = 100;
playerY = 490;
obstacleX = 200;
obstacleY = 490;
actorX = 300;
actorY = 490;
}
function alignedBoth() {
playerX = 200;
playerY = 200;
obstacleX = 300;
obstacleY = 300;
actorX = 400;
actorY = 400;
}
#options {
position: fixed;
top: 5px;
left: 5px;
}
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<div id="options">
<button onclick="nonAligned()">Spawn non-aligned</button>
<button onclick="alignedY()">Spawn Y aligned</button>
<button onclick="alignedBoth()">Spawn diagonally aligned</button>
</div>
</body>
</html>
I would like to remove the balls already generated in the canvas on the click and decrease the counter on the bottom, but my function does not work. Here is my code concerning the part of the ball removal.
Is it possible to use a div to get the same result and to facilitate the removal of the balls? thank you
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
below I enclose my complete code
// GLOBAL VARIBLES
var gravity = 4;
var forceFactor = 0.3; //0.3 0.5
var mouseDown = false;
var balls = []; //hold all the balls
var mousePos = []; //hold the positions of the mouse
var ctx = canvas.getContext('2d');
var heightBrw = canvas.height = window.innerHeight;
var widthBrw = canvas.width = window.innerWidth;
var bounciness = 1; //0.9
window.onload = function gameCore() {
function onMouseDown(event) {
mouseDown = true;
mousePos["downX"] = event.pageX;
mousePos["downY"] = event.pageY;
}
canvas.onclick = function onMouseUp(event) {
mouseDown = false;
balls.push(new ball(mousePos["downX"], mousePos["downY"], (event.pageX - mousePos["downX"]) * forceFactor,
(event.pageY - mousePos["downY"]) * forceFactor, 5 + (Math.random() * 10), bounciness, random_color()));
ball
}
function onMouseMove(event) {
mousePos['currentX'] = event.pageX;
mousePos['currentY'] = event.pageY;
}
function resizeWindow(event) {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function reduceBounciness(event) {
if (bounciness == 1) {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 0.9;
document.getElementById("btn-bounciness").value = "⤽ Bounciness";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 1;
document.getElementById("btn-bounciness").value = " ⤼ Bounciness";
}
}
return bounciness;
}
function reduceSpeed(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 20 + c;
balls[i].vy = velocityY = 20 + c;
}
}
function speedUp(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 120 + c;
balls[i].vy = velocityY = 120 + c;
}
}
function stopGravity(event) {
if (gravity == 4) {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 0;
balls[i].vx = velocityX = 0;
balls[i].vy = velocityY = 0;
document.getElementById("btn-gravity").value = "►";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 4;
balls[i].vx = velocityX = 100;
balls[i].vy = velocityY = 100;
document.getElementById("btn-gravity").value = "◾";
}
}
}
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
document.getElementById("btn-gravity").addEventListener("click", stopGravity);
document.getElementById("btn-speed-up").addEventListener("click", speedUp);
document.getElementById("btn-speed-down").addEventListener("click", reduceSpeed);
document.getElementById("btn-bounciness").addEventListener("click", reduceBounciness);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
window.addEventListener('resize', resizeWindow);
}
// GRAPHICS CODE
function circle(x, y, r, c) { // x position, y position, r radius, c color
//draw a circle
ctx.beginPath(); //approfondire
ctx.arc(x, y, r, 0, Math.PI * 2, true);
ctx.closePath();
//fill
ctx.fillStyle = c;
ctx.fill();
//stroke
ctx.lineWidth = r * 0.1; //border of the ball radius * 0.1
ctx.strokeStyle = "#000000"; //color of the border
ctx.stroke();
}
function random_color() {
var letter = "0123456789ABCDEF".split(""); //exadecimal value for the colors
var color = "#"; //all the exadecimal colors starts with #
for (var i = 0; i < 6; i++) {
color = color + letter[Math.round(Math.random() * 15)];
}
return color;
}
function selectDirection(fromx, fromy, tox, toy) {
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.moveTo(tox, toy);
}
//per velocità invariata rimuovere bounciness
function draw_ball() {
this.vy = this.vy + gravity * 0.1; // v = a * t
this.x = this.x + this.vx * 0.1; // s = v * t
this.y = this.y + this.vy * 0.1;
if (this.x + this.r > canvas.width) {
this.x = canvas.width - this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.x - this.r < 0) {
this.x = this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.y + this.r > canvas.height) {
this.y = canvas.height - this.r;
this.vy = this.vy * -1 * this.b;
}
if (this.y - this.r < 0) {
this.y = this.r;
this.vy = this.vy * 1 * this.b;
}
circle(this.x, this.y, this.r, this.c);
}
// OBJECTS
function ball(positionX, positionY, velosityX, velosityY, radius, bounciness, color, gravity) {
this.x = positionX;
this.y = positionY;
this.vx = velosityX;
this.vy = velosityY;
this.r = radius;
this.b = bounciness;
this.c = color;
this.g = gravity;
this.draw = draw_ball;
}
// GAME LOOP
function game_loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (mouseDown == true) {
selectDirection(mousePos['downX'], mousePos['downY'], mousePos['currentX'], mousePos['currentY']);
}
for (var i = 0; i < balls.length; i++) {
balls[i].draw();
}
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length, 10, canvas.height - 10);
}
setInterval(game_loop, 10);
* {
margin: 0px; padding: 0px;
}
html, body {
width: 100%; height: 100%;
}
#canvas {
display: block;
height: 95%;
border: 2px solid black;
width: 98%;
margin-left: 1%;
}
#btn-speed-up, #btn-speed-down, #btn-bounciness, #btn-gravity{
padding: 0.4%;
background-color: beige;
text-align: center;
font-size: 20px;
font-weight: 700;
float: right;
margin-right: 1%;
margin-top:0.5%;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Power Balls</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<input type="button" value="⤼ Bounciness" id="btn-bounciness"></input>
<input type="button" onclick="c+=10" value="+ Speed" id="btn-speed-up"></input>
<input type="button" value="◾" id="btn-gravity"></input>
<input type="button" onclick="c+= -10" value=" - Speed" id="btn-speed-down"></input>
<script src="js/main.js"></script>
</body>
</html>
I see two problems:
ball.onclick will not get triggered, as ball it is not a DOM object. Instead, you need to work with canvas.onclick. Check whether the user clicked on a ball or on empty space to decide whether to delete or create a ball.
To delete the ball, ctx.clearRect is not sufficient. This will clear the ball from the currently drawn frame, but the object still remains in the array balls and will therefore be drawn again in the next frame via balls[i].draw();. Instead, you need to remove the clicked ball entirely from the array, e. g. by using splice.
Otherwise, nice demo! :)
This cannot be done because the canvas is an immediate mode API. All you can do is draw over the top but this is not reliable.
https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)
You will have to store each item in a separate data structure, then re-draw whenever a change occurs. In your case there is an array of balls, so you should remove the one that was clicked, before redrawing the entire array.
Each time you remove something, clear the canvas then re-draw each ball.
There are optimisations you can make if this is too slow.
I'm building a dynamic radar chart, I got the code reviewed and followed the recommendation from fellow SO member.
This is how far I've come, but seem to have hit a roadblock:
var canv = document.getElementById('canvas');
var canv1 = document.getElementById('canvas1');
var point_xy = document.getElementById('point_xy');
var tipCanvas = document.getElementById("tip");
var tipCtx = tipCanvas.getContext("2d");
var point_xy_cords = [
[]
];
var pentagon_one = 24;
var pentagon_two = 18;
var pentagon_three = 12;
var pentagon_four = 6;
var pentagon_five = 0;
var circles = [];
var contx = canv.getContext('2d');
var contx1 = canv1.getContext('2d');
var offsetX = canv1.offsetLeft;
var offsetY = canv1.offsetTop;
contx.clearRect(0, 0, canv.width, canv.height);
function drawShape(ctx, x, y, points, radius1, radius2, alpha0) {
//points: number of points (or number of sides for polygons)
//radius1: "outer" radius of the star
//radius2: "inner" radius of the star (if equal to radius1, a polygon is drawn)
//angle0: initial angle (clockwise), by default, stars and polygons are 'pointing' up
var radius_size = radius1;
var i, angle, radius;
if (radius2 !== radius1) {
points = 2 * points;
}
for (var i = 0; i <= 5; i++) {
var temp = [];
contx1.beginPath();
for (var j = 0; j <= 4; j++) {
angle = j * 2 * Math.PI / points - Math.PI / 2 + alpha0;
radius = j % 2 === 0 ? radius_size : radius_size;
temp[j] = [(x + radius_size * Math.cos(angle)), (y + radius_size * Math.sin(angle))];
ctx.lineTo(temp[j][0], temp[j][1]);
}
ctx.closePath();
style(ctx);
radius_size = radius_size - 20;
point_xy_cords.push(temp);
}
point_xy.textContent = "[1] = " + point_xy_cords[1] + " y = " + point_xy_cords[1][1];
}
function style(ctx, fill) {
ctx.strokeStyle = "rgba(0, 109, 0, 1)";
ctx.lineWidth = 2;
if (fill) {
ctx.fillStyle = "rgba(74, 157, 33, 0.6)";
ctx.fill();
} else {
ctx.stroke()
}
//contx.fill();
}
var radius = 2;
var Circle = function(x, y, radius) {
this.left = x - radius;
this.top = y - radius;
this.right = x + radius;
this.bottom = y + radius;
this.point_clicked = [];
this.clicked = function(){
points[1][0] = x; //hardcoded part
points[1][1] = y; //hardcoded part
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
this.draw = function(ctx) {
//Draw all points
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI, false);
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(74, 157, 33, 1)";
ctx.fill();
ctx.stroke();
}
this.containsPoint = function(x,y){
return (x < this.right && x > this.left && y > this.top && y < this.bottom);
}
};
//Draw background
function drawBackgroundPentagons(ctx) {
drawShape(ctx, 120, 120, 5, 100, 100, 0);
}
drawBackgroundPentagons(contx1);
//Draw all the points
function drawPoints(){
for (var x = 1; x <= 5; x++){
for (var y = 0; y <= 4; y++){
var circle = new Circle(point_xy_cords[x][y][0], point_xy_cords[x][y][1], 8);
circle.draw(contx1);
circles.push(circle);
}
}
}
drawPoints();
function drawMainPentagon(ctx, points) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var x = 1; x <= 4; x++) {
ctx.lineTo(points[x][0], points[x][1]);
}
style(ctx, "fill");
ctx.closePath();
}
points = point_xy_cords[1];
drawMainPentagon(contx1, points);
function handleMouseDown(e, message) {
point_xy.textContent = (message);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canv1.onmousedown = function(e) {
var pos = getMousePos(canv1, e);
var clickedX = pos.x;
var clickedY = pos.y;
var tooltipText = "nothing";
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
if (circle.containsPoint(clickedX, clickedY)) {
circle.clicked();
return;
}
}
tooltip("points[0]", clickedX, clickedY);
};
function tooltip(text, clickedX, clickedY) {
tipCtx.fillStyle = "black";
tipCtx.fillRect(0, 0, canvas.width, canvas.height);
tipCtx.fillStyle = "white";
tipCtx.fillText(text, 5, 10);
tipCanvas.style.left = (clickedX + 15) + "px";
tipCanvas.style.top = (clickedY - 26) + "px";
}
canv1.onmouseover = function(e) {
return null;
}
canv1.onmouseout = function(e) {
return null;
}
canv1.onmousemove = function(e) {
return null;
}
#tip {
left: -200px;
top: 100px;
position: absolute;
float: left;
maxWidth: 200px;
backgroundColor: rgba(0, 0, 0, 0.8);
border: rgba(45, 65, 45, 1);
borderRadius: 5px;
color: #f9f9f9;
fontSize: 14px;
padding: 5px;
textAlign: left;
}
<div id="canvasesdiv" style="position:relative; width:400px; height:300px">
<canvas id="tip" width=100 height=100 style="z-index: 3;"></canvas>
<canvas id="canvas" style="z-index: 1;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<canvas id="canvas1" style="z-index: 2;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<div id='point_xy'></div>
If you click a point, it is suppose to move the point of the highlighted pentagon to the clicked point. It works, except I can't figure out what conditions to add in order to move the correct corner of the highlighted pentagon. In the above code I have hardcoded it, so that no matter which point you click, it will move point at index 0.
Any direction would be appreciated.
So what you want to do is let each circle know what spoke or radii it belongs to. Something like this:
var Circle = function(x, y, radius, spoke, value) {
this.x = x;
this.y = y;
this.radius = radius;
this.spoke = spoke;
this.value = value;
Now create them something like:
function drawPoints() {
for (var value = 1; value <= 5; value++){
for (var spoke = 0; spoke <= 4; spoke++){
var circle = new Circle(point_xy_cords[value][spoke][0], point_xy_cords[value][spoke][1], 8, spoke, value);
circle.draw(contx1);
circles.push(circle);
}
}
}
I changed the variable names to something meaningful. One note here is that you mix code to create the circles and code to draw them. You don't want to do this. Create them once on initialization and redraw them as changes are made (clicking). You don't want to re-create the circles every time you redraw.
Lastly change this:
// Circle
this.clicked = function(){
points[this.spoke][0] = this.x;
points[this.spoke][1] = this.y;
updateCanvas();
}
And elsewhere:
function updateCanvas() {
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
If I can make a suggestion, start with the simplest code you can. Start just by displaying the circles and pentagons, get that working cleanly and build onto it. Try and keep logic separate in your code. There are several places where you create objects and initialize arrays (like coords) while you are drawing which is both unnecssary but also means that you do it over and over instead of just once. There is a also lot of code here that is unnecessary.
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.