Javascript canvas arrow - arrowhead not pointing at line end - javascript

I am using this code to draw an arrow on a canvas:
var arrowCanvas = document.getElementById("arrowCanvas");
var ctx = arrowCanvas.getContext("2d");
drawArrow(ctx, 30, 10, 30, 100);
function drawArrow(ctx, fromx, fromy, tox, toy){
//variables to be used when creating the arrow
var headlen = 10;
ctx.strokeStyle = "#cc0000";
ctx.fillStyle = "#cc0000";
ctx.lineWidth = 10;
var angle = Math.atan2(toy-fromy,tox-fromx);
//starting path of the arrow from the start square to the end square and drawing the stroke
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.stroke();
//starting a new path from the head of the arrow to one of the sides of the point
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
//path from the side point of the arrow, to the other side point
ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));
//path from the side point back to the tip of the arrow, and then again to the opposite side point
ctx.lineTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
//draws the paths created above
ctx.stroke();
ctx.fill();
}
(Code is found in this answer: Draw arrow on canvas tag).
My problem with this code is that because of ctx.lineWidth the arrowHead points over the specified coordinates (tox, toy). In the provided example the arrowhead points onto the canvas y-position 110, due to lineWidth = 10px. I want it to point exactly on the tox, toy-coordinates, independent of the arrows angle.
My solution would be to reduce the initial length of the arrow by the amount of lineWidth, but I failed at considering the arrow angle for it.

A little trig is all that is needed. Bellow is a snippet that solves the 3 types of line joins. Bevel, Miter, and round.
The function drawArrow draw a line from x,y,xx,yy with options setting the various dimensions for the arrow head.
The function reads the ctx.lineWidth and ctx.lineJoin values to work out how to move the end point to ensure it does not pass the point.
For "round" joins the distance to move in is half the line width
For "miter" the distance is half line width divide the sin of half the arrowhead pointy angle
For "Bevel" the distance is cos of half the angle of the pointy end times the half line width
const PI = Math.PI;
const PI2 = PI * 2;
function drawArrow(ctx,x,y,xx,yy,options){
function getDef(name,def){return options[name] !== undefined ? options[name] : def;}
var w = getDef("width",5); // get settings
var hs = getDef("headSize",15); //
var hw = getDef("headWidth",15); //
var dx = xx-x;
var dy = yy-y;
var dir = Math.atan2(dy,dx);
var dist = Math.sqrt(dx*dx+dy*dy);
var lineWidth = Number(ctx.lineWidth)
var endMove = ctx.lineWidth/2; // assume round joins
if(ctx.lineJoin === "miter"){
endMove = Math.min(ctx.miterLimit,endMove / (hw / Math.sqrt(hs*hs+hw*hw)));
}else if(ctx.lineJoin === "bevel"){
endMove = endMove * Math.cos(Math.asin(hs / Math.sqrt(hs*hs+hw*hw)));
}
// move canvas coordinates so that the arrow starts at 0,0,
ctx.setTransform(1,0,0,1,x,y);
ctx.rotate(dir); // and is aligned to x
dist -= endMove; // shorten for line width
ctx.beginPath();
ctx.moveTo(0,-w);
ctx.lineTo(dist - hs,-w);
ctx.lineTo(dist - hs,-hw);
ctx.lineTo(dist ,0);
ctx.lineTo(dist - hs,hw);
ctx.lineTo(dist - hs,w);
ctx.lineTo(0,w);
ctx.stroke();
ctx.fill();
}
var arrows = [
{width : 5, headWidth : 10, headSize : 20, lineWidth : 5,line : "red", fill : "blue",join : "bevel", limit : 100},
{width : 10, headWidth : 20, headSize : 20, lineWidth : 5,line : "Orange", fill : "blue",join : "miter", limit : 5},
{width : 10, headWidth : 20, headSize : 20, lineWidth : 5,line : "Green", fill : "blue",join : "round", limit : 0},
]
var tempArrow = {width : 10, headWidth : 20, headSize : 20};
const numArrows = 3;
const mouseClear = 30;
// main update function
function display(){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.arc(mouse.x,mouse.y,mouseClear,0,PI2);
ctx.miterLimit = 1000;
ctx.stroke();
for(var i = 0; i < numArrows; i ++){
var x = cw + Math.cos((i/numArrows)*PI2) * cw *1.8;
var y = ch + Math.sin((i/numArrows)*PI2) * ch *1.8;
var dir = Math.atan2(y-mouse.y,x-mouse.x);
var xx = mouse.x + Math.cos(dir) * mouseClear;
var yy = mouse.y + Math.sin(dir) * mouseClear;
var scaleLine = (Math.sin(globalTime/1000)+1.1) * 2;
var style = arrows[i%arrows.length];
var arrowHead = (Math.sin(globalTime/770)+1.1) * 2;
var arrowSize = (Math.sin(globalTime/1370)+1.1) * 2;
ctx.lineWidth = style.lineWidth * scaleLine;
ctx.strokeStyle = style.line;
ctx.fillStyle = style.fill;
ctx.lineJoin = style.join;
tempArrow.headWidth = style.headSize * arrowHead;
tempArrow.headSize = style.headSize * arrowSize;
drawArrow(ctx,x,y,xx,yy,tempArrow);
}
}
//==============================================================================
// From here down part of answer just boiler room stuff
// can be ignored.
/** SimpleFullCanvasMouse.js begin **/
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
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;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
function update(timer) { // Main update loop
if(ctx === undefined){
return;
}
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();

Related

Bullets follows mouse position

Im making simple arena shooter in JS and HTML.
right now I wanted to add shooting mechanic,I thought i would use code from my enemy class with bits of tweaking
here:
//differences between objects
let dirx = mouse.x - player.x,
diry = mouse.y - player.y;
//normalizing
let dist = Math.sqrt(dirx * dirx + diry * diry);
dirx = dirx / dist;
diry = diry / dist;
this.x += dirx * 5;
this.y += diry * 5;
Since my player is moving the player.x& player.y coords in dirX & dirY will change, meaning bullet wont go to original coords.
So I would be pleased for some help.
You could use vectors to determine the trajectory of the bullets.
In the example below, when I fire a bullet:
Initialize the position at the source of the bullet
The speed is determined by:
Subtracting the mouse position from the source
Normalizing the distance
Applying a speed constant
class Bullet {
constructor(source, target, created) {
this.position = source.clone();
this.speed = this.position.clone()
.subtract(Victor.fromObject(target))
.normalize()
.multiply(new Victor(-2, -2));
this.size = 3;
this.timeLeft = created + 500;
}
update() {
this.position = this.position.add(this.speed);
}
draw(ctx) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI);
ctx.fill();
}
}
Note: I also added a timeLeft variable to remove the bullet from the game after a set period of time e.g. 500ms.
Demo
Here is an example using the vector logic explained above.
Controls
W (Up) - Move up
A (Left) - Move left
S (Down) - Move down
D (Right) - Move right
Mouse 1 - Hold to fire
const VK_W = 87;
const VK_A = 65;
const VK_S = 83;
const VK_D = 68;
const VK_UP = 38;
const VK_DOWN = 40;
const VK_LEFT = 37;
const VK_RIGHT = 39;
const canvas = document.querySelector('.game'),
stateBtn = document.querySelector('.state-btn');
let game;
const main = () => {
stateBtn.addEventListener('click', changeState);
const ctx = canvas.getContext('2d');
game = new Game(ctx, { width: 400, height: 300 });
game.run();
};
const changeState = (e) => {
const btn = e.currentTarget, running = btn.dataset.running === 'true';
if (running) {
game.pause();
} else {
game.run();
}
btn.textContent = running ? 'Pause' : 'Resume';
btn.dataset.running = !running;
};
class Game {
constructor(ctx, options) {
const { width, height } = options;
this.lastRender = 0;
this.ctx = ctx;
this.state = { keySet: new Set() };
this.bullets = [];
Object.assign(ctx.canvas, { width, height });
const origin = new Victor(width / 2, height / 2);
this.player = new Player({
name: 'Bob',
position: origin
});
ctx.canvas.addEventListener('mousemove', (e) => this.followMouse(e));
ctx.canvas.addEventListener('mousedown', (e) => this.mouseDown(e));
ctx.canvas.addEventListener('mouseup', (e) => this.mouseUp(e));
document.addEventListener('keydown', (e) => this.addKey(e), false);
document.addEventListener('keyup', (e) => this.removeKey(e), false);
}
followMouse(e) {
this.state.mousePos = getMousePos(e.currentTarget, e);
}
mouseDown(e) {
this.state.mouseDown = true;
}
mouseUp(e) {
this.state.mouseDown = false;
}
addKey(e) {
const key = e.which || e.keyCode || 0;
this.state.keySet.add(key);
}
removeKey(e) {
const key = e.which || e.keyCode || 0;
this.state.keySet.delete(key);
}
update(progress) {
const ks = this.state.keySet;
const x = (ks.has(VK_D) || ks.has(VK_RIGHT))
? 1 : (ks.has(VK_A) || ks.has(VK_LEFT)) ? -1 : 0;
const y = (ks.has(VK_S) || ks.has(VK_DOWN))
? 1 : (ks.has(VK_W) || ks.has(VK_UP)) ? -1 : 0;
this.player.position.add(new Victor(x, y));
this.bullets.forEach((bullet, index) => {
if (this.lastRender > bullet.timeLeft) {
this.bullets.splice(index, 1);
}
bullet.update(this.lastRender);
});
if (this.state.mousePos && this.state.mouseDown) {
this.bullets.push(new Bullet(this.player.position,
this.state.mousePos, this.lastRender));
}
}
draw() {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.player.draw(this.ctx);
this.bullets.forEach(bullet => bullet.draw(this.ctx));
}
run(timestamp) {
const progress = timestamp - this.lastRender;
this.update(progress);
this.draw();
this.lastRender = timestamp;
this.req = window.requestAnimationFrame((t) => this.run(t));
}
pause() {
const { width, height } = this.ctx.canvas;
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
this.ctx.fillRect(0, 0, width, height);
this.ctx.font = '32px Arial';
this.ctx.fillStyle = '#FFF';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText('Paused', width / 2, height / 2);
cancelAnimationFrame(this.req);
this.req = null;
}
isRunning() {
return this.req !== null;
}
}
class Player {
constructor(options) {
const opts = { ...Player.defaultOptions, ...options };
this.name = opts.name;
this.position = opts.position;
this.size = opts.size;
}
draw(ctx) {
ctx.fillStyle = '#00FF00';
ctx.fillRect(this.position.x - this.size / 2,
this.position.y - this.size / 2, this.size, this.size);
}
};
Player.defaultOptions = {
size: 10,
position: new Victor(0, 0)
};
class Bullet {
constructor(source, target, created) {
this.position = source.clone();
this.speed = this.position.clone()
.subtract(Victor.fromObject(target))
.normalize()
.multiply(new Victor(-2, -2));
this.size = 3;
this.timeLeft = created + 500;
}
update() {
this.position = this.position.add(this.speed);
}
draw(ctx) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI);
ctx.fill();
}
}
const getMousePos = (canvas, event) =>
(({ top, left }) => ({
x: event.clientX - left,
y: event.clientY - top
}))(canvas.getBoundingClientRect());
main();
.game {
background: #111;
}
.as-console-wrapper { max-height: 4em !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/victor/1.1.0/victor.min.js"></script>
<canvas class="game"></canvas>
<div class="controls">
<button class="state-btn" data-running="true">Pause</button>
</div>

Zoom/scale from different point of image in canvas

I have made a canvas in html5. I'm using it to show parts of a bigger image that you can move and I want to make it possible to zoom both in and out. But I do not know how to make the image scale from a certain point. When I increase the size of the image the part which is shown by the canvas is moved, I want the part in the center of the canvas to be the focus point when the scaling is complete, but no matter how I try it gets distorted some how. It seems like depending on which part of the image is shown, when the scaling happens that part of the picture is going to get moved to different coordinates. I do not know what type of algorithm I would have to use so calculate the movement of the image.
This is a link to a visual example of the movement i mean, https://imgur.com/a/aZiVM, the two images are scaled the same but depending on which part of the image that is visible in the canvas, the amount the image needs to be moved tor the zoom differs.
This is my code, but it isn't really working that well.
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js"></script>
<style>
body {
padding: 0px;
margin: 0px;
}
</style>
</head>
<body>
<div id="map" style="position:absolute;top:10px;left:50px;">
<canvas id="canvas" width="800" height="600" style="float:left;border:1px solid #000000;">Your browser doesn't support canvas</canvas>
<div id="floorDown" onMouseDown="zoomIn()" style="width:200px;height:50px;float:left;">Zoom in</div><br>
<div id="floorDown" onMouseDown="zoomOut()" style="width:200px;height:50px;float:left;">Zoom out</div>
</div>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var startX;
var startY;
var isDown=false;
//start position
var imageX=0;
var imageY=0;
var imageWidth,imageHeight,imageRight,imageBottom;
var draggingImage=false;
var startX;
var startY;
var img=new Image();
img.onload=function(){
imageWidth=img.width;
imageHeight=img.height;
draw();
}
img.src='http://orig00.deviantart.net/35cb/f/2013/030/f/0/tripolar_by_zy0rg-d5t9tqh.png';
function draw(){
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
//Disable anti-aliasing
ctx.imageSmoothingEnabled=false;
// draw the image
ctx.drawImage(img,0,0,img.width,img.height,imageX,imageY,imageWidth,imageHeight);
}
function handleMouseDown(e){
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
draggingImage= true;
}
function handleMouseUp(e){
draggingImage=false;
draw();
}
function handleMouseOut(e){
handleMouseUp(e);
}
function handleMouseMove(e){
if(draggingImage){
imageClick=false;
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// move the image by the amount of the latest drag
var dx=mouseX-startX;
var dy=mouseY-startY;
imageX+=dx;
imageY+=dy;
// reset the startXY for next time
startX=mouseX;
startY=mouseY;
// redraw the image with border
draw();
}
}
// TEST zoom in/out functions
function zoomIn() {
imageX=imageX*2;
imageY=imageY*2;
imageWidth=imageWidth*2;
imageHeight=imageHeight*2;
draw();
}
function zoomOut() {
imageX=imageX/2;
imageY=imageY/2;
imageWidth=imageWidth/2;
imageHeight=imageHeight/2;
draw();
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
</script>
</body>
Given the origin (pos) and scale to zoom at a point
var pos = {x : 0, y : 0};
var scale = 1;
function zoomAt(x,y,_scale)
scale *= _scale
pos.x = x - (x - pos.x) * scale;
pos.y = y - (y - pos.y) * scale;
}
You can then create the transform with
ctx.setTransform(scale, 0, 0, scale, pos.x, pos.y);
So to zoom at the center of screen
zoomAt(canvas.width / 2, canvas.height / 2, 1.1); // zoom in
zoomAt(canvas.width / 2, canvas.height / 2, 1 / 1.1); // zoom out
Put all together
// the following globals are available
// w, h, cw, ch, width height centerWidth centerHeight of canvas
// canvas, ctx, mouse, globalTime
const image = new Image;
image.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/KTZ_2TE10U_Aynabulak.jpg/800px-KTZ_2TE10U_Aynabulak.jpg";
const font = {
font : "28px Arial",
textAlign : "center",
textBaseline : "middle",
}
function setStyle(ctx, style){
Object.keys(style).forEach(key => ctx[key] = style[key]);
}
// Handle all key input
const keys = { // key input object
ArrowLeft : false, // only add key names you want to listen to
ArrowRight : false,
keyEvent (event) {
if (keys[event.code] !== undefined) { // are we interested in this key
keys[event.code] = event.type === "keydown";
}
}
}
// add key listeners
document.addEventListener("keydown", keys.keyEvent);
document.addEventListener("keyup", keys.keyEvent);
const view = (()=>{
const matrix = [1,0,0,1,0,0]; // current view transform
const invMatrix = [1,0,0,1,0,0]; // current inverse view transform
var m = matrix; // alias
var im = invMatrix; // alias
var rotate = 0; // current x axis direction in radians
var scale = 1; // current scale
const pos = { // current position of origin
x : 0,
y : 0,
}
var dirty = true;
return {
apply(ctx){
if(dirty){ this.update() }
var m = matrix;
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
},
update(){ // call to update transforms
var xdx = Math.cos(rotate) * scale;
var xdy = Math.sin(rotate) * scale;
m[0] = xdx;
m[1] = xdy;
m[2] = -xdy;
m[3] = xdx;
m[4] = pos.x;
m[5] = pos.y;
// calculate the inverse transformation
cross = m[0] * m[3] - m[1] * m[2];
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
dirty = false;
},
toWorld(x,y,point = {}){ // convert screen to world coords
var xx, yy;
if(dirty){ this.update() }
xx = x - matrix[4];
yy = y - matrix[5];
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
},
toScreen(x,y,point = {}){ // convert world coords to coords
if(dirty){ this.update() }
point.x = x * m[0] + y * m[2] + m[4];
point.y = x * m[1] + y * m[3] + m[5];
return point;
},
movePos(x,y){
pos.x += x;
pos.y += y;
dirty = true;
},
setPos(x,y){
pos.x = x;
pos.y = y;
dirty = true;
},
setScale(sc){
scale = sc;
dirty = true;
},
scaleScale(sc){
scale *= sc;
dirty = true;
},
scaleAt(x,y,sc){
if(dirty){ this.update() }
scale *= sc;
pos.x = x - (x - pos.x) * sc;
pos.y = y - (y - pos.y) * sc;
dirty = true;
}
};
})();
function onResize(){
setStyle(ctx,font);
}
const drag = {
dragging : false,
lastX : 0,
lastY : 0,
update(){
var dx,dy;
if(mouse.w){
if(mouse.w < 0){
mouse.w += 10;
view.scaleAt(mouse.x,mouse.y,1/1.02);
if(mouse.w > 0){
mouse.w = 0;
}
} else if(mouse.w > 0){
mouse.w -= 10;
view.scaleAt(mouse.x,mouse.y,1.02);
if(mouse.w < 0){
mouse.w = 0;
}
}
}
if(mouse.buttonRaw){
if(!this.dragging){
this.dragging = true;
this.lastX = mouse.x;
this.lastY = mouse.y;
}else{
if(mouse.buttonRaw & 1){
dx = mouse.x-this.lastX;
dy = mouse.y-this.lastY;
this.lastX = mouse.x;
this.lastY = mouse.y;
view.movePos(dx,dy);
}
}
}else{
if(this.dragging){
this.dragging = false;
}
}
}
}
function display() { // call once per frame
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
if(keys.ArrowLeft ){ mouse.w += 10 }
if(keys.ArrowRight){ mouse.w -= 10 }
drag.update();
if(image.complete){
view.apply(ctx);
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillText("Click drag to pan. Wheel to zoom or left/right arrow.",cw,20)
}else{
ctx.fillText("Loading Image...",cw,ch)
}
}
/******************************************************************************
The code from here down is generic full page mouse and canvas boiler plate
code. As I do many examples which all require the same mouse and canvas
functionality I have created this code to keep a consistent interface. The
Code may or may not be part of the answer.
This code may or may not have ES6 only sections so will require a transpiler
such as babel.js to run on legacy browsers.
*****************************************************************************/
// V2.0 ES6 version for Stackoverflow and Groover QuickRun
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0;
// You can declare onResize (Note the capital R) as a callback that is also
// called once at start up. Warning on first call canvas may not be at full
// size.
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var resizeTimeoutHandle;
var firstRun = true;
function createCanvas () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 10;
document.body.appendChild(c);
return c;
}
function resizeCanvas () {
if (canvas === undefined) { canvas = createCanvas() }
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals() }
if (typeof onResize === "function") {
clearTimeout(resizeTimeoutHandle);
if (firstRun) { onResize() }
else { resizeTimeoutHandle = setTimeout(onResize, RESIZE_DEBOUNCE_TIME) }
firstRun = false;
}
}
function setGlobals () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) { e.preventDefault() }
var m; // alias for mouse
var mouse = {
x : 0, y : 0, w : 0, // mouse position and wheel
alt : false, shift : false, ctrl : false, // mouse modifiers
buttonRaw : 0,
over : false, // true if mouse over the element
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
active : false,
bounds : null,
eventNames : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
else if (t === "mouseout") { m.over = false }
else if (t === "mouseover") { m.over = true }
else if (t === "mousewheel") {
m.w = e.wheelDelta
e.preventDefault();
}
else if (t === "DOMMouseScroll") {
m.w = -e.detail
e.preventDefault();
}
},
start(element) {
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
document.addEventListener("contextmenu", preventDefault, false);
m.active = true;
},
}
m = mouse;
return mouse;
})();
function update(timer) { // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update)
}
setTimeout(function(){
canvas = createCanvas();
mouse.start(canvas, true);
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
#imageCC {
font-family : arial;
font-size : 10px;
position : absolute;
z-index : 100;
bottom : 3px;
right : 10px;
background : rgba(255,255,255,0.7);
}
<div id=imageCC>Image rights.
Kabelleger / David Gubler (http://www.bahnbilder.ch), KTZ 2TE10U Aynabulak, CC BY-SA 3.0
</div>

Incorrectly drawing line to edge of ellipse given angle

Sorry for the confusing title, I don't know how to succinctly describe my question.
I'm drawing an ellipse on a canvas element using javascript and I'm trying to figure out how to detect if the mouse is clicked inside of the ellipse or not. The way I'm trying to do this is by comparing the distance from the center of the ellipse to the mouse to the radius of the ellipse at the same angle as the mouse click. Here's a terrible picture representing what I just said if it's still confusing:
Obviously this isn't working, otherwise I wouldn't be asking this, so below is a picture of the computed radius line (in red) and the mouse line (in blue). In this picture, the mouse has been clicked at a 45° angle to the center of the ellipse and I've calculated that the radius line is being drawn at about a 34.99° angle.
And below is the calculation code:
//This would be the blue line in the picture above
var mouseToCenterDistance = distanceTo(centerX, centerY, mouseX, mouseY);
var angle = Math.acos((mouseX - centerX) / mouseToCenterDistance);
var radiusPointX = (radiusX * Math.cos(angle)) + centerX;
var radiusPointY = (radiusY * Math.sin(-angle)) + centerY;
//This would be the red line in the picture above
var radius = distanceTo(centerX, centerY, radiusPointX, radiusPointY);
var clickedInside = mouseToCenterDistance <= radius;
I'm really not sure why this isn't working, I've been staring at this math forever and it seems correct. Is it correct and there's something about drawing on the canvas that's making it not work? Please help!
Ellipse line intercept
Finding the intercept includes solving if the point is inside.
If it is the ellipse draw via the 2D context the solution is as follows
// defines the ellipse
var cx = 100; // center
var cy = 100;
var r1 = 20; // radius 1
var r2 = 100; // radius 2
var ang = 1; // angle in radians
// rendered with
ctx.beginPath();
ctx.ellipse(cx,cy,r1,r2,ang,0,Math.PI * 2,true)
ctx.stroke()
To find the point on the ellipse that intersects the line from the center to x,y. To solve I normalise the ellipse so that it is a circle (well the line is moved so that the ellipse is a circle in its coordinate space).
var x = 200;
var y = 200;
var ratio = r1 / r2; // need the ratio between the two radius
// get the vector from the ellipse center to end of line
var dx = x - cx;
var dy = y - cy;
// get the vector that will normalise the ellipse rotation
var vx = Math.cos(-ang);
var vy = Math.sin(-ang);
// use that vector to rotate the line
var ddx = dx * vx - dy * vy;
var ddy = (dx * vy + dy * vx) * ratio; // lengthen or shorten dy
// get the angle to the line in normalise circle space.
var c = Math.atan2(ddy,ddx);
// get the vector along the ellipse x axis
var eAx = Math.cos(ang);
var eAy = Math.sin(ang);
// get the intercept of the line and the normalised ellipse
var nx = Math.cos(c) * r1;
var ny = Math.sin(c) * r2;
// rotate the intercept to the ellipse space
var ix = nx * eAx - ny * eAy
var iy = nx * eAy + ny * eAx
// cx,cy to ix ,iy is from the center to the ellipse circumference
The procedure can be optimised but for now that will solve the problem as presented.
Is point inside
Then to determine if the point is inside just compare the distances of the mouse and the intercept point.
var x = 200; // point to test
var y = 200;
// get the vector from the ellipse center to point to test
var dx = x - cx;
var dy = y - cy;
// get the vector that will normalise the ellipse rotation
var vx = Math.cos(ang);
var vy = Math.sin(ang);
// use that vector to rotate the line
var ddx = dx * vx + dy * vy;
var ddy = -dx * vy + dy * vx;
if( 1 >= (ddx * ddx) / (r1 * r1) + (ddy * ddy) / (r2 * r2)){
// point on circumference or inside ellipse
}
Example use of method.
function path(path){
ctx.beginPath();
var i = 0;
ctx.moveTo(path[i][0],path[i++][1]);
while(i < path.length){
ctx.lineTo(path[i][0],path[i++][1]);
}
if(close){
ctx.closePath();
}
ctx.stroke();
}
function strokeCircle(x,y,r){
ctx.beginPath();
ctx.moveTo(x + r,y);
ctx.arc(x,y,r,0,Math.PI * 2);
ctx.stroke();
}
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
var cx = w/2;
var cy = h/2;
var r1 = Math.abs(Math.sin(globalTime/ 4000) * w / 4);
var r2 = Math.abs(Math.sin(globalTime/ 4300) * h / 4);
var ang = globalTime / 1500;
// find the intercept from ellipse center to mouse on the ellipse
var ratio = r1 / r2
var dx = mouse.x - cx;
var dy = mouse.y - cy;
var dist = Math.hypot(dx,dy);
var ex = Math.cos(-ang);
var ey = Math.sin(-ang);
var c = Math.atan2((dx * ey + dy * ex) * ratio, dx * ex - dy * ey);
var nx = Math.cos(c) * r1;
var ny = Math.sin(c) * r2;
var ix = nx * ex + ny * ey;
var iy = -nx * ey + ny * ex;
var dist = Math.hypot(dx,dy);
var dist2Inter = Math.hypot(ix,iy);
ctx.strokeStyle = "Blue";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.ellipse(cx,cy,r1,r2,ang,0,Math.PI * 2,true)
ctx.stroke();
if(dist2Inter > dist){
ctx.fillStyle = "#7F7";
ctx.globalAlpha = 0.5;
ctx.fill();
ctx.globalAlpha = 1;
}
// Display the intercept
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
path([[cx,cy],[mouse.x,mouse.y]])
ctx.strokeStyle = "red";
ctx.lineWidth = 5;
path([[cx,cy],[cx + ix,cy+iy]])
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
strokeCircle(cx + ix, cy + iy, 6)
ctx.fillStyle = "white";
ctx.fill();
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
strokeCircle(cx, cy, 6)
ctx.fillStyle = "white";
ctx.fill();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
strokeCircle(mouse.x, mouse.y, 4)
ctx.fillStyle = "white";
ctx.fill();
}
/** SimpleFullCanvasMouse.js begin **/
//==============================================================================
// Boilerplate code from here down and not related to the answer
//==============================================================================
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
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;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
if ((m.buttonRaw & 2) && m.crashRecover !== null) {
if (typeof m.crashRecover === "function") {
setTimeout(m.crashRecover, 0);
}
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
// Clean up. Used where the IDE is on the same page.
var done = function () {
window.removeEventListener("resize", resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = undefined;
}
function update(timer) { // Main update loop
if(ctx === undefined){ return; }
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
//mouse.crashRecover = done;
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
If you have an ellipse of the form (x-x0)2/a2 + (y-y0)2/b2 = 1, then a point (x, y) is inside the ellipse if and only if (x-x0)2/a2 + (y-y0)2/b2 < 1. You can just test that inequality to see if the mouse is inside the ellipse.
To be able to draw a line to the edge of the ellipse: get the theta of the mouse with atan2 (don't use acos, you'll get incorrect results in quadrants III & IV), use the polar equation of the ellipse to solve for r, then convert back to rectangular coordinates and draw.

How to draw a smooth continuous line with mouse using html canvas and javascript

I'm attempting to create a simple draw/paint programme using html5 canvas and plain javascript. I've got it working ok, but when drawing and moving the mouse too fast the line disconnects and I just end up with a line of dots - how can I make this a smooth continuous line?
Advice would be much appreciated! I'm quite new to JS so code examples would be really useful, thanks in advance.
Current JS is:
var canvas, ctx
var mouseX, mouseY, mouseDown = 0
function draw(ctx,x,y,size) {
ctx.fillStyle = "#000000"
ctx.beginPath()
ctx.arc(x, y, size, 0, Math.PI*2, true)
ctx.closePath()
ctx.fill()
}
function clearCanvas(canvas,ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
function onMouseDown() {
mouseDown = 1
draw(ctx, mouseX, mouseY, 2)
}
function onMouseUp() {
mouseDown = 0
}
function onMouseMove(e) {
getMousePos(e)
if (mouseDown == 1) {
draw(ctx, mouseX, mouseY, 2)
}
}
function getMousePos(e) {
if (!e)
var e = event
if (e.offsetX) {
mouseX = e.offsetX
mouseY = e.offsetY
}
else if (e.layerX) {
mouseX = e.layerX
mouseY = e.layerY
}
}
function init() {
canvas = document.getElementById('sketchpad')
ctx = canvas.getContext('2d')
canvas.addEventListener('mousedown', onMouseDown, false)
canvas.addEventListener('mousemove', onMouseMove, false)
window.addEventListener('mouseup', onMouseUp, false)
}
init();
<canvas id="sketchpad" width="500" height="500"></canvas>
Drawing a smooth curve with the mouse.
Sadly it is not that easy if you wish to stay true to the artists intended line.
It involves recording the whole mouse stroke. When the stroke is complete, reduce the number of points to the detail limit (set by artist) then apply a bezier smoothing function on the remaining points.
It can be done as the stroke is drawn but for some devices this can become too much if the line becomes very long. As the line detail reduction looks at all points when showing the smoothed line live some people dont like the way it slightly changes as the line gets longer.
Demo
The code below demonstrates a solution I have found useful.
Use the left button to draw with smoothing done one button release.
Use the right button to draw with live smoothing (blue line).
Middle mouse button click to clear.
Use the two sliders at the top to set the amount of smoothing, and the amount of detail. Left click to drag out a stroke, the raw line is shown. When the mouse is released the line is then simplified, smoothed, and added to the background image.
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
// mouse stuff
var mouse = {
x:0,
y:0,
buttonLastRaw:0, // user modified value
buttonRaw:0,
buttons:[1,2,4,6,5,3], // masks for setting and clearing button raw bits;
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
if(event.type === "mousedown"){ mouse.buttonRaw |= mouse.buttons[event.which-1];
}else if(event.type === "mouseup"){mouse.buttonRaw &= mouse.buttons[event.which+2];
}else if(event.type === "mouseout"){ mouse.buttonRaw = 0; mouse.over = false;
}else if(event.type === "mouseover"){ mouse.over = true; }
event.preventDefault();
}
canvas.addEventListener('mousemove',mouseMove);
canvas.addEventListener('mousedown',mouseMove);
canvas.addEventListener('mouseup' ,mouseMove);
canvas.addEventListener('mouseout' ,mouseMove);
canvas.addEventListener('mouseover' ,mouseMove);
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
// Line simplification based on
// the Ramer–Douglas–Peucker algorithm
// referance https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// points are and array of arrays consisting of [[x,y],[x,y],...,[x,y]]
// length is in pixels and is the square of the actual distance.
// returns array of points of the same form as the input argument points.
var simplifyLineRDP = function(points, length) {
var simplify = function(start, end) { // recursize simplifies points from start to end
var maxDist, index, i, xx , yy, dx, dy, ddx, ddy, p1, p2, p, t, dist, dist1;
p1 = points[start];
p2 = points[end];
xx = p1[0];
yy = p1[1];
ddx = p2[0] - xx;
ddy = p2[1] - yy;
dist1 = (ddx * ddx + ddy * ddy);
maxDist = length;
for (var i = start + 1; i < end; i++) {
p = points[i];
if (ddx !== 0 || ddy !== 0) {
t = ((p[0] - xx) * ddx + (p[1] - yy) * ddy) / dist1;
if (t > 1) {
dx = p[0] - p2[0];
dy = p[1] - p2[1];
} else
if (t > 0) {
dx = p[0] - (xx + ddx * t);
dy = p[1] - (yy + ddy * t);
} else {
dx = p[0] - xx;
dy = p[1] - yy;
}
}else{
dx = p[0] - xx;
dy = p[1] - yy;
}
dist = dx * dx + dy * dy
if (dist > maxDist) {
index = i;
maxDist = dist;
}
}
if (maxDist > length) { // continue simplification while maxDist > length
if (index - start > 1){
simplify(start, index);
}
newLine.push(points[index]);
if (end - index > 1){
simplify(index, end);
}
}
}
var end = points.length - 1;
var newLine = [points[0]];
simplify(0, end);
newLine.push(points[end]);
return newLine;
}
// This is my own smoothing method
// It creates a set of bezier control points either 2nd order or third order
// bezier curves.
// points: list of points
// cornerThres: when to smooth corners and represents the angle between to lines.
// When the angle is smaller than the cornerThres then smooth.
// match: if true then the control points will be balanced.
// Function will make a copy of the points
var smoothLine = function(points,cornerThres,match){ // adds bezier control points at points if lines have angle less than thres
var p1, p2, p3, dist1, dist2, x, y, endP, len, angle, i, newPoints, aLen, closed, bal, cont1, nx1, nx2, ny1, ny2, np;
function dot(x, y, xx, yy) { // get do product
// dist1,dist2,nx1,nx2,ny1,ny2 are the length and normals and used outside function
// normalise both vectors
dist1 = Math.sqrt(x * x + y * y); // get length
if (dist1 > 0) { // normalise
nx1 = x / dist1 ;
ny1 = y / dist1 ;
}else {
nx1 = 1; // need to have something so this will do as good as anything
ny1 = 0;
}
dist2 = Math.sqrt(xx * xx + yy * yy);
if (dist2 > 0) {
nx2 = xx / dist2;
ny2 = yy / dist2;
}else {
nx2 = 1;
ny2 = 0;
}
return Math.acos(nx1 * nx2 + ny1 * ny2 ); // dot product
}
newPoints = []; // array for new points
aLen = points.length;
if(aLen <= 2){ // nothing to if line too short
for(i = 0; i < aLen; i ++){ // ensure that the points are copied
newPoints.push([points[i][0],points[i][1]]);
}
return newPoints;
}
p1 = points[0];
endP =points[aLen-1];
i = 0; // start from second poitn if line not closed
closed = false;
len = Math.hypot(p1[0]- endP[0], p1[1]-endP[1]);
if(len < Math.SQRT2){ // end points are the same. Join them in coordinate space
endP = p1;
i = 0; // start from first point if line closed
p1 = points[aLen-2];
closed = true;
}
newPoints.push([points[i][0],points[i][1]])
for(; i < aLen-1; i++){
p2 = points[i];
p3 = points[i + 1];
angle = Math.abs(dot(p2[0] - p1[0], p2[1] - p1[1], p3[0] - p2[0], p3[1] - p2[1]));
if(dist1 !== 0){ // dist1 and dist2 come from dot function
if( angle < cornerThres*3.14){ // bend it if angle between lines is small
if(match){
dist1 = Math.min(dist1,dist2);
dist2 = dist1;
}
// use the two normalized vectors along the lines to create the tangent vector
x = (nx1 + nx2) / 2;
y = (ny1 + ny2) / 2;
len = Math.sqrt(x * x + y * y); // normalise the tangent
if(len === 0){
newPoints.push([p2[0],p2[1]]);
}else{
x /= len;
y /= len;
if(newPoints.length > 0){
var np = newPoints[newPoints.length-1];
np.push(p2[0]-x*dist1*0.25);
np.push(p2[1]-y*dist1*0.25);
}
newPoints.push([ // create the new point with the new bezier control points.
p2[0],
p2[1],
p2[0]+x*dist2*0.25,
p2[1]+y*dist2*0.25
]);
}
}else{
newPoints.push([p2[0],p2[1]]);
}
}
p1 = p2;
}
if(closed){ // if closed then copy first point to last.
p1 = [];
for(i = 0; i < newPoints[0].length; i++){
p1.push(newPoints[0][i]);
}
newPoints.push(p1);
}else{
newPoints.push([points[points.length-1][0],points[points.length-1][1]]);
}
return newPoints;
}
// creates a drawable image
var createImage = function(w,h){
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d");
return image;
}
// draws the smoothed line with bezier control points.
var drawSmoothedLine = function(line){
var i,p;
ctx.beginPath()
ctx.moveTo(line[0][0],line[0][1])
for(i = 0; i < line.length-1; i++){
p = line[i];
p1 = line[i+1]
if(p.length === 2){ // linear
ctx.lineTo(p[0],p[1])
}else
if(p.length === 4){ // bezier 2nd order
ctx.quadraticCurveTo(p[2],p[3],p1[0],p1[1]);
}else{ // bezier 3rd order
ctx.bezierCurveTo(p[2],p[3],p[4],p[5],p1[0],p1[1]);
}
}
if(p.length === 2){
ctx.lineTo(p1[0],p1[1])
}
ctx.stroke();
}
// smoothing settings
var liveSmooth;
var lineSmooth = {};
lineSmooth.lengthMin = 8; // square of the pixel length
lineSmooth.angle = 0.8; // angle threshold
lineSmooth.match = false; // not working.
// back buffer to save the canvas allowing the new line to be erased
var backBuffer = createImage(canvas.width,canvas.height);
var currentLine = [];
mouse.lastButtonRaw = 0; // add mouse last incase not there
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.clearRect(0,0,canvas.width,canvas.height);
var drawing = false; // if drawing
var input = false; // if menu input
var smoothIt = false; // flag to allow feedback that smoothing is happening as it takes some time.
function draw(){
// if not drawing test for menu interaction and draw the menus
if(!drawing){
if(mouse.x < 203 && mouse.y < 24){
if(mouse.y < 13){
if(mouse.buttonRaw === 1){
ctx.clearRect(3,3,200,10);
lineSmooth.angle = (mouse.x-3)/200;
input = true;
}
}else
if(mouse.buttonRaw === 1){
ctx.clearRect(3,14,200,10);
lineSmooth.lengthMin = (mouse.x-3)/10;
input = true;
}
canvas.style.cursor = "pointer";
}else{
canvas.style.cursor = "crosshair";
}
if(mouse.buttonRaw === 0 && input){
input = false;
mouse.lastButtonRaw = 0;
}
ctx.lineWidth = 0.5;
ctx.fillStyle = "red";
ctx.clearRect(3,3,200,10);
ctx.clearRect(3,14,200,10);
ctx.fillRect(3,3,lineSmooth.angle*200,10);
ctx.fillRect(3,14,lineSmooth.lengthMin*10,10);
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "#000"
ctx.strokeRect(3,3,200,10);
ctx.fillText("Smooth "+(lineSmooth.angle * (180 / Math.PI)).toFixed(0)+"deg",5,2)
ctx.strokeRect(3,14,200,10);
ctx.fillText("Detail "+lineSmooth.lengthMin.toFixed(0) + "pixels",5,13);
}else{
canvas.style.cursor = "crosshair";
}
if(!input){
ctx.lineWidth = 3;
if(mouse.buttonRaw === 4 && mouse.lastButtonRaw === 0){
currentLine = [];
drawing = true;
backBuffer.ctx.clearRect(0,0,canvas.width,canvas.height);
backBuffer.ctx.drawImage(canvas,0,0);
currentLine.push([mouse.x,mouse.y])
}else
if(mouse.buttonRaw === 4){
var lp = currentLine[currentLine.length-1]; // get last point
// dont record point if no movement
if(mouse.x !== lp[0] || mouse.y !== lp[1] ){
currentLine.push([mouse.x,mouse.y]);
ctx.beginPath();
ctx.moveTo(lp[0],lp[1])
ctx.lineTo(mouse.x,mouse.y);
ctx.stroke();
liveSmooth = smoothLine(
simplifyLineRDP(
currentLine,
lineSmooth.lengthMin
),
lineSmooth.angle,
lineSmooth.match
);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(backBuffer,0,0);
ctx.strokeStyle = "Blue";
drawSmoothedLine(liveSmooth );
ctx.strokeStyle = "black";
}
}else
if(mouse.buttonRaw === 0 && mouse.lastButtonRaw === 4){
ctx.textAlign = "center"
ctx.fillStyle = "red"
ctx.fillText("Smoothing...",canvas.width/2,canvas.height/5);
smoothIt = true;
}else
if(smoothIt){
smoothIt = false;
var newLine = smoothLine(
simplifyLineRDP(
currentLine,
lineSmooth.lengthMin
),
lineSmooth.angle,
lineSmooth.match
);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(backBuffer,0,0);
drawSmoothedLine(newLine);
drawing = false;
}
if(mouse.buttonRaw === 1 && mouse.lastButtonRaw === 0){
currentLine = [];
drawing = true;
backBuffer.ctx.clearRect(0,0,canvas.width,canvas.height);
backBuffer.ctx.drawImage(canvas,0,0);
currentLine.push([mouse.x,mouse.y])
}else
if(mouse.buttonRaw === 1){
var lp = currentLine[currentLine.length-1]; // get last point
// dont record point if no movement
if(mouse.x !== lp[0] || mouse.y !== lp[1] ){
currentLine.push([mouse.x,mouse.y]);
ctx.beginPath();
ctx.moveTo(lp[0],lp[1])
ctx.lineTo(mouse.x,mouse.y);
ctx.stroke();
}
}else
if(mouse.buttonRaw === 0 && mouse.lastButtonRaw === 1){
ctx.textAlign = "center"
ctx.fillStyle = "red"
ctx.fillText("Smoothing...",canvas.width/2,canvas.height/5);
smoothIt = true;
}else
if(smoothIt){
smoothIt = false;
var newLine = smoothLine(
simplifyLineRDP(
currentLine,
lineSmooth.lengthMin
),
lineSmooth.angle,
lineSmooth.match
);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(backBuffer,0,0);
drawSmoothedLine(newLine);
drawing = false;
}
}
// middle button clear
if(mouse.buttonRaw === 2){
ctx.clearRect(0,0,canvas.width,canvas.height);
}
mouse.lastButtonRaw = mouse.buttonRaw;
requestAnimationFrame(draw);
}
draw();
.canC { width:1000px; height:500px; border:1px black solid;}
<canvas class="canC" id="canV" width=1000 height=500></canvas>
You could save the last position and draw a line between the last point and the actual point.
if (lastX && lastY && (x !== lastX || y !== lastY)) {
ctx.fillStyle = "#000000";
ctx.lineWidth = 2 * size;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
// ...
lastX = x;
lastY = y;
}
On mouseup event set the two variables to zero.
var canvas, ctx
var mouseX, mouseY, mouseDown = 0,
lastX, lastY;
function draw(ctx,x,y,size) {
if (lastX && lastY && (x !== lastX || y !== lastY)) {
ctx.fillStyle = "#000000";
ctx.lineWidth = 2 * size;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
}
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
lastX = x;
lastY = y;
}
function clearCanvas(canvas,ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
function onMouseDown() {
mouseDown = 1
draw(ctx, mouseX, mouseY, 2)
}
function onMouseUp() {
mouseDown = 0;
lastX = 0;
lastY = 0;
}
function onMouseMove(e) {
getMousePos(e)
if (mouseDown == 1) {
draw(ctx, mouseX, mouseY, 2)
}
}
function getMousePos(e) {
if (!e)
var e = event
if (e.offsetX) {
mouseX = e.offsetX
mouseY = e.offsetY
}
else if (e.layerX) {
mouseX = e.layerX
mouseY = e.layerY
}
}
function init() {
canvas = document.getElementById('sketchpad')
ctx = canvas.getContext('2d')
canvas.addEventListener('mousedown', onMouseDown, false)
canvas.addEventListener('mousemove', onMouseMove, false)
window.addEventListener('mouseup', onMouseUp, false)
}
init();
<canvas id="sketchpad" width="600" height="300"></canvas>
Good question! And I recommend you a site https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API to learn more canvas API.
I think using lineTo is better than arc.So I hope this code will help you.
var canvas, ctx;
var mouseDown = 0, lastX, lastY;
function draw(ctx,x,y) {
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(x,y);
ctx.closePath();
ctx.stroke();
}
function clearCanvas(canvas,ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
function onMouseDown(e) {
var xy = getMousePos(e);
lastX = xy.mouseX;
lastY = xy.mouseY;
mouseDown = 1;
}
function onMouseUp() {
mouseDown = 0
}
function onMouseMove(e) {
if (mouseDown == 1) {
var xy = getMousePos(e);
draw(ctx, xy.mouseX, xy.mouseY);
lastX = xy.mouseX, lastY = xy.mouseY;
}
}
function getMousePos(e) {
var o = {};
if (!e)
var e = event
if (e.offsetX) {
o.mouseX = e.offsetX
o.mouseY = e.offsetY
}
else if (e.layerX) {
o.mouseX = e.layerX
o.mouseY = e.layerY
}
return o;
}
function init() {
canvas = document.getElementById('sketchpad')
ctx = canvas.getContext('2d')
canvas.addEventListener('mousedown', onMouseDown, false)
canvas.addEventListener('mousemove', onMouseMove, false)
canvas.addEventListener('mouseup', onMouseUp, false)
}
init();

How to make a game on javascript with canvas that makes a sprite appear to be jumping

I am new to javascript, and am trying to make a game that would hopefully end up isometric (I don't care so much about that, as long as I get an idea of how to). My code is:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
canvas {
border:1px solid #d3d3d3;
background-color: #f1f1f1;
}
</style>
</head>
<body onload="startGame()">
<script>
var myGamePiece;
function startGame() {
myGamePiece = new component(30, 30, "blue", 225, 225);
myGameArea.start();
}
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
e.preventDefault();
myGameArea.keys = (myGameArea.keys || []);
myGameArea.keys[e.keyCode] = (e.type == "keydown");
})
window.addEventListener('keyup', function (e) {
myGameArea.keys[e.keyCode] = (e.type == "keydown");
})
},
stop : function() {
clearInterval(this.interval);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speed = 0;
this.angle = 0;
this.moveAngle = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillStyle = color;
ctx.fillRect(this.width / -2, this.height / -2, this.width, this.height);
ctx.restore();
}
this.newPos = function() {
this.angle += this.moveAngle * Math.PI / 180;
this.x += this.speed * Math.sin(this.angle);
this.y -= this.speed * Math.cos(this.angle);
}
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.moveAngle = 0;
myGamePiece.speed = 0;
if (myGameArea.keys && myGameArea.keys[37]) {myGamePiece.x -=2; }
if (myGameArea.keys && myGameArea.keys[39]) {myGamePiece.x += 2; }
if (myGameArea.keys && myGameArea.keys[38]) {myGamePiece.y -= 1; }
if (myGameArea.keys && myGameArea.keys[40]) {myGamePiece.y += 1; }
if (myGameArea.keys && myGameArea.keys[32]) {myGamePiece.y -= 3;}
myGamePiece.newPos();
myGamePiece.update();
}
</script>
<p></p>
</body>
</html>
which I mostly copied and pasted from another website (http://www.w3schools.com/games/tryit.asp?filename=trygame_movement_keyboard). What I want to know is how to make it so that when the player presses space, myGamePiece goes up and down to appear to be jumping; making it move up a certain number of spaces, but then return back to the coordinates it was before.
Game Physics. JUMPING the basics
Real world V game world.
Game jumping is usually done non deterministically, that means you are not sure when or where the play may land. Very much not like real life. In real life once you jump, where and when you land is up to gravity and air friction, unless you can fly the result of jumping is up to the universe.
In the game world this is far from true. The jumper can usually change direction, double jump, do some hang time, or combo accelerated power punch down. All these things can happen at any time depending on the input of the user. Also gravity in the game world does not act like real gravity, sometimes some thing fall faster because they are heavy, some things need a second or to to feel the effect of gravity. The list goes on.
FALLING
BUT with all that said the game must still do the important thing that makes falling unlike an elevator ride. When in free fall you accelerate, every instance of time your speed changes, when you jump up you decelerate, when you fall you accelerate. We have our position y and our speed dy (delta y) to add gravity (g) we add a constant to the speed, when traveling up the screen (dy is < 0) or down the gravity changes the speed in the same direction at the same rate.
So every frame, add gravity dy += g then add our speed to our position y += dy. And that is it a very simple simulation of gravity, which if you measure time in game frames is also a perfect simulation of real gravity (near a big thing like the earth)
Thus the best way to do things like jumping, and the gravity that comes into play is to do it frame by frame.
Lets define what we need to do a jump.
A simple character
var c = {
x : ?, // this character's position
y : ?,
dx : ?, // the amount to move per frame The players velocity in x and y
dy : ?,
w : ?, // the character's width and height
h : ?,
onGround : false, // a flag to indicate on the ground or not
}
And some environment info
const GROUND_Y = canvas.height - 10; // where the ground is
const GRAVITY = 1; // in pixels per frame
Then every frame we update the character checking if on the ground and if not applying gravity and checking for the ground.
c.update = function(){
if(this.onGround){ // nothing to do but wait
}else{ // must be in the air
// Every frame the player accelerates down by the pull of gravity
// so increase the player y speed
this.dy += GRAVITY; // apply the gravity to the speed.
// now add the y speed to the y position
this.y += this.dy;
// Now we must check for the ground which if the player position x,y is for
// its center the ground will be half it's height away
if(this.y + (this.h / 2) > GROUND_Y){ // have we hit the ground
// yes stop downward motion
this.dy = 0;
// the speed may have put the character slightly below the ground
// so fix the postion so that it is correct
this.y = GROUND_Y - this.h /2; // set position to the ground - half its height
// And set the flag to indicate that the character is on the ground
this.onGround = true;
}
}
}
So that is gravity taken care of.
JUMPING
To jump we apply a force that accelerates us away from the ground. This force is only for an instant, once of the ground we have nothing to push against so we can apply no more force, it is up to gravity to bring us down. As gravity has been sorted in the above function all we need to do is the apply the jumping force.
const JUMP_ACCELERATION = GRAVITY * 20; // the bigger this number the higher the jump
Now add the function to make the jump
c.jump = function(){
// check if we can jump. That is are we on the ground
if(this.onGround){
// flag that we are no longer on the ground and left to the will of gravity
this.onGround = false;
// then apply the change in speed.
this.dy -= JUMP_ACCELERATION; // subtract jump accel from the speed
// to give a negative speed (up)
}
}
And that is it, the gravity function will take care of everything for you so you must call the c.update Function once every frame, the jump function you call only once per jump.
JUMPING IT DEMO
Click mouse to jump, a non challenging flappy It.
Taken from an old project this demo shows a very simple jumping character. The object name is it and the functions you want to look at are it.update(), it.jump(), and it.preJump() The code you want is between the comments //Answer code
All the character can do is jump, it can multy jump, and can jump higher if you click and hold the mouse, then release to jump.
/** ImageTools.js begin **/
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var i = this.canvas(width, height);
i.ctx = i.getContext("2d");
return i;
},
loadImage : function (url, cb) {
var i = new Image();
i.src = url;
i.addEventListener('load', cb);
i.addEventListener('error', cb);
return i;
},
image2Canvas : function (img) {
var i = this.canvas(img.width, img.height);
i.ctx = i.getContext("2d");
i.drawImage(i, 0, 0);
return i;
},
drawSpriteLinked : function(image,spriteIndex, x, y, scale, ang, alpha) {
var w,h,spr;
spr = image.sprites[spriteIndex];
w = spr.w; h = spr.h;
ctx.globalAlpha = alpha;
var xdx = Math.cos(ang) * scale;
var xdy = Math.sin(ang) * scale;
ctx.save();
ctx.transform(xdx, xdy, -xdy, xdx, x, y);
ctx.drawImage(image, spr.x, spr.y, w, h, -w/2, -h/2, w, h);
ctx.restore();
},
drawSprite : function(image,spriteIndex, x, y, scale, ang, alpha) {
var w,h,spr;
spr = image.sprites[spriteIndex];
w = spr.w; h = spr.h;
ctx.globalAlpha = alpha;
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.rotate(ang);
ctx.drawImage(image, spr.x, spr.y, w, h, -w/2, -h/2, w, h);
},
drawSpriteSLinked : function(image,spriteIndex, x, y, scale, scaleX, ang, alpha) {
var w,h,spr;
spr = image.sprites[spriteIndex];
w = spr.w; h = spr.h;
ctx.globalAlpha = alpha;
var xdx = Math.cos(ang) * scale;
var xdy = Math.sin(ang) * scale;
ctx.save()
ctx.transform(xdx * scaleX, xdy * scaleX, -xdy, xdx, x, y);
ctx.drawImage(image, spr.x, spr.y, w, h, -w/2, -h/2, w, h);
ctx.restore();
},
drawSpriteS : function(image,spriteIndex, x, y, scale, scaleX, ang, alpha) {
var w,h,spr;
spr = image.sprites[spriteIndex];
w = spr.w; h = spr.h;
ctx.globalAlpha = alpha;
ctx.setTransform(scale * scaleX, 0, 0, scale, x, y);
ctx.rotate(ang);
ctx.drawImage(image, spr.x, spr.y, w, h, -w/2, -h/2, w, h);
},
hex2RGBA : function(hex){
if(typeof hex === "string"){
var str = "rgba(";
if(hex.length === 4 || hex.length === 5){
str += (parseInt(hex.substr(1,1),16) * 16) + ",";
str += (parseInt(hex.substr(2,1),16) * 16) + ",";
str += (parseInt(hex.substr(3,1),16) * 16) + ",";
if(hex.length === 5){
str += (parseInt(hex.substr(3,1),16) / 16);
}else{
str += "1";
}
return str + ")";
}
if(hex.length === 7 || hex.length === 8){
str += parseInt(hex.substr(1,2),16) + ",";
str += parseInt(hex.substr(3,2),16) + ",";
str += parseInt(hex.substr(5,2),16) + ",";
if(hex.length === 5){
str += (parseInt(hex.substr(7,2),16) / 255).toFixed(3);
}else{
str += "1";
}
return str + ")";
}
return "rgba(0,0,0,0)";
}
},
createGradient : function(ctx, type, x, y, xx, yy, colours){
var i,g,c;
var len = colours.length;
if(type.toLowerCase() === "linear"){
g = ctx.createLinearGradient(x,y,xx,yy);
}else{
g = ctx.createRadialGradient(x,y,xx,x,y,yy);
}
for(i = 0; i < len; i++){
c = colours[i];
if(typeof c === "string"){
if(c[0] === " #"){
c = this.hex2RGBA(c);
}
g.addColorStop(Math.min(1,i / (len -1)),c); // need to clamp top to 1 due to floating point errors causes addColorStop to throw rangeError when number over 1
}
}
return g;
},
};
return tools;
})();
/** ImageTools.js end **/
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var globalTimeInt = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
var resized = false;
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
resized = true;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){
cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2;
if(it !== undefined){
it = createIt(cw,ch,sprites);
}
}
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { 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; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
resized = false;
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
function drawText(text,x,y,size,col){
var f = size + "px Arial";
if(f !== ctx.font){
ctx.font = f;
}
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = col;
ctx.fillText(text,x,y);
}
function drawLoad(){
if(!resourcesReady || !canPlay){
drawText(message,cw,ch * 0.5, FONT_SIZE, MESSAGE_COL);
if (!canPlay && resourcesReady){
drawText("Try reloading the page.",cw,ch * 0.5 + FONT_SIZE + 8,Math.floor(FONT_SIZE /2) ,MESSAGE_COL);
}else{
drawText("Loading resources." ,cw,ch * 0.5 + FONT_SIZE + 8,Math.floor(FONT_SIZE /2) ,MESSAGE_COL);
}
}else{
if(message !== ""){
drawText(message,cw,ch * 0.5, FONT_SIZE, MESSAGE_COL);
}
}
}
const FONT = "px Arial"
const FONT_SIZE = Math.max(Math.floor(window.innerHeight/20),24)
ctx.textAlign = "center";
ctx.textBaseline = "middle";
function loaded(e){
if(e.type !== "error"){
this.sprites = [
{ x : 0, y : 0, w : 74, h : 116, },
{ x : 0, y : 126, w : 100, h : 113, },
{ x : 75, y : 0, w : 29, h : 42, },
{ x : 75, y : 43, w : 17, h : 22, },
{ x : 0, y : 249, w : 42, h : 18, },
{ x : 75, y : 66, w : 17, h : 15, },
{ x : 75, y : 82, w : 17, h : 12, },
{ x : 75, y : 95, w : 16, h : 9, },
{ x : 75, y : 105, w : 7, h : 7, },
{ x : 0, y : 268, w : 11, h : 5, },
]
resourcesReady = true;
canPlay = true;
it = createIt(cw,ch,this );
message = "";
return;
}
resourcesReady = true;
message = "LOAD FAILED!"
}
var it = null; // it is the character
var resourcesReady = false;
var canPlay = false;
var message = "Please Wait..."
const MESSAGE_COL = "white";
//var sprites = imageTools.loadImage("GreenIt.png",loaded )
var sprites = imageTools.loadImage("http://i.stack.imgur.com/ED6oC.png",loaded )
var background = imageTools.createImage(8,8);
background.ctx.fillStyle = imageTools.createGradient(ctx,"linear",0,0,8,8,["#0AF","#05A"]);
background.ctx.fillRect(0,0,8,8);
var ground = imageTools.createImage(8,32);
ground.ctx.fillStyle = imageTools.createGradient(ctx,"linear",0,0,8,32,["#0A0","#450","#754"]);
ground.ctx.fillRect(0,0,8,32);
ground.ctx.fillStyle = "black";
ground.ctx.fillRect(0,0,8,4);
const GROUND_OFFSET = 32;
const GRAV = 1;
var landed = false;
const MESSAGES = [
"Click mouse button to Jump",
"Click hold ... release to to add power to jump",
"Double click to double jump",
""
];
var messageCount = 0;
var fly = { // something to see
x : 0,
y : 0,
dx : 0,
dy : 0,
wait : 0,
onTheWall : false,
update : function(){
if(this.wait <= 0){
this.wait = Math.random() * 200+ 60;
this.onTheWall = Math.random() < 0.1 ? true : false;
if(this.onTheWall){
this.dx = 0;
this.dy = 0;
}else{
this.wait = Math.random() < 0.2 ? 10 : this.wait;
var x = (Math.random()-0.5) * 200;
var y = (Math.random()-0.5) * 200;
this.dx = (x - this.x) / this.wait;
this.dx = (y - this.y) / this.wait;
}
}else{
this.wait -= 1;
this.x += this.dx;
this.y += this.dy;
}
}
};
/*==============================================================================================
// Answer code
==============================================================================================*/
// info to define the character
const IT = {
body : 0, // sprite indexes
bodyFly : 1,
footDown : 2,
eyeOpen : 3,
foot : 4,
mouthOpen : 5,
eyeShut : 6,
mouthSmirk : 7,
eyeBall : 8,
mouth : 9, // sprite index end
grav : GRAV, // grav accel
maxJumpPower : 40,
minJump : 10,
jumpPower : 30, // mutiplys squat amount to give jump power
squatRate : 1, // how quick the squat is
squatResist : 0.8, // limits the amount of squat
landingBlinkTime : 30, // how long blink is on landing
blinkTime : 15, // how many frames to close eyes
blinkRate : 60 * 3, // 60 is one second . Time between blinks average
eyePos : {x : 0.13, y : -0.1}, // position as fraction of size
footPos : {x : 0.3, y : 0.5}, // position as fraction of size
lookAtGround : 1, // look ats
lookAtMouse : 2,
lookAtUser : 3,
lookAtFly : 4,
angle: 0,
jumpDy: 0, // the jump up speed used to rotate It when in air
}
// Function updates the character
const updateIt = function(){
if(this.blink > 0){
this.blink -= 1;
}
if(this.blinkTimer > 0){
this.blinkTimer -= 1;
if(this.blinkTimer === 0){
this.blink = IT.blinkTime;
}
}else{
// the two randoms create a random number that has a gausian distrabution centered on 0.5
// this creates a more realistic set of numbers.
this.blinkTimer = Math.floor(IT.blinkRate * (Math.random() + Math.random())/2 + IT.blinkRate / 2);
this.lookAt = Math.random() < 0.33 ? IT.lookAtUser : (Math.random() < 0.5 ? IT.lookAtMouse : IT.lookAtFly);
}
if(!this.onGround){
this.squat = 0;
//-------------------------------------
// do gravity
this.dy += IT.grav;
this.y += this.dy;
this.x += this.dx;
this.x = (this.x + ctx.canvas.width) % ctx.canvas.width;
var rotFraction = (this.jumpDy - this.dy) / this.jumpDy;
this.angle = this.jumpAngle * -rotFraction ;
if(this.dy > 13){
this.lookAt = IT.lookAtGround;
}
// check for the ground
if(this.y + this.tall / 2 > h - GROUND_OFFSET){
this.y = h - GROUND_OFFSET - this.tall / 2;
this.blink = Math.floor(IT.landingBlinkTime * (this.dy / 20));
this.blinkTimer = this.blink + 30;
this.squat = this.dy;
this.dy = 0;
this.onGround = true;
this.angle = -this.jumpAngle
}
}else{
this.squat *= IT.squatResist;
}
}
// draw the character
const drawIt = function(){
var bod = IT.body;
var spr = this.img.sprites;
var eye = this.blink > 0 ? IT.eyeShut : IT.eyeOpen;
var foot = IT.foot;
var footBehind = false; // draw feet behind or infront of body
if(!this.onGround){
if(this.dy >= 0){
if(this.dy > 2){
bod = IT.bodyFly;
}
}else{
footBehind = true;
foot = IT.footDown;
}
}
var xdx = Math.cos(this.angle);
var xdy = Math.sin(this.angle);
var px = this.x; // pivot
var py = this.y + 50;
var x = this.x ;
var y = this.y + this.squat;
var t = this.tall;
var f = this.fat;
if(footBehind){
if(!this.onGround){
var r = 1 - Math.min(1,-this.dy / 10);
imageTools.drawSpriteS(this.img,foot,x + f * IT.footPos.x,y - this.squat+ t * IT.footPos.y,1,-1,r,1);
imageTools.drawSprite(this.img,foot,x - f * IT.footPos.x,y - this.squat + t * IT.footPos.y,1,r,1);
}
}
ctx.setTransform(xdx,xdy,-xdy,xdx,px,py);
imageTools.drawSpriteLinked(this.img,bod,x - px,y - py,1,0,1);
if(!footBehind){
if(this.onGround){
imageTools.drawSpriteS(this.img,foot,x + f * IT.footPos.x,y - this.squat+ t * IT.footPos.y,1,-1,0,1);
imageTools.drawSprite(this.img,foot,x - f * IT.footPos.x,y - this.squat + t * IT.footPos.y,1,0,1);
}else{
var r = this.dy / 10;
imageTools.drawSpriteS(this.img,foot,x + f * IT.footPos.x,y - this.squat+ t * IT.footPos.y,1,-1,r,1);
imageTools.drawSprite(this.img,foot,x - f * IT.footPos.x,y - this.squat + t * IT.footPos.y,1,r,1);
}
}
if(this.blink){
ctx.setTransform(xdx,xdy,-xdy,xdx,px,py);
imageTools.drawSpriteLinked(this.img,eye,x + f * IT.eyePos.x - px, y + t * IT.eyePos.y - py,1,0,1);
imageTools.drawSpriteSLinked(this.img,eye,x - f * IT.eyePos.x - px, y + t * IT.eyePos.y - py,1,-1,0,1);
}else{
ctx.setTransform(xdx,xdy,-xdy,xdx,px,py);
imageTools.drawSpriteLinked(this.img,eye,x + f * IT.eyePos.x - px, y + t * IT.eyePos.y - py,1,0,1);
imageTools.drawSpriteSLinked(this.img,eye,x - f * IT.eyePos.x - px, y + t * IT.eyePos.y - py,1,-1,0,1);
var eyeDir = 0;
var eyeDist = 0;
if(this.blink === 0){
if(this.lookAt === IT.lookAtGround){
eyeDir = Math.PI/2;
eyeDist = 0.3;
}else if(this.lookAt === IT.lookAtUser){
eyeDir = 0;
eyeDist = 0;
}else if(this.lookAt === IT.lookAtFly){
eyeDir = Math.atan2(fly.y, fly.x);
eyeDist = (Math.hypot(fly.y ,fly.x) / 200) * 0.3;
}else{
eyeDir = Math.atan2(mouse.y - this.y, mouse.x - this.x);
eyeDist = (Math.hypot(this.y - mouse.y,this.x - mouse.x) / (Math.min(w,h)/2)) * 0.3;
}
eyeDist = Math.max(-0.3, Math.min(0.3, eyeDist));
var ex,ey;
ex = Math.cos(eyeDir) * spr[IT.eyeOpen].w * eyeDist;
ey = Math.sin(eyeDir) * spr[IT.eyeOpen].h * eyeDist;
imageTools.drawSpriteLinked(this.img, IT.eyeBall, x + f * IT.eyePos.x + ex - px, y + t * IT.eyePos.y + ey-py,1,0,1);
imageTools.drawSpriteLinked(this.img, IT.eyeBall, x - f * IT.eyePos.x + ex - px, y + t * IT.eyePos.y + ey-py,1,0,1);
}
}
}
// While mouse is down squat and prep to jump
const preJump = function(){
this.squat += IT.squatRate;
this.jumpPower += 0.5;
if(this.jumpPower > 30 && this.wiggle === 0) {
this.wiggle = 1;
}
this.jumpReady = true;
}
// when mouse released apply jump force
const jumpIt = function(){
var power = -IT.jumpPower * Math.min(IT.maxJumpPower,Math.max(IT.minJump,this.jumpPower))/IT.maxJumpPower;
this.dy = Math.sin(this.angle + Math.PI /2) * power;
this.dx = Math.cos(this.angle + Math.PI /2) * power;
if(this.onGround){
this.jumpDy = this.dy;
this.jumpAngle = this.angle;
}
this.wiggle = 0;
this.jumpPower = 0;
this.jumpReady = false;
this.squat = 0;
this.onGround = false;
}
// creates a character
var createIt = function(x,y,img){
return {
img : img,
x : x, // position
y : y,
dx : 0, // deltat speed
dy : 0,
sqaut : 0, // for landing and pre jump slight squat
onGround : false,
jumpPower : 0,
blink : 0, // blink controls
blinkTimer : 0,
lookAt : "ground", /// where to look
jumpReady : false, // flags if ready to jump
tall : img.sprites[IT.body].h, // how tall
fat : img.sprites[IT.body].w, // how wide
draw : drawIt, // functions
update : updateIt,
jump : jumpIt,
squatF : preJump,
}
}
function display(){ // put code in here
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.drawImage(background,0,0,w,h)
ctx.drawImage(ground,0,h-GROUND_OFFSET,w,GROUND_OFFSET);
fly.update()
drawLoad();
if(canPlay){
if(messageCount < MESSAGES.length){
if(it.onGround && !landed){
landed = true;
message = MESSAGES[messageCount];
messageCount += 1;
}
}
if(resized) { // to prevent resize display issue
resized = false;
it.y = h - GROUND_OFFSET - it.tall / 2;
}
if(it.onGround) {
it.angle = Math.atan2((it.y + 130)-10, it.x- mouse.x) / 3;
it.angle = it.angle < -1 ? -1 : it.angle > 1 ? 1 : it.angle;
it.angle = Math.pow(Math.abs(it.angle),0.5) * Math.sign(it.angle);
it.angle -= Math.PI / 4;
if(it.wiggle > 0.1) {
it.angle += Math.sin((it.wiggle * Math.PI) ** 2) * 0.01 * it.wiggle;
it.wiggle *= 0.95;
}
}
if(mouse.buttonRaw & 1){
it.squatF();
}else{
if(it.jumpReady){
it.jump();
landed = false;
}
}
it.update();
it.draw();
}
//ctx.clearRect(0,0,w,h);
}
/*==============================================================================================
// Answer End
==============================================================================================*/
function update(timer){ // Main update loop
globalTimeInt = Math.floor(globalTime = timer);
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
I recommend you check this (part 1) and this(part 2) tutorial that I have followed.
Your "jump" animation just boils down to creating a jump function that sets a max jump height to your object and sets a boolean to var jumping = true. As long as your character is "jumping" you increment the y position of your character.
Once you get to your desired height, create a land function that does the opposite.
Make a Javascript setinterval to update the height of object after every 20ms.
after every 20s set height = Initial_Height + u*t - (1/2)gt^2
use g = 9.8, u = some constant according to your screen.
t = Time passed till now. Which mean, Initially t=0, on first update t=20ms, on second update t=40ms.
Basically, you are simulating real life jumping in gravity.

Categories