(Javascript) Bouncing balls clip into wall - javascript

document.addEventListener("DOMContentLoaded", function(event){
var context,
width = window.screen.availWidth - 120,
height = window.screen.availHeight - 120,
xTemp,
yTemp,
x = [],
y = [],
dx = [0],
dy = [5],
gravity = [1],
bounceTime = [1],
canvas = document.getElementById("bouncingField"),
isSpawned = 0,
image = new Image();
document.getElementById("bouncingField").width = width;
document.getElementById("bouncingField").height = height;
//Image to use as ball texture
image.src = "http://www.freeiconspng.com/uploads/soccer-ball-icon-14.png";
//Run func init on page load
window.onload = init;
//Get 2d context and repaint every 10 milliseconds
context = bouncingField.getContext('2d');
setInterval(repaint, 10);
canvas.onclick = function setSpawnTrue(){
if(!isSpawned){
x[0] = xTemp;
y[0] = yTemp;
} else{
x.push(xTemp);
y.push(yTemp);
dx.push(0);
dy.push(5);
gravity.push(1);
bounceTime.push(1);
}
isSpawned = 1;
}
//Draws the various entities
function draw(){
context = bouncingField.getContext('2d');
for(var i = 0; i < x.length; i++){
//context.beginPath();
//context.fillStyle = "#00ccff";
//Draw circles of r = 25 at coordinates x and y
//context.arc(x[i], y[i], 25, 0, Math.PI*2, true);
context.drawImage(image, x[i], y[i], 50, 50);
//context.closePath();
//context.fill();
}
}
//Repaints entities, essentially animating them
function repaint(){
for(var i = 0; i < x.length; i++){
context.clearRect(0, 0, 2000, 2000);
if(x[i] < 20 || x[i] > width) dx[i] *= -1;
if(y[i] < 20 || y[i] > height) {
dy[i] *= -1;
//We add bounceTime to dy so that it gradually loses speed
dy[i] += bounceTime[i];
//Inverting graviy to slow down on rise
gravity[i] *= -1;
}
x[i] += dx[i];
//Gravity affects the ball bounce speed, that gradually slows down.
y[i] += dy[i] + gravity[i];
//bounceTime gradually reduces the amount of speed the ball has
gravity[i] += 0.2 * bounceTime[i];
bounceTime[i] += 0.01;
if(isSpawned){
draw();
}
}
}
//Initializes Event.MOUSEMOVE to capture cursor coordinates
function init(){
if(window.Event){
document.captureEvents(Event.MOUSEMOVE);
}
document.onmousemove = getCoordinates;
}
//Gets mouse coordinates and puts them into xTemp and yTemp
function getCoordinates(e){
xTemp = (window.Event) ? e.pageX : event.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
yTemp = (window.Event) ? e.pageY : event.clientY + (document.documentElement.scrollRight ? document.documentElement.scrollRight : document.body.scrollRight);
xTemp -= 14;
yTemp -= 14;
}
});
body{
background-color: #555555;
}
#bouncingField{
border-style: solid;
border-width: 10px;
border-color: white;
}
<HTML>
<HEAD>
<TITLE>
Wingin' it
</TITLE>
<script type="text/javascript" src="script.js"></script>
<link href="style.css" rel="stylesheet" type="text/css">
</HEAD>
<BODY>
<CANVAS id="bouncingField" width="0" height="0"></CANVAS>
</BODY>
</HTML>
I'm working on a simple JavaScript project to create bouncing balls that simulate gravity and that bounce off of the floor or the walls. The problem is that sometimes they clip into the floor and "spaz out" until they disappear from the screen. Any clue why? I've been trying to figure it out by adding a tiny time-out every time it collides but JS doesn't have sleep so I'm just confused now.
Thank you in advance :)

I hope this helps. the tricky part is making a convincing "stop".
function getCoordinates(event) { return { x: event.offsetX, y: event.offsetY }; }
function spawnBall(coords, x, y, dx, dy){
x.push(coords.x);
y.push(coords.y);
dx.push(0);
dy.push(2);
}
// =========================
// Draws the various entities
// =========================
function draw(canvas, image, x, y, width, height) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < x.length; i++){ context.drawImage(image, x[i], y[i], width, height); }
}
// =========================
// =========================
// At the moment all this is concerned with is the "floor"
// =========================
function move(x, y, dx, dy, gravity, bounciness, floor){
for(var i = 0; i < x.length; i++){
// =========================
// Ball is close to the floor and not moving very fast, set it to rest
// otherwise it bounces forever.
// =========================
if (y[i] >= floor - 10 && Math.abs(dy[i]) <= 2 * gravity) {
dy[i] = 0;
y[i] = floor;
continue;
}
// =========================
// =========================
// Update the speed and position
// =========================
dy[i] += gravity;
y[i] += dy[i];
// =========================
// =========================
// Simulate a bounce if we "hit" the floor
// =========================
if(y[i] > floor) {
y[i] = floor - (y[i] - floor);
dy[i] = -1.0 * bounciness * dy[i];
}
// =========================
}
}
// =========================
document.addEventListener("DOMContentLoaded", function(event){
var canvas = document.getElementById("bouncingField");
canvas.width = window.innerWidth - 50;
canvas.height = window.innerHeight - 50;
//Image to use as ball texture
var image = new Image();
image.src = "http://www.freeiconspng.com/uploads/soccer-ball-icon-14.png";
var gravity = 1;
var ballSize = 50;
var ballBounciness = 0.8;
var floor = canvas.height - ballSize;
var x = [];
var y = [];
var dx = [];
var dy = [];
// =========================
// This can be done via requestAnimationFrame()
// https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
// =========================
var isSpawned = false;
setInterval(function(){
if(!isSpawned){ return; }
move(x, y, dx, dy, gravity, ballBounciness, floor)
draw(canvas, image, x, y, ballSize, ballSize);
}, 10);
// =========================
// =========================
// Add a ball
// =========================
canvas.onclick = function(event) {
isSpawned = true;
var coords = getCoordinates(event);
spawnBall(coords, x, y, dx, dy);
}
// =========================
});
body {
background-color: #555555;
}
#bouncingField {
border-style: solid;
border-width: 10px;
border-color: white;
}
<canvas id="bouncingField"></canvas>

Related

How to calculate jump based on delta time?

I'm trying to make a little game with JavaScript (no engine) and I want to get rid of frame-based animation.
I successfully added delta time for horizontal movements (work fine with 60 or 144fps).
But I can't make it work with the jump, height (or the strength) isn't always the same, and I don't know why.
I already tried (And still had the exact same problem):
Passing Delta Time at the end of update(): x += Math.round(dx * dt)
Changing Date.now() to performance.now()
Not rounding DeltaY
Locking Jump Height
I made a simplified example with 2 jump type, height locked jump and a normal jump (IDK what to call it). Both have the same problem.
const canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
canvas2 = document.getElementById('canvas2'),
ctx2 = canvas2.getContext('2d');
// CLASS PLAYER ------------------------
class Actor {
constructor(color, ctx, j) {
this.c = ctx
this.w = 20
this.h = 40
this.x = canvas.width /2 - this.w/2
this.y = canvas.height/2 - this.h/2
this.color = color
// Delta
this.dy = 0
// Movement
this.gravity = 25/1000
this.maxSpeed = 600/1000
// Jump Height lock
this.jumpType = (j) ? 'capedJump' : 'normalJump'
this.jumpHeight = -50
// Booleans
this.isOnFloor = false
}
// Normal jump
normalJump(max) {
if(!this.isOnFloor) return
this.dy = -max
this.isOnFloor = false
}
// Jump lock (locked max height)
capedJump(max) {
const jh = this.jumpHeight;
if(jh >= 0) return
this.dy += -max/15
if(jh - this.dy >= 0) {
this.dy = (jh - this.dy) + jh
this.jumpHeight = 0
} else {
this.jumpHeight += -this.dy
}
}
update(dt) {
const max = this.maxSpeed*dt,
gravity = this.gravity*dt;
// JUMP
this[this.jumpType](max)
// GRAVITY
this.dy += gravity
// TOP AND DOWN COLLISION (CANVAS BORDERS)
const y = this.y + this.dy,
h = y + this.h;
if (y <= 0) this.y = this.dy = 0
else if (h >= canvas.height) {
this.y = canvas.height - this.h
this.dy = 0
this.isOnFloor = true
this.jumpHeight = -50
}
// Update Y
this.y += Math.round(this.dy)
}
draw() {
const ctx = this.c
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.w, this.h)
}
}
const Player = new Actor('brown', ctx, false)
const Player2 = new Actor('blue', ctx2, true)
// ANIMATE -----------------------------
let lastRender = 0
let currRender = Date.now()
function animate() {
// Set Delta Time
lastRender = currRender
currRender = Date.now()
let dt = currRender - lastRender
// CANVAS #1 (LEFT)
ctx.clearRect(0, 0, canvas.width, canvas.height)
background(ctx)
Player.update(dt)
Player.draw()
// CANVAS #2 (RIGHT)
ctx2.clearRect(0, 0, canvas2.width, canvas2.height)
background(ctx2)
Player2.update(dt)
Player2.draw()
window.requestAnimationFrame(animate)
}
animate()
// EVENT LISTENERS -----------------------
window.addEventListener('keydown', (e) => {
e.preventDefault()
if (Player.keys.hasOwnProperty(e.code)) Player.keys[e.code] = true
})
window.addEventListener('keyup', (e) => {
e.preventDefault()
if (Player.keys.hasOwnProperty(e.code)) Player.keys[e.code] = false
})
// Just a function to draw Background nothing to see here
function background(c) {
const lineNumber = Math.floor(canvas.height/10)
c.fillStyle = 'gray'
for(let i = 0; i < lineNumber; i++) {
c.fillRect(0, lineNumber*i, canvas.width, 1)
}
}
div {
display: inline-block;
font-family: Arial;
}
canvas {
border: 1px solid black;
}
span {
display: block;
color: gray;
text-align: center;
}
<div>
<canvas width="100" height="160" id="canvas"></canvas>
<span>Normal</span>
</div>
<div>
<canvas width="100" height="160" id="canvas2"></canvas>
<span>Locked</span>
</div>
Here's how I would refactor the code:
Don't use dy for both speed and position (which you seem to be doing). Rename it vy and use it purely as the vertical velocity.
Move isOnFloor to a function so that we can always check for collisions with the floor.
Decouple the jump functions from actual movement updates. Just make them set the vertical velocity if the player is on the floor.
Perform top / bottom collision checking separately depending on the direction of movement.
Don't round DeltaY - it'll mess up small movements.
With these changes in place, the movement behavior is correct and stable:
const canvas1 = document.getElementById('canvas1'),
ctx1 = canvas1.getContext('2d'),
canvas2 = document.getElementById('canvas2'),
ctx2 = canvas2.getContext('2d');
// Global physics variables
const GRAVITY = 0.0015;
const MAXSPEED = 0.6;
const MAXHEIGHT = 95;
// CLASS PLAYER ------------------------
class Actor {
constructor(C, W, H, J) {
// World size
this.worldW = W;
this.worldH = H;
// Size & color
this.w = 20;
this.h = 40;
this.color = C;
// Speed
this.vy = 0;
// Position
this.x = W/2 - this.w/2;
this.y = H/2 - this.h/2;
// Jump Height lock
this.jumpCapped = J;
this.jumpHeight = 0;
}
// move isOnFloor() to a function
isOnFloor() {
return this.y >= this.worldH - this.h;
}
// Normal jump
normalJump() {
if(!this.isOnFloor()) return;
this.vy = -MAXSPEED;
}
// Jump lock (locked max height)
cappedJump(max) {
if(!this.isOnFloor()) return;
this.vy = -MAXSPEED;
this.jumpHeight = max;
}
update(dt) {
// JUMP
if (this.jumpCapped)
this.cappedJump(MAXHEIGHT);
else
this.normalJump();
// GRAVITY
this.vy += GRAVITY * dt;
this.y += this.vy * dt;
// Bottom collision
if (this.vy > 0) {
if (this.isOnFloor()) {
this.y = this.worldH - this.h;
this.vy = 0;
}
}
else
// Top collision
if (this.vy < 0) {
const maxh = (this.jumpCapped) ? (this.worldH - this.jumpHeight) : 0;
if (this.y < maxh) {
this.y = maxh;
this.vy = 0;
}
}
}
draw(ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
const Player1 = new Actor('brown', canvas1.width, canvas1.height, false);
const Player2 = new Actor('blue', canvas2.width, canvas2.height, true);
// ANIMATE -----------------------------
let lastUpdate = 0;
let randomDT = 0;
function animate() {
// Compute delta time
let currUpdate = Date.now();
let dt = currUpdate - lastUpdate;
// Randomize the update time interval
// to test the physics' stability
if (dt > randomDT) {
randomDT = 35 * Math.random() + 5;
Player1.update(dt);
Player2.update(dt);
lastUpdate = currUpdate;
}
// CANVAS #1 (LEFT)
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
background(ctx1);
Player1.draw(ctx1);
// CANVAS #2 (RIGHT)
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
background(ctx2);
Player2.draw(ctx2);
window.requestAnimationFrame(animate);
}
animate();
// EVENT LISTENERS -----------------------
window.addEventListener('keydown',
(e) => {
e.preventDefault();
if (Player.keys.hasOwnProperty(e.code))
Player.keys[e.code] = true;
}
)
window.addEventListener('keyup',
(e) => {
e.preventDefault()
if (Player.keys.hasOwnProperty(e.code))
Player.keys[e.code] = false;
}
)
// Just a function to draw Background nothing to see here
function background(c) {
const lineNumber = Math.floor(canvas1.height/10)
c.fillStyle = 'gray'
for(let i = 0; i < lineNumber; i++) {
c.fillRect(0, lineNumber*i, canvas1.width, 1)
}
}
div {
display: inline-block;
font-family: Arial;
}
canvas {
border: 1px solid black;
}
span {
display: block;
color: gray;
text-align: center;
}
<div>
<canvas width="100" height="160" id="canvas1"></canvas>
<span>Normal</span>
</div>
<div>
<canvas width="100" height="160" id="canvas2"></canvas>
<span>Locked</span>
</div>

Canvas starts to lag after some time

Reproduction: focus your mouse on canvas , then spin your mouse in circles for ~15 sec. At first you'll notice how things are smooth. After some time it starts to lose its smoothness and becomes really laggy.
Part of the js function came from the following answer
Make moving Rect more smooth
var canvas = document.getElementById('canvas');
var ctx = document.getElementById('canvas').getContext('2d');
var x;
var y;
var tx = tx || 0;
var ty = ty || 0;
var xDir;
var yDir;
function followMouse(e) {
x = e.offsetX;
y = e.offsetY;
moveObject();
}
function moveObject() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var scale = 0.2 * Math.max(canvas.width, canvas.height);
xDir = 0;
yDir = 0;
xDir = (x - tx) / scale;
yDir = (y - ty) / scale;
tx = tx != x ? tx + xDir : tx;
ty = ty != y ? ty + yDir : ty;
ctx.fillRect(tx - 25, ty + 25, 50, 10);
if (tx != x || ty != y) {
window.requestAnimationFrame(moveObject);
}
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
canvas.addEventListener('mousemove', _.throttle(function(e) {
followMouse(e);
}, 30));
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
html,
body {
margin: 0;
height: 100%;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<canvas id="canvas"></canvas>
This happens as for each mousemove a new loop is started. These loops will accumulate and eventually slow things down.
To solve you can implement cancelAnimationFrame() by doing:
...
var timer;
function followMouse(e) {
x = e.offsetX;
y = e.offsetY;
cancelAnimationFrame(timer);
moveObject();
}
Then store timer reference in the main loop:
...
timer = requestAnimationFrame(moveObject);
This will abort the current request for frame update and allow you to start a new loop without accumulating calls.
For this reason you would also have to initialize x and y since they are not initialized otherwise until the mouse has been moved (which is of course no guarantee).
var x = 0;
var y = 0;
Note: A side-effect of this correction is that now the movement is only calculated once per frame. When accumulated the movement got calculated many times per frame. To compensate adjust the scale to a lower value (shown below).
Modified example
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 0;
var y = 0;
var tx = tx || 0;
var ty = ty || 0;
var xDir;
var yDir;
var timer;
function followMouse(e) {
x = e.clientX;
y = e.clientY;
cancelAnimationFrame(timer);
moveObject();
}
function moveObject() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var scale = 0.02 * Math.max(canvas.width, canvas.height);
xDir = 0;
yDir = 0;
xDir = (x - tx) / scale;
yDir = (y - ty) / scale;
tx = tx != x ? tx + xDir : tx;
ty = ty != y ? ty + yDir : ty;
ctx.fillRect(tx - 25, ty + 25, 50, 10);
if (tx != x || ty != y) {
timer = requestAnimationFrame(moveObject);
}
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
canvas.addEventListener('mousemove', _.throttle(function(e) {
followMouse(e);
}, 30));
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
html,
body {
margin: 0;
height: 100%;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<canvas id="canvas"></canvas>

How to update text written on canvas

When I try to change the scoreboard using the computerCount variable (as you can see in my if statement in the update function for the ball variable) the other numbers aren't shown properly. Keep in mind that at first 0 works properly.
JSFiddle format - You can change code by going at top right corner that says "Edit" in JSFiddle.
Code:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pong</title>
<!-- Basic styling, centering the canvas -->
<style>
canvas{
position: absolute;
margin: auto;
top:60px;
bottom:0;
right:0;
left:0;
}
#scoreBoard{
position: absolute;
margin: auto;
top:0;
bottom:640px;
right:720px;
left:20px;
}
</style>
</head>
<body>
<canvas id = "scoreBoard" width="500" height = "100"></canvas>
<script>
var WIDTH = 700
var HEIGHT = 600
var pi = Math.PI
var canvas
var ctx
var keystate
var upArrow = 38;
var downArrow = 40;
var computerCount = 0;
var playerCount = 0;
var player = {
x: null,
y: null,
width: 20,
height: 100,
/**
* Update the position depending on pressed keys
*/
update: function() {
if(keystate[upArrow]){
this.y-=7;
}
if(keystate[downArrow]){
this.y+=7;
}},
/**
* Draw the player paddle to the canvas
*/
draw: function() {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
/**
* The ai paddle
*
* #type {Object}
*/
var ai = {
x: null,
y: null,
width: 20,
height: 100,
/**
* Update the position depending on the ball position
*/
update: function() {
var desty = ball.y-(this.height - ball.side)*0.5
this.y += (desty-this.y)*0.2;
},
draw: function() {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
/**
* The ball object
*
* #type {Object}
*/
var ball = {
x: null,
y: null,
vel:null,
side: 20,
speed:5,
update: function() {
this.x += this.vel.x;
this.y += this.vel.y;
if(0>this.y || this.y+this.side>HEIGHT){
var offset = this.vel.y<0 ? 0-this.y : HEIGHT-(this.y+this.side);
this.y+=2*offset
this.vel.y*=-1;
}
var AABBIntersect = function(ax, ay, aw, ah, bx, by, bw, bh) {
return ax < bx+bw && ay < by+bh && bx < ax+aw && by < ay+ah;
};
var pdle = this.vel.x<0 ? player : ai;
if(AABBIntersect(pdle.x,pdle.y,pdle.width,pdle.height, this.x, this.y, this.side, this.side)){
this.x = pdle===player ? player.x + player.width : ai.x - this.side;
var n = (this.y+this.side-pdle.y)/(pdle.height+this.side)
var phi = 0.25*pi*(2*n - 1) // pi/4 = 45
this.x = pdle===player ? player.x+player.width : ai.x - this.side;
var n = (this.y+this.side - pdle.y)/(pdle.height+this.side);
var phi = 0.25*pi*(2*n - 1); // pi/4 = 45
this.vel.x = (pdle===player ? 1 : -1)*this.speed*Math.cos(phi);
this.vel.y = this.speed*Math.sin(phi);
}
if(ball.x<0){
computerCount =+1;
ball.x = (WIDTH - ball.side) / 2;
ball.y = (HEIGHT - ball.side) / 2;
}
},
// reset the ball when ball outside of the canvas in the
draw: function() {
ctx.fillRect(this.x, this.y, this.side, this.side);
}
}
function score(){
}
/**
* Starts the game
*/
function main() {
// create, initiate and append game canvas
canvas = document.createElement("canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;
ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas2 = document.createElement("canvas");
canvas2.setAttribute("id", "scoreBoard");
canvas2.width = 200;
canvas2.height = 100;
ctx2 = canvas2.getContext("2d");
document.body.appendChild(canvas2);
keystate = {};
document.addEventListener("keydown" , function(event){
keystate[event.keyCode] = true;
})
document.addEventListener("keyup" , function(event){
delete keystate[event.keyCode];
})
init();
var loop = function() {
update();
draw();
window.requestAnimationFrame(loop, canvas);
};
window.requestAnimationFrame(loop, canvas);
}
/**
* Initatite game objects and set start positions
*/
function init() {
player.x = player.width;
player.y = (HEIGHT - player.height) / 2;
ai.x = WIDTH - (player.width + ai.width);
ai.y = (HEIGHT - ai.height) / 2;
ball.x = (WIDTH - ball.side) / 2;
ball.y = (HEIGHT - ball.side) / 2;
ball.vel = {
x:ball.speed,
y: 0
}
}
/**
* Update all game objects
*/
function update() {
ball.update();
player.update();
ai.update();
}
function draw(){
ctx.fillStyle = "black"
ctx.fillRect(0,0,WIDTH,HEIGHT)
ctx.save();
ctx2.fillStyle = "red"
ctx2.fillText(String(computerCount),100,100)
ctx2.fillText("VS",120,100)
ctx2.fillText(String(playerCount),175,100)
ctx2.font = "bold 40px monaco"
ctx.fillStyle = "white"
ball.draw();
player.draw();
ai.draw();
var w=4;
var x= (WIDTH-w)*0.5;
var y=2;
var step = HEIGHT/20;
while(y<HEIGHT){
ctx.fillRect(x,y+step*0.25,w,step*0.5)
y+=step;
}
ctx.restore();
}
// start and run the game
main();
</script>
</body>
</html>
You have two problems:
When you draw a number, you do not erase the previous number that is already printed. That is, when the score becomes 1, you render 1 on top of already rendered 0. The easiest way to address it is to do fillRect and draw a black rectangle on top of the previously written score before you draw a new score.
When you fix that, you will notice that your score never goes above 1. The reason is a typo in the code that increments it, replace
computerCount =+1;
with
computerCount += 1;
Mentioned by #Ishamael, line 139's computer count needs to be changed to computerCount += 1; //After this if statement add if the player scores.
In line 221,
function draw(){
ctx.fillStyle = "black";
ctx.fillRect(0,0,WIDTH,HEIGHT);
ctx.save();
ctx2.fillStyle = "black";
ctx2.fillRect(0,0,WIDTH, 100);
ctx2.save();
ctx2.fillStyle = "red";
ctx2.font = "bold 40px monaco";
ctx2.fillText(String(computerCount),100,100);
ctx2.fillText("VS",120,100);
ctx2.fillText(String(playerCount),175,100);
ctx2.save();
ctx.fillStyle = "white";
. . .
}
remember your semicolons and set text style before writing. I also added the repaint to erase the previous scoreboard.

How to create a camera view in canvas that will follow a players rotation and rotation?

I'm trying to create a game in canvas with javascript where you control a spaceship and have it so that the canvas will translate and rotate to make it appear like the spaceship is staying stationary and not rotating.
Any help would be greatly appreciated.
window.addEventListener("load",eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
}
function canvasSupport() {
return Modernizr.canvas;
}
function canvasApp() {
if (!canvasSupport()) {
return;
}
var theCanvas = document.getElementById("myCanvas");
var height = theCanvas.height; //get the heigth of the canvas
var width = theCanvas.width; //get the width of the canvas
var context = theCanvas.getContext("2d"); //get the context
var then = Date.now();
var bgImage = new Image();
var stars = new Array;
bgImage.onload = function() {
context.translate(width/2,height/2);
main();
}
var rocket = {
xLoc: 0,
yLoc: 0,
score : 0,
damage : 0,
speed : 20,
angle : 0,
rotSpeed : 1,
rotChange: 0,
pointX: 0,
pointY: 0,
setScore : function(newScore){
this.score = newScore;
}
}
function Star(){
var dLoc = 100;
this.xLoc = rocket.pointX+ dLoc - Math.random()*2*dLoc;
this.yLoc = rocket.pointY + dLoc - Math.random()*2*dLoc;
//console.log(rocket.xLoc+" "+rocket.yLoc);
this.draw = function(){
drawStar(this.xLoc,this.yLoc,20,5,.5);
}
}
//var stars = new Array;
var drawStars = function(){
context.fillStyle = "yellow";
if (typeof stars !== 'undefined'){
//console.log("working");
for(var i=0;i< stars.length ;i++){
stars[i].draw();
}
}
}
var getDistance = function(x1,y1,x2,y2){
var distance = Math.sqrt(Math.pow((x2-x1),2)+Math.pow((y2-y1),2));
return distance;
}
var updateStars = function(){
var numStars = 10;
while(stars.length<numStars){
stars[stars.length] = new Star();
}
for(var i=0; i<stars.length; i++){
var tempDist = getDistance(rocket.pointX,rocket.pointY,stars[i].xLoc,stars[i].yLoc);
if(i == 0){
//console.log(tempDist);
}
if(tempDist > 100){
stars[i] = new Star();
}
}
}
function drawRocket(xLoc,yLoc, rWidth, rHeight){
var angle = rocket.angle;
var xVals = [xLoc,xLoc+(rWidth/2),xLoc+(rWidth/2),xLoc-(rWidth/2),xLoc-(rWidth/2),xLoc];
var yVals = [yLoc,yLoc+(rHeight/3),yLoc+rHeight,yLoc+rHeight,yLoc+(rHeight/3),yLoc];
for(var i = 0; i < xVals.length; i++){
xVals[i] -= xLoc;
yVals[i] -= yLoc+rHeight;
if(i == 0){
console.log(yVals[i]);
}
var tempXVal = xVals[i]*Math.cos(angle) - yVals[i]*Math.sin(angle);
var tempYVal = xVals[i]*Math.sin(angle) + yVals[i]*Math.cos(angle);
xVals[i] = tempXVal + xLoc;
yVals[i] = tempYVal+(yLoc+rHeight);
}
rocket.pointX = xVals[0];
rocket.pointY = yVals[0];
//rocket.yLoc = yVals[0];
//next rotate
context.beginPath();
context.moveTo(xVals[0],yVals[0])
for(var i = 1; i < xVals.length; i++){
context.lineTo(xVals[i],yVals[i]);
}
context.closePath();
context.lineWidth = 5;
context.strokeStyle = 'blue';
context.stroke();
}
var world = {
//pixels per second
startTime: Date.now(),
speed: 50,
startX:width/2,
startY:height/2,
originX: 0,
originY: 0,
xDist: 0,
yDist: 0,
rotationSpeed: 20,
angle: 0,
distance: 0,
calcOrigins : function(){
world.originX = -world.distance*Math.sin(world.angle*Math.PI/180);
world.originY = -world.distance*Math.cos(world.angle*Math.PI/180);
}
};
var keysDown = {};
addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);
addEventListener("keyup", function (e) {
delete keysDown[e.keyCode];
}, false);
var update = function(modifier) {
if (37 in keysDown) { // Player holding left
rocket.angle -= rocket.rotSpeed* modifier;
rocket.rotChange = - rocket.rotSpeed* modifier;
//console.log("left");
}
if (39 in keysDown) { // Player holding right
rocket.angle += rocket.rotSpeed* modifier;
rocket.rotChange = rocket.rotSpeed* modifier;
//console.log("right");
}
};
var render = function (modifier) {
context.clearRect(-width*10,-height*10,width*20,height*20);
var dX = (rocket.speed*modifier)*Math.sin(rocket.angle);
var dY = (rocket.speed*modifier)*Math.cos(rocket.angle);
rocket.xLoc += dX;
rocket.yLoc -= dY;
updateStars();
drawStars();
context.translate(-dX,dY);
context.save();
context.translate(-rocket.pointX,-rocket.pointY);
context.translate(rocket.pointX,rocket.pointY);
drawRocket(rocket.xLoc,rocket.yLoc,50,200);
context.fillStyle = "red";
context.fillRect(rocket.pointX,rocket.pointY,15,5);
//context.restore(); // restores the coordinate system back to (0,0)
context.fillStyle = "green";
context.fillRect(0,0,10,10);
context.rotate(rocket.angle);
context.restore();
};
function drawStar(x, y, r, p, m)
{
context.save();
context.beginPath();
context.translate(x, y);
context.moveTo(0,0-r);
for (var i = 0; i < p; i++)
{
context.rotate(Math.PI / p);
context.lineTo(0, 0 - (r*m));
context.rotate(Math.PI / p);
context.lineTo(0, 0 - r);
}
context.fill();
context.restore();
}
// the game loop
function main(){
requestAnimationFrame(main);
var now = Date.now();
var delta = now - then;
update(delta / 1000);
//now = Date.now();
//delta = now - then;
render(delta / 1000);
then = now;
// Request to do this again ASAP
}
var w = window;
var requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
//start the game loop
//gameLoop();
//event listenters
bgImage.src = "images/background.jpg";
} //canvasApp()
Origin
When you need to rotate something in canvas it will always rotate around origin, or center for the grid if you like where the x and y axis crosses.
You may find my answer here useful as well
By default the origin is in the top left corner at (0, 0) in the bitmap.
So in order to rotate content around a (x,y) point the origin must first be translated to that point, then rotated and finally (and usually) translated back. Now things can be drawn in the normal order and they will all be drawn rotated relative to that rotation point:
ctx.translate(rotateCenterX, rotateCenterY);
ctx.rotate(angleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
Absolute angles and positions
Sometimes it's easier to keep track if an absolute angle is used rather than using an angle that you accumulate over time.
translate(), transform(), rotate() etc. are accumulative methods; they add to the previous transform. We can set absolute transforms using setTransform() (the last two arguments are for translation):
ctx.setTransform(1, 0, 0, 1, rotateCenterX, rotateCenterY); // absolute
ctx.rotate(absoluteAngleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
The rotateCenterX/Y will represent the position of the ship which is drawn untransformed. Also here absolute transforms can be a better choice as you can do the rotation using absolute angles, draw background, reset transformations and then draw in the ship at rotateCenterX/Y:
ctx.setTransform(1, 0, 0, 1, rotateCenterX, rotateCenterY);
ctx.rotate(absoluteAngleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
// update scene/background etc.
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transforms
ctx.drawImage(ship, rotateCenterX, rotateCenterY);
(Depending on orders of things you could replace the first line here with just translate() as the transforms are reset later, see demo for example).
This allows you to move the ship around without worrying about current transforms, when a rotation is needed use the ship's current position as center for translation and rotation.
And a final note: the angle you would use for rotation would of course be the counter-angle that should be represented (ie. ctx.rotate(-angle);).
Space demo ("random" movements and rotations)
The red "meteors" are dropping in one direction (from top), but as the ship "navigates" around they will change direction relative to our top view angle. Camera will be fixed on the ship's position.
(ignore the messy part - it's just for the demo setup, and I hate scrollbars... focus on the center part :) )
var img = new Image();
img.onload = function() {
var ctx = document.querySelector("canvas").getContext("2d"),
w = 600, h = 400, meteors = [], count = 35, i = 0, x = w * 0.5, y, a = 0, a2 = 0;
ctx.canvas.width = w; ctx.canvas.height = h; ctx.fillStyle = "#555";
while(i++ < count) meteors.push(new Meteor());
(function loop() {
ctx.clearRect(0, 0, w, h);
y = h * 0.5 + 30 + Math.sin((a+=0.01) % Math.PI*2) * 60; // ship's y and origin's y
// translate to center of ship, rotate, translate back, render bg, reset, draw ship
ctx.translate(x, y); // translate to origin
ctx.rotate(Math.sin((a2+=0.005) % Math.PI) - Math.PI*0.25); // rotate some angle
ctx.translate(-x, -y); // translate back
ctx.beginPath(); // render some moving meteors for the demo
for(var i = 0; i < count; i++) meteors[i].update(ctx); ctx.fill();
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transforms
ctx.drawImage(img, x - 32, y); // draw ship as normal
requestAnimationFrame(loop); // loop animation
})();
};
function Meteor() { // just some moving object..
var size = 5 + 35 * Math.random(), x = Math.random() * 600, y = -200;
this.update = function(ctx) {
ctx.moveTo(x + size, y); ctx.arc(x, y, size, 0, 6.28);
y += size * 0.5; if (y > 600) y = -200;
};
}
img.src = "http://i.imgur.com/67KQykW.png?1";
body {background:#333} canvas {background:#000}
<canvas></canvas>

HTML5 canvas, make image rotate around click to select and drag circle

I have completed code that has a user click a button (to the right of the canvas), then the image is added to the canvas and is constrained to only move around the circumference of a circle. In order to move the image the user just needs to click the image and then move the mouse. To release the image the user simply needs to click where the image goes on the canvas.
Here is a fiddle showing what the current code does.
http://jsfiddle.net/smacnabb/68awv7sq/9/
Question: I am looking to be able to have the images that move around the circumference of the circle rotate while moving around the circumference of the circle.
This is what I mean:
Here is a fiddle for the code I added to try and make this happen
http://jsfiddle.net/smacnabb/68awv7sq/11/
in the handlemousemove method, it calls state.draw() every time the mouse move i'm passing mouseX, mouseY to state.draw.
state.draw() is in addstate method and this was the code I added to make the image rotate
var dx = mouseX - centerX;
var dy = mouseY - centerY;
var radianAngle = Math.atan2(dy, dx);
context.save();
context.translate(centerX, centerY);
context.rotate(radianAngle);
if (this.dragging) {
context.strokeStyle = 'black';
context.strokeRect(this.x, this.y, this.width + 2, this.height + 2)
}
context.drawImage(this.image, this.x, this.y);
context.restore();
What am I doing wrong?
You are close...Take a look at this example:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var radianAngle = 0;
var cx = 225;
var cy = 125;
var radius = 50;
// image loader
var imageURLs = [];
var imagesOK = 0;
var imgs = [];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/cars.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/plane.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/cars1.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/plane1.png");
loadAllImages(start);
function loadAllImages(callback) {
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function() {
imagesOK++;
if (imagesOK >= imageURLs.length) {
callback();
}
};
img.onerror = function() {
alert("image load failed");
}
img.crossOrigin = "anonymous";
img.src = imageURLs[i];
}
}
var imagesY = 20;
var targets = [];
var selectedTarget = -1;
function start() {
var n = imgs.length / 2;
for (var i = 0; i < n; i++) {
var target = imgs[i + n];
ctx.drawImage(target, 15, imagesY);
targets.push({
x: 15,
y: imagesY,
width: target.width,
height: target.height,
image: imgs[i]
});
imagesY += target.height + 10;
}
}
function handleMouseDown(e) {
e.preventDefault();
x = parseInt(e.clientX - offsetX);
y = parseInt(e.clientY - offsetY);
for (var i = 0; i < targets.length; i++) {
var t = targets[i];
if (x >= t.x && x <= t.x + t.width && y >= t.y && y <= t.y + t.height) {
selectedTarget = i;
draw(x, y);
}
}
}
function handleMouseMove(e) {
if (selectedTarget < 0) {
return;
}
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
draw(mouseX, mouseY);
}
function draw(mouseX, mouseY) {
var dx = mouseX - cx;
var dy = mouseY - cy;
var radianAngle = Math.atan2(dy, dx);
// Drawing code goes here
var img = targets[selectedTarget].image;
ctx.clearRect(100, 0, canvas.width, canvas.height);
// draw the circle
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
// draw the image rotated around the circumference
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(radianAngle);
ctx.drawImage(img, radius - img.width / 2, -img.height / 2);
ctx.restore();
}
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function(e) {
handleMouseMove(e);
});
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Select an icon on left by clicking<br>
Then move mouse to have icon rotate around circle</h4>
<canvas id="canvas" width=350 height=350></canvas>

Categories

Resources