Related
This question already has an answer here:
HTML5 Canvas slows down with each stroke and clear
(1 answer)
Closed 10 months ago.
I am creating a web based drawing board. Part of the functionality of this is to draw shapes (like lines).
I'm finding myself needing to keep track of all the points drawn in the canvas in an array. Specifically because I need to clear the board and redraw the points when drawing lines and other shapes to make it feel "interactive". To clarify the functionality, I've created this JSFiddle https://jsfiddle.net/hwa95rcx/35/
HTML:
<canvas id="test" width="500" height="500">
</canvas>
JavasScript:
let lineStartX = 0;
let lineStartY = 0;
const drawingPoints = [];
let mouseDown = false;
const c = document.getElementById("test");
const ctx = c.getContext("2d");
c.onmousedown = (e) => {
lineStartX = e.clientX;
lineStartY = e.clientY;
mouseDown = true;
}
c.onmousemove = (e) => {
if (mouseDown) {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
redraw();
ctx.moveTo(lineStartX, lineStartY);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
}
c.onmouseup = (e) => {
mouseDown = false;
drawingPoints.push({
startX: lineStartX,
startY: lineStartY,
lineEndX: e.clientX,
lineEndY: e.clientY
});
}
function redraw() {
drawingPoints.forEach(el => {
ctx.moveTo(el.startX, el.startY);
ctx.lineTo(el.lineEndX, el.lineEndY);
ctx.stroke()
});
}
that exemplifies the reason I need to store an array of points to redraw.
I would like to avoid doing this because at large quantities of data, the drawing slows down or even crashes because we are looping through all the points to redraw them. Is there a more performant way of achieving this interactive shape drawing effect I am looking for?
Your redraw function calls stroke() for every sub-segment, but never calls beginPath(). This means that when you have two sub-segments, the first one will get painted at its own turn, and then again when the second sub-segment calls stroke().
You can see this quite clearly by looking at your first strokes and how their antialiasing darkens the more sub-segments there are.
You are actually asking the computer to render magnitudes more items than required, this is what is making your app slow and eventually crash.
So you could call beginPath() in the loop, but the best is instead to remove entirely stroke() from there. This way, at every frame you only paint one single path. The GPU has only one single operation to do and you should be fine for days of drawings.
let lineStartX = 0;
let lineStartY = 0;
const drawingPoints = [];
let mouseDown = false;
const c = document.getElementById("test");
const ctx = c.getContext("2d");
c.onmousedown = (e) => {
lineStartX = e.clientX;
lineStartY = e.clientY;
mouseDown = true;
}
c.onmousemove = (e) => {
if (mouseDown) {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
redraw();
ctx.moveTo(lineStartX, lineStartY);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
}
c.onmouseup = (e) => {
mouseDown = false;
drawingPoints.push({
startX: lineStartX,
startY: lineStartY,
lineEndX: e.clientX,
lineEndY: e.clientY
});
}
function redraw() {
// 'redraw' only traces the previous sub-segments
drawingPoints.forEach(el => {
ctx.moveTo(el.startX, el.startY);
ctx.lineTo(el.lineEndX, el.lineEndY);
});
}
<canvas id="test" width="500" height="500"></canvas>
Note that if you measure that even the tracing takes some computation time (though this would be surprising), you may want to try storing the sub-path in a Path2D object instead of calling the drawing methods every time:
let lineStartX = 0;
let lineStartY = 0;
const path = new Path2D();
let mouseDown = false;
const c = document.getElementById("test");
const ctx = c.getContext("2d");
c.onmousedown = (e) => {
lineStartX = e.clientX;
lineStartY = e.clientY;
mouseDown = true;
}
c.onmousemove = (e) => {
if (mouseDown) {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
redraw();
ctx.moveTo(lineStartX, lineStartY);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
}
c.onmouseup = (e) => {
mouseDown = false;
path.moveTo(lineStartX, lineStartY);
path.lineTo(e.clientX, e.clientY);
}
function redraw() {
ctx.stroke(path);
}
<canvas id="test" width="500" height="500"></canvas>
The Problem
I am creating a game using the HTML5 Canvas, the game has a main menu, the main menu has multiple buttons for you to choose. I am finding it difficult and confusing how I would, for example if the user presses the 'Play' button, to show the game. Here is an image of the main menu:
The Question
The question is how would I get from this page to another in my game?
I think you get the idea. I deliberately created the menu using the canvas, I know I could of made the menu using HTML for example but I cant as this is an example for students of what Canvas can do, whats good and bad etc.
The Code
<html>
<head>
<title>Sean Coyne</title>
</head>
<body onload="start_game()">
<body>
<div style id="canvas">
<canvas id="myCanvas" style="border:5px solid #410b11" height="320" width="480">
<p>Your browser does not support HTML5!</p>
</canvas>
<script type="text/javascript">
//Referencing the canvas
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var width = canvas.getAttribute('width');
var height = canvas.getAttribute('height');
//Finding the position of the mouse
var mouseX;
var mouseY;
//Images
var bgImage = new Image();
var logoImage = new Image();
var playImage = new Image();
var instructImage = new Image();
var settingsImage = new Image();
var aboutImage = new Image();
var peaceImage = new Image();
var backgroundY = 0;
var speed = 1;
//Arrays below used for mouse over function
var buttonX = [130,110,130,160];
var buttonY = [100,140,180,220];
var buttonWidth = [96,260,182,160];
var buttonHeight = [40,40,40,40];
var peaceX = [0,0];
var peaceY = [0,0];
var peaceWidth = 35;
var peaceHeight = 35;
var peaceVisible = false;
var peaceSize = peaceWidth;
var peaceRotate = 0;
var frames = 30;
var timerId = 0;
var fadeId = 0;
var time = 0.0;
peaceImage.src = "Images/peace.png";
bgImage.onload = function(){
context.drawImage(bgImage, 0, backgroundY);
};
bgImage.src = "Images/background.png";
logoImage.onload = function(){
context.drawImage(logoImage, 50, -10);
}
logoImage.src = "Images/logo.png";
playImage.onload = function(){
context.drawImage(playImage, buttonX[0], buttonY[0]);
}
playImage.src = "Images/play.png";
instructImage.onload = function(){
context.drawImage(instructImage, buttonX[1], buttonY[1]);
}
instructImage.src = "Images/instructions.png";
settingsImage.onload = function(){
context.drawImage(settingsImage, buttonX[2], buttonY[2]);
}
settingsImage.src = "Images/settings.png";
aboutImage.onload = function(){
context.drawImage(aboutImage, buttonX[3], buttonY[3]);
}
aboutImage.src = "Images/about.png";
timerId = setInterval("update()", 1000/frames);
canvas.addEventListener("mousemove", checkPos);
canvas.addEventListener("mouseup", checkClick);
function update() {
clear();
move();
draw();
}
function clear() {
context.clearRect(0, 0, width, height);
}
function move(){
backgroundY -= speed;
if(backgroundY == -1 * height){
backgroundY = 0;
}
if(peaceSize == peaceWidth){
peaceRotate = -1;
}
if(peaceSize == 0){
peaceRotate = 1;
}
peaceSize += peaceRotate;
}
function draw(){
context.drawImage(bgImage, 0, backgroundY);
context.drawImage(logoImage, 50,-10);
context.drawImage(playImage, buttonX[1], buttonY[0]);
context.drawImage(instructImage, buttonX[2], buttonY[1]);
context.drawImage(settingsImage, buttonX[2], buttonY[2]);
context.drawImage(aboutImage, buttonX[3], buttonY[3]);
if(peaceVisible == true){
context.drawImage(peaceImage, peaceX[0] - (peaceSize/2), peaceY[0], peaceSize, peaceHeight);
context.drawImage(peaceImage, peaceX[2] - (peaceSize/2), peaceY[2], peaceSize, peaceHeight);
}
}
function checkPos(mouseEvent){
if(mouseEvent.pageX || mouseEvent.pageY == 0){
mouseX = mouseEvent.pageX - this.offsetLeft;
mouseY = mouseEvent.pageY - this.offsetTop;
}else if(mouseEvent.offsetX || mouseEvent.offsetY == 0){
mouseX = mouseEvent.offsetX;
mouseY = mouseEvent.offsetY;
}
for(i = 0; i < buttonX.length; i++){
if(mouseX > buttonX[i] && mouseX < buttonX[i] + buttonWidth[i]){
if(mouseY > buttonY[i] && mouseY < buttonY[i] + buttonHeight[i]){
peaceVisible = true;
peaceX[0] = buttonX[i] - (peaceWidth/2) - 2;
peaceY[0] = buttonY[i] + 2;
peaceX[1] = buttonX[i] + buttonWidth[i] + (peaceWidth/2);
peaceY[1] = buttonY[i] + 2;
}
}else{
peaceVisible = false;
}
}
}
function checkClick(mouseEvent){
for(i = 0; i < buttonX.length; i++){
if(mouseX > buttonX[i] && mouseX < buttonX[i] + buttonWidth[i]){
if(mouseY > buttonY[i] && mouseY < buttonY[i] + buttonHeight[i]){
fadeId = setInterval("fadeOut()", 1000/frames);
clearInterval(timerId);
canvas.removeEventListener("mousemove", checkPos);
canvas.removeEventListener("mouseup", checkClick);
}
}
}
}
function fadeOut(){
context.fillStyle = "rgba(0,0,0, 0.2)";
context.fillRect (0, 0, width, height);
time += 0.1;
if(time >= 2){
clearInterval(fadeId);
time = 0;
timerId = setInterval("update()", 1000/frames);
canvas.addEventListener("mousemove", checkPos);
canvas.addEventListener("mouseup", checkClick);
}
}
</script>
</body>
</html>
What I usually do is have a switch statement inside the draw loop, and a state variable which holds the current game state (menu, playing, etc...).
Then, based on the current game state you only draw the objects required for the current scene.
Something like this:
var STATES = {
Menu: 0,
PauseMenu: 1,
Playing: 2
};
var currentState = STATES.Menu;
...
function draw() {
switch(currentState) {
case STATES.Menu:
// Draw buttons, etc..
break;
case STATES.Playing:
// Draw the game screen, the player, etc...
break;
}
}
When the user presses the Play button the only thing you have to do is:
function onPlayButtonClick() {
currentState = STATES.Playing;
// Starting the next frame the new state will be "magically" drawn
}
If you don't like the switch statement, you can create a State class that has a draw method. Then you can simply create new states, each with it's own drawing method and in the main draw loop only call the draw method of the current state.
Same goes for the update function, each state has it's own update function (in the main menu you update buttons or animate things, while playing the game you update the game world and run your physics). So, based on the current state your update function is actually different. It's up to you how you structure your code and how you call different functions based on the current state.
In each text option, you should create a smaller Canvas, only with the option text and add a 'click' event with the callbacks.
Tip: You don't need another page, just erase the main canvas and draw what you want.
I have create a drawing canvas. In javascript.
But when i move my mouse fast i haven't a full line but a line with points.
I have used an arc for this i dont know of there is a better option for painting a line
How can i fix this ??
already thanks
<script type="application/javascript" src="jquery.js"></script>
<script>
var canvas;
var ctx;
var StartDraw = false;
var dikte = 7;
$(document).ready(DoInit());
function DoInit()
{
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
$(".kleur").on('click',doKleur);
$("canvas").on('mouseup',DoUp);
$("canvas").on('mousedown',DoDown);
$("canvas").on('mousemove',DoMove)
$("#dikte").on('change',doDikte);
$(".clear").on('click',clear);
}
function doDikte(){
dikte = this.value;
}
function clear(){
ctx.clearRect(0,0,canvas.width,canvas.height);
}
function doKleur(){
ctx.fillStyle = this.id;
}
function DoDown()
{
StartDraw = true;
}
function DoUp()
{
StartDraw = false;
}
function DoDot(x,y)
{
ctx.beginPath();
ctx.arc(x, y, dikte, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
function DoMove(event)
{
if(StartDraw)
{
DoDot(event.offsetX, event.offsetY)
}
}
</script>
<style>
canvas
{
border: solid 5px red;
border-radius: 15px;
}
</style>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<canvas id="canvas" width="1000" height="650"></canvas>
<input class="kleur" type="button" id="blue" value="blauw">
<input class="kleur" type="button" id="green" value="groen">
<input class="kleur" type="button" id="red" value="rood">
<input class="kleur" type="button" id="black" value="zwart">
<input class="kleur" type="button" id="orange" value="oranje">
<input type="button" class="clear" value="clear">
<input type="range" id="dikte" min="1" max="35" value="7">
</body>
</html>
You need to check when the mouse has just been clicked, is being held, when it released, or is just a quick click. You need to draw line segments when the mouse is being dragged, or just a point if its a quick click.
I prefer to bundle all my mouse events into one function and I create an abstract mouse object that I then use for whatever needs I have.
When I handle a mouse event, be that a move or click, whatever it is the last thing I do is save the button state. That means that next time the mouse event is called I know by checking the last and current mouse button state if the button was just clicked, is being held, or just up. You may say that the mousedown and mouseup events do that for you already, and yes they do and there is no reason for you not to use them. I just find it easier in the long run, as I can manipulate the mouse state.
So then when the mouse first goes down, record the coordinates, then when the mouse moves, draw a line from the last coordinate to the new one. When the mouse button goes up then do a quick check to see if it is just a point to draw and draw it if so, else do nothing.
Here is an example. The mouse code is at the bottom with mouse.buttonRaw being a bit field of the current mouse button state where first bit is left, second is middle, and third is right.
var mouse;
document.body.innerHTML = "Use mouse to draw on the this snippet.";
var demo = function(){
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
if(mouse !== undefined){ // if the mouse exists
mouse.removeMouse(); // remove previous events
}
var canvasMouseCallBack = undefined; // if needed
// my mouse handler has more functionality than needed but to lazy to clean it ATM
mouse = (function(){
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
interfaceId : 0,
buttonLastRaw : 0,
buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
startMouse:undefined,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX;
m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true;
} else if (t === "mousewheel") { m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") { m.w = -e.detail;}
// call mouse callback if set
if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
e.preventDefault();
}
// function to add events to element
function startMouse(element){
if(element === undefined){
element = document;
}
mouse.element = element;
mouse.mouseEvents.forEach(
function(n){
element.addEventListener(n, mouseMove);
}
);
element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
}
// function to remove events
mouse.removeMouse = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(
function(n){
mouse.element.removeEventListener(n, mouseMove);
}
);
canvasMouseCallBack = undefined;
}
}
mouse.mouseStart = startMouse;
return mouse;
})();
// if there is a canvas add the mouse event else add to document
if(typeof canvas !== "undefined"){
mouse.mouseStart(canvas);
}else{
mouse.mouseStart();
}
// for the previouse mouse state
var lastMouseButton = 0;
var x,y,xx,yy; //for saving line segments drawn
// set up drawing
ctx.fillStyle = "red";
ctx.strokeStyle = "red";
ctx.lineWidth = 10;
ctx.lineJoin = "round";
ctx.lineCap = "round";
// set the mouse callback function. It is called for every mouse event
canvasMouseCallBack = function(mouse){
// is the first (left) button down and the last button state up?
if((mouse.buttonRaw & 1) === 1 && (lastMouseButton & 1) === 0){
x = mouse.x; // save the mouse coordinates
y = mouse.y;
}else
// is both the mouse button down and the last one down
if((mouse.buttonRaw & 1) === 1 && (lastMouseButton & 1) === 1){
xx = x; // yes move the last coordinate to the
yy = y; // start of the line segment
x = mouse.x; // get the mouse coords for the end of the line seg
y = mouse.y;
ctx.beginPath(); // draw the line segment
ctx.moveTo(x,y);
ctx.lineTo(xx,yy);
ctx.stroke();
}else
// has the mouse just been released
if( (mouse.buttonRaw & 1) === 0 && (lastMouseButton & 1) === 1){
if(xx === undefined){ // if xx is undefined then no line segment
// has been drawn so need to ad just a point
ctx.beginPath(); // draw a point at the last mouse point
ctx.arc(x,y,5,0,Math.PI*2);
ctx.fill();
}
xx = undefined; // clear the line segment start
}
// save the last mouse start
lastMouseButton = mouse.buttonRaw;
}
}
// resize demo to fit window if needed
window.addEventListener("resize",demo);
// start the demo
demo();
You may also be interested in this answer that deals with drawing as well but demonstrates how to smooth a line being draw as the mouse input can be very dirty.
Smooth a line
I want to create a Canvas in which there will be two areas (Left and right), Left panel will contain some shapes which will be draggable(static as well) and on the right side I would be able to drop them, but I am facing following problem,
I am not able to make the shapes which i draw on the left side, draggable, because there is no id associated with them.
I do not know how to make some particular area droppable.
Here is code to visualize what I am trying to achieve-
<body>
<canvas id="myCanvas" width="800" height="600" style="border:1px solid #000000;">
</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.moveTo(250,0);
ctx.lineTo(250,600);
ctx.stroke();
ctx.fillStyle = "#FF0000";
ctx.fillRect(50,50,160,25);
ctx.fillStyle = "#0000FF";
ctx.font = "15px";
ctx.strokeText("Draggable Elements here",57,67);
ctx.fillStyle = "#FF0000";
ctx.fillRect(500,50,130,25);
ctx.font = "15px";
ctx.strokeText("Droppable area Here",510,67);
</script>
</body>
Here is the JS fiddle for the same -
http://jsfiddle.net/akki166786/4tfyy4o5/
so if anybody can shed some light on how can I achieve this, it will be a great help.
Thanks in Advance
Drag and drop in specifik area
UPDATE: Copy of box remains at original position while it's being moved.
First you need to be able to detect your rectangles. You do this by making then into objects in your code:
function box(x,y,w,h,rgb) {
this.x = x,
this.y = y;
this.xS = x; //saving x
this.yS = y; //saving y
this.w = w;
this.h = h;
this.rgb = rgb;
//to determine if the box is being draged
this.draging = false;
}
No you need to add an event listener to determine if someone is clicking, you also need to determine if the person clicked in one of your boxes.
c.addEventListener("mousedown",down);
c.addEventListener("mousemove",move);
c.addEventListener("mouseup",up);
So events have been made to detect when the mouse button is pressed down, released back up and if the mouse moves within the canvas. To these events we have functions, down(), move() and up(), ready to be executed.
All functions will be visible in the example below.
When we're happily draging our boxes and releasing our mouse button, we need to check if the box was dropped in the dropable area. We do this in the up()-function. If the drop was OK, the box can stay, otherwise we send it back to where it came from.
Working example
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = 600;
c.height = 300;
//My mouse coordinates
var x,y;
c.addEventListener("mousedown",down);
c.addEventListener("mousemove",move);
c.addEventListener("mouseup",up);
//I'll save my boxes in this array
var myBoxes = new Array();
//This function describes what a box is.
//Each created box gets its own values
function box(x,y,w,h,rgb) {
this.x = x,
this.y = y;
this.xS = x; //saving x
this.yS = y; //saving y
this.w = w;
this.h = h;
this.rgb = rgb;
//to determine if the box is being draged
this.draging = false;
}
//Let's make some boxes!!
myBoxes[0] = new box(10,10,50,100,"green");
myBoxes[1] = new box(80,50,100,75,"blue");
myBoxes[2] = new box(40,150,20,70,"yellow");
//here we draw everything
function draw() {
ctx.clearRect(0,0,c.width,c.height);
//Dropable area
ctx.fillStyle="red";
ctx.fillRect(c.width/2,0,c.width,c.height);
//Boxes!
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
//NEW CODE FOR UPDATE
if (b.draging) { //box on the move
//Also draw it on the original spot
ctx.fillStyle="grey"; //I chose a different color to make it appear more as a shadow of the box that's being moved.
ctx.fillRect(b.xS,b.yS,b.w,b.h);
ctx.strokeRect(b.xS,b.yS,b.w,b.h);
}
//End of new code for update
ctx.fillStyle=b.rgb;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeRect(b.x,b.y,b.w,b.h);
}
//Let's keep re-drawing this
requestAnimationFrame(draw);
}
function down(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (x>b.x && x<b.x+b.w && y>b.y && y<b.y+b.h) {
b.draging = true;
}
}
}
function move(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging) {
b.x = x;
b.y = y;
}
}
}
function up(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging) {
//Let's see if the rectangle is inside the dropable area
if (b.x>c.width/2) {
//Yes is it!
b.x = x;
b.y = y;
b.draging = false;
}
else {
//No it's not, sending it back to its ordiginal spot
b.x = b.xS;
b.y = b.yS;
b.draging = false;
}
}
}
}
draw();
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
You're using just one canvas, maybe it would be better if you use two separate canvas, one for each element you want to handle on page. so you'll have one element ID for each one.
plus. if your drawing is simple, consider using a div for it instead a canvas
Once drawn to the canvas, shapes(or lines, images, everything) are no longer accessible.
What you will need to do is store each shape in an object in your code. For example:
var rectangle = {
width: 100,
height: 100,
x: 50,
y: 50
}
Then when you drag rectangle, you will need to update it's x and y properties on mouseup (or while it's being dragged if you want a drag preview).
i have been having trouble with reading a mouse position on a canvas. The code is working (semi) correctly as it reads the position when clicking he canvas in IE but only on one frame, in chrome it is just displaying the value as 0.
Here is the full code:
<script>
var blip = new Audio("blip.mp3");
blip.load();
var levelUp = new Audio("levelUp.mp3");
levelUp.load();
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
context.font = '18pt Calibri';
context.fillStyle = 'white';
//load and draw background image
var bgReady = false;
var background = new Image();
background.src = 'images/background.jpg';
background.onload = function(){
bgReady = true;
}
var startMessage = 'Click the canvas to start';
//load plane image
var planeReady = false;
var planeImage = new Image();
planeImage.src = 'images/plane.png';
planeImage.onload = function() {
planeReady = true;
}
//load missile image
var missileReady = false;
var missileImage = new Image();
missileImage.src = 'images/missile-flipped.gif';
missileImage.onload = function() {
missileReady = true;
}
//initialise lives and score
var score = 0;
var lives = 3;
var missilesLaunched = 0;
var missileSpeed = 5;
var level = 1;
var missileX = 960;
var missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
function getMousePos(canvas, event) {
return {
x: input.x - rect.left,
y: input.y - rect.top
};
}
function update_images(event) {
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
planeImage.y = pos.y;
missileX = missileX - missileSpeed;
if (missileX < - 70) {
missilesLaunched++;
missileX = 960;
missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
blip.play();
score = missilesLaunched;
if (score % 5 == 0) {
missileSpeed = missileSpeed + 2;
level++;
levelUp.play();
}
}
}
function reload_images() {
if (bgReady = true) {
context.drawImage(background, 0, 0);
}
if (planeReady = true) {
context.drawImage(planeImage, 10, planeImage.y);
}
if (missileReady = true) {
context.drawImage(missileImage, missileX, missileY);
}
context.fillText('Lives: ' + lives, 200, 30);
context.fillText('Score: ' + score, 650, 30);
context.fillText('Level: ' + missileSpeed, 420, 30);
context.fillText('Position: ' + missileImage.y, 420, 70);
}
function main(event) {
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
update_images(event);
reload_images();
if (lives > 0) {
window.requestAnimationFrame(main);
}
else {
}
}
function start() {
context.drawImage(background, 0, 0);
context.fillText('Click the canvas to start', 350, 250);
function startMain(event) {
game.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("mousedown", startMain);
}
start();
</script>
Joe, you should actually be capturing the mouse position every time you click...
...but you're actually also starting a new game (without stopping the old one), every time you click, too.
First problem: starting game engine several times to draw on the same instance of the canvas
Solution:
In your start function, you need to remove the mousedown event listener, after you've triggered it.
function start () {
// ... other setup
function startMain (event) {
canvas.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("click", startMain);
}
Now it will only listen for the first click, before starting, and will only start once.
Second Problem: mouse doesn't update as expected
Solution: two issues here...
...first, you are passing event into main on first call...
...after that, you're passing main into requestAnimationFrame.
requestAnimationFrame won't call it with an event, it will call it with the number of microseconds (or ms or some other unit as a fractional precision of ms) since the page was loaded.
So the first time you got main({ type: "mousedown", ... });.
The next time you get main(4378.002358007);
So lets refactor the startMain we had above, so that main never ever collects an event, just a time.
function startMain ( ) {
canvas.removeEventListener("click", startMain);
requestAnimationFrame(main);
}
The next problem is that even if you were getting just events, you're only ever capturing a click event (which as we mentioned earlier, fires a new copy of the game logic).
Your solution is to separate the code which catches mouse events from the code which reads mouse position.
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
function getMousePos (rect, input) {
return {
x : input.x - rect.left,
y : input.y - rect.top
};
}
// currently in updateImages (should not be there, but... a different story)
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
You've got other problems, too...
You're calling getMousePos and passing in game at the moment. I don't see where game is defined in your JS, so either you're making game somewhere else (begging for bugs), or it's undefined, and your app blows up right there.
You should really be building this with your console / dev-tools open, in a hands-on fashion, and cleaning bugs in each section, as you go.