(toggle) function is not defined - javascript

I'm trying to use a button to toggle the audio for a P5 oscillator on and off (because as I understand it, WebAudio API stuff requires an interaction in browsers now for audio to play).
Before the setup I have:
var button;
In the function setup I have:
button = createButton("audio on/off");
button.mousePressed(toggle);
button.position(32, 180);
In function toggle I have:
if (!playing()) {
osc.start();
//playing = true; } else {
osc.stop();
//playing = false; }
It keeps giving me the error message
Uncaught ReferenceError: toggle is not defined (sketch: line 45)"
Why do I keep getting this? Any help would be greatly appreciated.
The full sketch.js code is:
var button;
var stepX;
var stepY;
let osc;
function setup() {
createCanvas(displayWidth, displayHeight);
noCursor();
noStroke();
colorMode(HSB, width, height, 100);
reverb = new p5.Reverb();
delay = new p5.Delay();
osc = new p5.Oscillator();
osc.setType('sine');
reverb.process(osc, 5, 5);
osc.start();
// delay.process() accepts 4 parameters:
// source, delayTime, feedback, filter frequency
// play with these numbers!!
delay.process(osc, 0.6, 0.3, 1000);
// play the noise with an envelope,
// a series of fades ( time / value pairs )
var t = 10
let text = createP("s-p-e-c-t-r-u-m");
text.position(30, 30);
text.style("font-family", "monospace");
text.style("background-color", "#FFFFFF");
text.style("color", "#F20FD7");
text.style("font-size", "12pt");
text.style("padding", "10px");
let texty = createP("move mouse to control the <br>sine-wave theramin.<br><br>x axis = pitch 30-3000hz<br>y axis = volume quiet-loud)");
texty.position(32, 80);
//texty.style("background-color", "#FFFFFF");
texty.style("font-family", "monospace");
texty.style("color", "#FFFFFF");
texty.style("font-size", "10pt");
button = createButton("audio on/off");
button.mousePressed(toggle);
button.position(32, 180);
}
function draw() {
let pitch = map(mouseX, 0, width, 30, 3000);
let volume = map(mouseY, 0, height, 1, 0);
background(200);
osc.freq(pitch);
osc.amp(volume);
stepX = mouseX + 2;
stepY = mouseY + 2;
for (var gridY = 0; gridY < height; gridY += stepY) {
for(var gridX = 0; gridX < width; gridX += stepX) {
fill(gridX, height - gridY, 100);
rect(gridX, gridY, stepX, stepY);
}
}
function toggle() {
if (!playing) {
osc.start();
playing = true;
} else {
osc.stop();
playing = false;
}
}
}

toggle is declared in scope of draw:
function draw() {
// [...]
function toggle() {
// [...]
}
}
Move it out of draw, top solve the issue:
function draw() {
// [...]
}
function toggle() {
// [...]
}
See the example:
var button;
var stepX;
var stepY;
let osc;
function setup() {
createCanvas(displayWidth, displayHeight);
noCursor();
noStroke();
colorMode(HSB, width, height, 100);
reverb = new p5.Reverb();
delay = new p5.Delay();
osc = new p5.Oscillator();
osc.setType('sine');
reverb.process(osc, 5, 5);
osc.start();
// delay.process() accepts 4 parameters:
// source, delayTime, feedback, filter frequency
// play with these numbers!!
delay.process(osc, 0.6, 0.3, 1000);
// play the noise with an envelope,
// a series of fades ( time / value pairs )
var t = 10
let text = createP("s-p-e-c-t-r-u-m");
text.position(30, 30);
text.style("font-family", "monospace");
text.style("background-color", "#FFFFFF");
text.style("color", "#F20FD7");
text.style("font-size", "12pt");
text.style("padding", "10px");
let texty = createP("move mouse to control the <br>sine-wave theramin.<br><br>x axis = pitch 30-3000hz<br>y axis = volume quiet-loud)");
texty.position(32, 80);
//texty.style("background-color", "#FFFFFF");
texty.style("font-family", "monospace");
texty.style("color", "#FFFFFF");
texty.style("font-size", "10pt");
button = createButton("audio on/off");
button.mousePressed(toggle);
button.position(32, 180);
}
function draw() {
let pitch = map(mouseX, 0, width, 30, 3000);
let volume = map(mouseY, 0, height, 1, 0);
background(200);
osc.freq(pitch);
osc.amp(volume);
stepX = mouseX + 2;
stepY = mouseY + 2;
for (var gridY = 0; gridY < height; gridY += stepY) {
for(var gridX = 0; gridX < width; gridX += stepX) {
fill(gridX, height - gridY, 100);
rect(gridX, gridY, stepX, stepY);
}
}
}
function toggle() {
if (!playing) {
osc.start();
playing = true;
} else {
osc.stop();
playing = false;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.sound.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.js"></script>

Try assigning your functions to variables. Without doing that all your functions will be hoisted to the top of the file at run time. Also make sure you declare toggle before you use it!

Related

Move an object through set of coordinates on HTML5 Canvas

I want to move a object (circle in this case) through array of coordinates (for example: {(300,400), (200,300), (300,200),(400,400)})on HTML5 Canvas. I could move the object to one coordinate as follows. The following code draws a circle at (100,100) and moves it to (300,400). I am stuck when trying to extend this so that circle moves through set of coordinates one after the other.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
//circle object
let circle ={
x:100,
y:100,
radius:10,
dx:1,
dy:1,
color:'blue'
}
//function to draw above circle on canvas
function drawCircle(){
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2);
ctx.fillStyle=circle.color;
ctx.fill();
ctx.closePath();
}
//Moving to a target coordinate (targetX,targetY)
function goTo(targetX,targetY){
if(Math.abs(circel.x-targetX)<circle.dx && Math.abs(circel.y-targetY)<circle.dy){
circle.dx=0;
circle.dy=0;
circel.x = targetX;
circle.y = targetY;
}
else{
const opp = targetY - circle.y;
const adj = targetX - circle.x;
const angle = Math.atan2(opp,adj)
circel.x += Math.cos(angle)*circle.dx
circle.y += Math.sin(angle)*circle.dy
}
}
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawCircle()
goTo(300,400)
requestAnimationFrame(update);
}
update()
Random access key frames
For the best control of animations you need to create way points (key frames) that can be accessed randomly by time. This means you can get any position in the animation just by setting the time.
You can then play and pause, set speed, reverse and seek to any position in the animation.
Example
The example below uses a set of points and adds data required to quickly locate the key frames at the requested time and interpolate the position.
The blue dot will move at a constant speed over the path in a time set by pathTime in this case 4 seconds.
The red dot's position is set by the slider. This is to illustrate the random access of the animation position.
const ctx = canvas.getContext('2d');
const pathTime = 4; // Total time to travel path from start to end in seconds
var startTime, animTime = 0, paused = false;
requestAnimationFrame(update);
const P2 = (x, y) => ({x, y, dx: 0,dy: 0,dist: 0, start: 0, end: 0});
const pathCoords = [
P2(20, 20), P2(100, 50),P2(180, 20), P2(150, 100), P2(180, 180),
P2(100, 150), P2(20, 180), P2(50, 100), P2(20, 20),
];
createAnimationPath(pathCoords);
const circle ={
draw(rad = 10, color = "blue") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, rad, 0, Math.PI * 2);
ctx.fill();
}
};
function createAnimationPath(points) { // Set up path for easy random position lookup
const segment = (prev, next) => {
[prev.dx, prev.dy] = [next.x - prev.x, next.y - prev.y];
prev.dist = Math.hypot(prev.dx, prev.dy);
next.end = next.start = prev.end = prev.start + prev.dist;
}
var i = 1;
while (i < points.length) { segment(points[i - 1], points[i++]) }
}
function getPos(path, pos, res = {}) {
pos = (pos % 1) * path[path.length - 1].end; // loop & scale to total length
const pathSeg = path.find(p => pos >= p.start && pos <= p.end);
const unit = (pos - pathSeg.start) / pathSeg.dist; // unit distance on segment
res.x = pathSeg.x + pathSeg.dx * unit; // x, y position on segment
res.y = pathSeg.y + pathSeg.dy * unit;
return res;
}
function update(time){
// startTime ??= time; // Throws syntax on iOS
startTime = startTime ?? time; // Fix for above
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (paused) { startTime = time - animTime }
else { animTime = time - startTime }
getPos(pathCoords, (animTime / 1000) / pathTime, circle).draw();
getPos(pathCoords, timeSlide.value, circle).draw(5, "red");
requestAnimationFrame(update);
}
pause.addEventListener("click", ()=> { paused = true; pause.classList.add("pause") });
play.addEventListener("click", ()=> { paused = false; pause.classList.remove("pause") });
rewind.addEventListener("click", ()=> { startTime = undefined; animTime = 0 });
div {
position:absolute;
top: 5px;
left: 20px;
}
#timeSlide {width: 360px}
.pause {color:blue}
button {height: 30px}
<div><input id="timeSlide" type="range" min="0" max="1" step="0.001" value="0" width= "200"><button id="rewind">Start</button><button id="pause">Pause</button><button id="play">Play</button></div>
<canvas id="canvas" width="200" height="200"></canvas>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// array of path coords
const pathCoords = [
[200,100],
[300, 150],
[200,190],
[400,100],
[50,10],
[150,10],
[0, 50],
[500,90],
[20,190],
[10,180],
];
// current point
let currentTarget = pathCoords.shift();
//circle object
const circle ={
x:10,
y:10,
radius:10,
dx:2,
dy:2,
color:'blue'
}
//function to draw above circle on canvas
function drawCircle(){
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2);
ctx.fillStyle=circle.color;
ctx.fill();
ctx.closePath();
}
//Moving to a target coordinate (targetX,targetY)
function goTo(targetX, targetY){
if(Math.abs(circle.x-targetX)<circle.dx && Math.abs(circle.y-targetY)<circle.dy){
// dont stop...
//circle.dx = 0;
//circle.dy = 0;
circle.x = targetX;
circle.y = targetY;
// go to next point
if (pathCoords.length) {
currentTarget = pathCoords.shift();
} else {
console.log('Path end');
}
} else {
const opp = targetY - circle.y;
const adj = targetX - circle.x;
const angle = Math.atan2(opp,adj)
circle.x += Math.cos(angle)*circle.dx
circle.y += Math.sin(angle)*circle.dy
}
}
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawCircle();
goTo(...currentTarget);
requestAnimationFrame(update);
}
update();
<canvas id=canvas width = 500 height = 200></canvas>

p5js - Animating lines with 3 different positions

In my project i have 2 lines.
Each line changes position when you click on the screen.
The position of each line always falls randomly in thirds of the screen width.
I accomplished the clicks and the random position in thirds.
Now i would like the lines to animate to the new position on mousePress but i don’t know how to go about it. i’m wondering if a have to rebuild in another way.
can someone guide me? :slight_smile:
https://editor.p5js.org/antoniofrom1984/sketches/8n9le2Wvh
function setup(){
createCanvas(windowWidth, windowHeight);
}
function draw(){
noLoop();
background(backcolor);
}
function mousePressed(){
loop();
var h = height;
var w = width;
var thirdsH = [h/3, h/2, h/1.5 ];
var thirdsV = [w/3, w/2, w/1.5];
var randomthirdsH = random(thirdsH);
var randomthirdsV = random(thirdsV);
strokeWeight(2);
stroke(linecolor);
line(0, randomthirdsH, w, randomthirdsH);
line(randomthirdsV, 0 ,randomthirdsV, h);
print(randomthirdsH);
}
To do what you want you, you've to remove tha noLoop() instruction and to implement the animation in draw().
Define a current_pos variable for the current position of the line, a target_pos variable for the target position of the line and a speed for the animation. Let current_pos and target_pos undefined:
let current_pos, target_pos;
let speed = 2;
Set the target position target_pos, when the mouse is pressed:
function mousePressed(){
var h = height;
var w = width;
var thirdsH = [h/3, h/2, h/1.5 ];
var thirdsV = [w/3, w/2, w/1.5];
target_pos = [random(thirdsV), random(thirdsH)];
}
As soon as target_pos is define, start to draw the line in draw. If current_pos is not defined, then initialize current_pos by target_pos. This happens when the line is drawn the first time:
if (target_pos) {
if (!current_pos) {
current_pos = [target_pos[0], target_pos[1]];
} else {
// [...]
}
// [...]
}
When the target_pos is changed the change the current_pos by speed and slightly move it in the direction of target_pos:
for (let i = 0; i < 2; ++i) {
if (current_pos[i] < target_pos[i])
current_pos[i] = Math.min(target_pos[i], current_pos[i]+speed)
else if (current_pos[i] > target_pos[i])
current_pos[i] = Math.max(target_pos[i], current_pos[i]-speed)
}
Always draw the line at current_pos:
line(0, current_pos[1], width, current_pos[1]);
line(current_pos[0], 0, current_pos[0], height);
See the example, where I applied the suggestions to your original code:
let backcolor = (0, 0, 0);
let linecolor = (255, 255, 255);
let current_pos, target_pos;
let speed = 2;
function setup(){
createCanvas(windowWidth, windowHeight);
// this is just to see somthing at start
target_pos = [10, 10]
}
function draw(){
background(backcolor);
if (target_pos) {
if (!current_pos) {
current_pos = [target_pos[0], target_pos[1]];
} else {
for (let i = 0; i < 2; ++i) {
if (current_pos[i] < target_pos[i])
current_pos[i] = Math.min(target_pos[i], current_pos[i]+speed)
else if (current_pos[i] > target_pos[i])
current_pos[i] = Math.max(target_pos[i], current_pos[i]-speed)
}
}
// draw lines
strokeWeight(2);
stroke(linecolor);
line(0, current_pos[1], width, current_pos[1]);
line(current_pos[0], 0, current_pos[0], height);
// draw target marker
strokeWeight(3);
stroke(255, 0, 0);
line(target_pos[0]-10, target_pos[1], target_pos[0]+10, target_pos[1]);
line(target_pos[0], target_pos[1]-10, target_pos[0], target_pos[1]+10);
}
}
function mousePressed(){
var h = height;
var w = width;
var thirdsH = [h/3, h/2, h/1.5 ];
var thirdsV = [w/3, w/2, w/1.5];
target_pos = [random(thirdsV), random(thirdsH)];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
I assume you mean that the splitting of the canvas happens at the point where you click, something like this:
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
var backcolor = "rgb(194, 24, 91)";
var linecolor = "rgb(240, 98, 146)";
var h;
var w;
var thirdsH;
var thirdsV;
var randomthirdsH;
var randomthirdsV;
function setup(){
createCanvas(windowWidth, windowHeight);
}
function draw(){
noLoop();
background(backcolor);
}
function mousePressed(){
loop();
var h = height;
var w = width;
var thirdsH = [h/3, h/2, h/1.5 ];
var thirdsV = [w/3, w/2, w/1.5];
var randomthirdsH = mouseY;
var randomthirdsV = mouseX;
strokeWeight(2);
stroke(linecolor);
line(0, randomthirdsH, w, randomthirdsH);
line(randomthirdsV, 0 ,randomthirdsV, h);
print(randomthirdsH);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
The key thing here being:
var randomthirdsH = mouseY;
var randomthirdsV = mouseX;
In the mousePressed() handler.

Canvas game, where to write code for background?

The Problem
I have been creating a game, I have got to a stage where I want to see what it looks like with a mockup background I have created.
The Question
Where about in my code should I place this code as the place it currently is doesnt show the background.
I want this background on the canvas, the dimensions are correct.
The Code
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var background_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
background_img.src = "background.png";
background_img.onload = function(){
context.drawImage(background_img, 0, 0);
}
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#410b11";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 20) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 20) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#410b11";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
ctx.drawImage(background_img, 0, backgroundY);
}
return {
init: init
};
}
As already pointed out in a comment, here more precisely:
First of all, the background picture must be rendered first in every animation frame.
However, the picture didn't show up at all. This is due to the fact that variable was used (backgroundY), which is never declared somewhere.
This should actually printed to the console as an error "backgroundY" is not defined.
Whenever an the property src of an image object is set to a value, it takes some time until it's loaded. So in many cases, it's necessary to indicate the moment, when it's finished loading by the onload callback.
In this case, however, it's not necessary. The tick / animation loop function will just draw nothing (an empty image object) until it's loaded. After it's loaded it will continue to draw the loaded image every frame.
If the background is really important, meaning, the app should only start, when it's there, of course, one can only start the whole game / animation from within the img.onload handler.
You must draw:
the background first
the player later
level/score info last
Background < Player < UI < You Looking
The drawing order is from back to top (painters algorithm)
Also note that for performance reasons if you background never changes you could draw it in another 'static' canvas under the game canvas.
Otherwise the background will be drawn above/over the player and hide it.

How do I reset the velocity variable of my object

How can I reset the velocity variable in my objects. I am making a canvas game, in which stars fall from the top of the Canvas. Problem is when I run the game in a setinterval() the velocity keeps getting greater and greater. What i want is the speed to stay the same unless i change it.
function Star(x, y, rad, velocity, fill){
this.x = Math.floor(Math.random() * 999);//this create a random number between 0 and 599 on the x axis
this.y = 0;
this.rad = Math.floor((Math.random() * 30) + 15);//this create a random number between 10 and 30 for the radius
this.velocity = 5;
this.fill = fill
this.draw = function(){
ctx.beginPath();
ctx.fillStyle = this.fill;
ctx.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
this.y += this.velocity;
}
}
function createMultipleStars(){
for (var i = 0; i <= numOfStars; i++)
stars[i] = new Star(i * 50, 10, i, i, "rgba(255,215,0,0.6)");
}
//createMultipleStars();
function step() {
ctx.clearRect(0,0,canvas.width, canvas.height);
for (var i = 0; i<= numOfStars; i++)
stars[i].draw();
requestAnimationFrame(step);
}
spaceShip.drawSpaceShip();
var myVar = setInterval(function(){ init() }, 4000);
function init(){
createMultipleStars();
step();
}
Your frames per second were increasing with each interval. Every four seconds another step function is added to the animation frame. To fix this I added an fps counter and singleton pattern. With the singleton pattern you shouldn't break the requestAnimationFrame max 60 fps. Without it you will see that the fps increases. Technically it can't go above 60 but the step function runs multiple times in the same frame increasing the velocity each time and making the stars run faster.
var canvas = document.getElementById('can');
var ctx = canvas.getContext('2d');
var stars = [];
var numOfStars = 10;
function Star(x, y, rad, velocity, fill) {
this.x = Math.floor(Math.random() * 999); //this create a random number between 0 and 599 on the x axis
this.y = 0;
this.rad = Math.floor((Math.random() * 30) + 15); //this create a random number between 10 and 30 for the radius
this.velocity = 5;
this.fill = fill
this.draw = function() {
ctx.beginPath();
ctx.fillStyle = this.fill;
ctx.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
this.y += this.velocity;
}
}
function createMultipleStars() {
for (var i = 0; i <= numOfStars; i++) {
stars[i] = new Star(i * 50, 10, i, i, "rgba(255,215,0,0.6)");
}
}
function fps() {
var now = (new Date()).getTime();
fps.frames++;
if ((now - fps.lastFps) >= 1000) {
fps.total = fps.frames;
fps.lastFps = now;
fps.frames = 0;
}
return fps.total;
}
fps.frames = 0;
fps.lastFps = (new Date()).getTime();
fps.total = 0;
// Step is a singleton. Only one instance can be created.
function Step() {
// comment out the line below to see what happens when not running
// singleton
if (Step.instance !== null) return Step.instance;
var self = this;
function frame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i <= numOfStars; i++) {
stars[i].draw();
}
ctx.fillStyle = "red";
ctx.fillText("FPS: " + fps(), 10, 10);
requestAnimationFrame(frame);
}
frame();
Step.instance = this;
return this;
}
Step.instance = null;
//spaceShip.drawSpaceShip();
function init() {
var myVar = setInterval(function() {
createMultipleStars();
var step = new Step();
}, 4000);
createMultipleStars();
//
var step = new Step();
var step = new Step();
var step = new Step();
var step = new Step();
}
init();
#can {
border: 1px solid #FF0000;
}
<canvas id="can" width="400" height="200"></canvas>
Your issue is very simple : you are using both requestAnimationFrame and setInterval to drive the animation. More and more render loops get created and run at the same time, causing the issue
Separate the concerns :
have one render loop working for ever with RequestAnimationFrame
Have a setInterval-ed function inject some new stuff in your game.
So the only change you need to do is here :
var myVar = setInterval(createMultipleStars, 4000);

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>

Categories

Resources