There are two squares, they are moved with the arrows and asdw, and I have been trying to get this collision detection down for ages, it's killing me. Anyways, I have tried so many methods, I got really really close with one method, and everything was fine with it, until I found out that when you jumped next to it, it didn't complete the full jump because one of the sides was able to go through the other square. After a long time, I found out that method wouldn't work because with GetContext.2d, you cannot reference the bottom left of a square. There also was the issue that one corner could touch two different sides, which would set off conflicting messages. My solution for that was to make a third condition to specify which side the corner was touching, weather the top/bottom or left/right. I made a model of how the code would be structured:
The code I have currently looks like this:
const context = document.getElementById("canvasmulti").getContext("2d");
canvasmulti.width = window.innerWidth;
canvasmulti.height = window.innerHeight;
//CHARACTER:
const square = {
height: 75,
jumping: true,
width: 75,
x: canvasmulti.width - 75,
xVelocity: 0,
y: canvasmulti.height / 2,
yVelocity: 0,
jumpHeight: 30
};
const square2 = {
height: 75,
jumping: true,
width: 75,
x: 0,
xVelocity: 0,
y: canvasmulti.height / 2,
yVelocity: 0,
jumpHeight: 30
};
//MOVEMENT:
const controller = {
left: false,
right: false,
up: false,
keyListener: function (event) {
let key_state = (event.type == "keydown") ? true : false;
switch (event.keyCode) {
case 37: // left arrow
controller.left = key_state;
break;
case 38: // up arrow
controller.up = key_state;
break;
case 39: // right arrow
controller.right = key_state;
break;
}
}
};
const controller2 = {
left: false,
right: false,
up: false,
keyListener: function (event) {
let key_state = (event.type == "keydown") ? true : false;
switch (event.keyCode) {
case 65: // left arrow
controller2.left = key_state;
break;
case 87: // up arrow
controller2.up = key_state;
break;
case 68: // right arrow
controller2.right = key_state;
break;
}
}
};
const loop = function () {
//controller one
if (controller.up && square.jumping == false) {
square.yVelocity -=square.jumpHeight;
square.jumping = true;}
if (controller.left) {
square.xVelocity -= 0.5;}
if (controller.right) {
square.xVelocity += 0.5;}
//controller two
if (controller2.up && square2.jumping == false) {
square2.yVelocity -= square2.jumpHeight;
square2.jumping = true;}
if (controller2.left) {
square2.xVelocity -= 0.5;}
if (controller2.right) {
square2.xVelocity += 0.5;}
//controller one
square.yVelocity += 1.5;// gravity
square.x += square.xVelocity;
square.y += square.yVelocity;
square.xVelocity *= 0.9;// friction
square.yVelocity *= 0.9;// friction
//controller two
square2.yVelocity += 1.5;// gravity
square2.x += square2.xVelocity;
square2.y += square2.yVelocity;
square2.xVelocity *= 0.9;// friction
square2.yVelocity *= 0.9;// friction
// if square1 is falling below floor line
if (square.y > canvasmulti.height - 75) {
square.jumping = false;
square.y = canvasmulti.height - 75;
square.yVelocity = 0;
}
// if square2 is falling below floor line
if (square2.y > canvasmulti.height - 75) {
square2.jumping = false;
square2.y = canvasmulti.height - 75;
square2.yVelocity = 0;
}
// if square1 is going off the left of the screen
if (square.x < 0) {
square.x = 0;
} else if (square.x > canvasmulti.width - 75) {// if square goes past right boundary
square.x = canvasmulti.width - 75;
}
// if square2 is going off the left of the screen
if (square2.x < 0) {square2.x = 0;}
else if (square2.x > canvasmulti.width - 75) {// if square goes past right boundary
square2.x = canvasmulti.width - 75;
}
// Creates the backdrop for each frame
context.fillStyle = "#394129";
context.fillRect(0, 0, canvasmulti.width, canvasmulti.height); // x, y, width, height
// Creates and fills square1 for each frame
context.fillStyle = "#8DAA9D"; // hex for cube color
context.beginPath();
context.rect(square.x, square.y, square.width, square.height);
context.fill();
// Creates and fills square2 for each frame
context.fillStyle = "#781818"; // hex for cube color
context.beginPath();
context.rect(square2.x, square2.y, square2.width, square2.height);
context.fill();
// Collision detection of squares
if (square.x <= square2.x + square2.width && square.x >= square2.x &&
square.y >= square2.y && square.y <= square2.y + square2.height &&
square2.y + square2.height >= square.y && square2.y + square2.height <= square.y + square.height)
{square.y = square2.y - square.height; //set it to a position where they don't overlap
square.yVelocity = 0;}; //One move down
if (square.x <= square2.x + square2.width && square.x >= square2.x &&
square.y >= square2.y && square.y <= square2.y + square2.height &&
square2.x + square2.width >= square.x && square2.x + square2.width <= square.x + square.width);
{square.x = square2.x + square2.width; // set it to a position where they don't overlap
square.xVelocity = 0;}; //One move right
if (square2.x >= square.x && square2.x <= square.x + square.width &&
square2.y >= square.y && square2.y <= square.y + square.heights &&
square.y + square.height >= square2.y && square.y + square.height <= square2.y + square2.height)
{square.y = square2.y - square.height; // set it to a position where they don't overlap
square.yVelocity = 0;}; //One move up
if (square2.x >= square.x && square2.x <= square.x + square.width &&
square2.y >= square.y && square2.y <= square.y + square.heights &&
square.x + square.width >= square2.y && square.x + square.width <= square2.y + square2.height)
{square.x = square2.x - square.width; // set it to a position where they don't overlap
square.xVelocity = 0;}; //One move left
// call update when the browser is ready to draw again
window.requestAnimationFrame(loop);
};
//square1
window.addEventListener("keydown", controller.keyListener)
window.addEventListener("keyup", controller.keyListener);
//square2
window.addEventListener("keydown", controller2.keyListener)
window.addEventListener("keyup", controller2.keyListener);
window.requestAnimationFrame(loop);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">
<title>ToneMain</title>
<style>
body {
height:100vh;
width:100vh;
margin: 0;
}
</style>
</head>
<body>
<canvas id="canvasmulti"></canvas>
<script src="ToneMainMulti.js"></script>
</body>
</html>
For the love of my sanity this was the last time in a month I tried to fix this. This was my last blow. I would really apprestiate any effort or advice to solve my problem, this is tough to wrap your head around and may take a long time, thxxx <3
I feel your pain. CD is not as easy as one would hope. I was unable to get your code working so I rewrote it using ES6 classes because I like them. I also don't put everything into a loop function but rather place controlling parameters into separate functions and call those functions in the animate loop. Hopefully it isn't confusing from how you write.
The CD portion calculates the distance between the different sides of the blocks and determines which sides from each block are the closest and will collide. By doing this only one function will get called at a time. This prevents any of the unwanted behavior you get because of the blocks having a slight overlap and causing other functions to trigger also. i.e. colliding on the X but having the Y trigger too. I've also set it to where if the blocks are stacked only the top one can jump.
You can tailor this to whatever suits your needs.
const canvas = document.getElementById('canvasmulti');
const context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
let gravity = 1.5;
let friction = 0.9;
//CHARACTER:
class Player {
constructor(x, y, vx, vy, c, j) {
//each player must have separate values for control purposes i.e. velocity/jump/attack etc.
this.x = x;
this.y = y;
this.w = 50;
this.h = 50;
this.vx = vx;
this.vy = vy;
this.color = c;
this.jumping = j;
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
}
update() {
this.draw();
this.vy += gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.canvasCollision() //must be after other updates
}
}
let player1 = new Player(0, 0, 0, 0, 'red', false); //must have separate variables like jump/attack etc
let player2 = new Player(canvasmulti.width - 50, 0, 0, 0, 'blue', false);
function controlPlayer1(obj) {
//this order matters. If update is before jump then obj won't jump when on top of other block.
if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller1.left1) { obj.vx -= 0.5 };
if (controller1.right1) { obj.vx += 0.5 };
obj.update();
}
function controlPlayer2(obj) {
if (controller2.up2 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller2.right2) { obj.vx += 0.5 };
if (controller2.left2) { obj.vx -= 0.5 };
obj.update();
}
//MOVEMENT:
class Controller {
constructor() {
this.left1 = false;
this.up1 = false;
this.right1 = false;
this.left2 = false;
this.up2 = false;
this.right2 = false;
let controller1 = (e) => {
if (e.code === 'ArrowRight') { this.right1 = e.type === 'keydown' }
if (e.code === 'ArrowLeft') { this.left1 = e.type === 'keydown' }
if (e.code === 'ArrowUp') { this.up1 = e.type === 'keydown' }
}
let controller2 = (e) => {
if (e.code === 'KeyD') { this.right2 = e.type === 'keydown' }
if (e.code === 'KeyA') { this.left2 = e.type === 'keydown' }
if (e.code === 'KeyW') { this.up2 = e.type === 'keydown' }
}
window.addEventListener('keydown', controller1);
window.addEventListener('keyup', controller1);
window.addEventListener('keydown', controller2);
window.addEventListener('keyup', controller2);
}
}
let controller1 = new Controller();
let controller2 = new Controller();
function collisionDetection(obj, obj2) {
//center point of each side of obj1
let objLeft = {x: obj.x, y: obj.y + obj.h/2};
let objTop = {x: obj.x + obj.w/2, y: obj.y};
let objRight = {x: obj.x + obj.w, y: obj.y + obj.h/2};
let objBottom = {x: obj.x + obj.w/2, y: obj.y + obj.h};
//center point of each side a obj2
let obj2Left = {x: obj2.x, y: obj2.y + obj2.h/2};
let obj2Top = {x: obj2.x + obj2.w/2, y: obj2.y};
let obj2Right = {x: obj2.x + obj2.w, y: obj2.y + obj2.h/2};
let obj2Bottom = {x: obj2.x + obj2.w/2, y: obj2.y + obj2.h};
//distance between obj1 and obj2 opposing sides
let rightDistX = objRight.x - obj2Left.x;
let rightDistY = objRight.y - obj2Left.y;
let leftDistX = objLeft.x - obj2Right.x;
let leftDistY = objLeft.y - obj2Right.y;
let topDistX = objTop.x - obj2Bottom.x;
let topDistY = objTop.y - obj2Bottom.y;
let bottomDistX = objBottom.x - obj2Top.x;
let bottomDistY = objBottom.y - obj2Top.y;
//pythagorean theorem for distance. dRight is from the right side of obj1 to the left of obj2. the rest follow suit.
let dRight = Math.sqrt(rightDistX*rightDistX + rightDistY*rightDistY);
let dLeft = Math.sqrt(leftDistX*leftDistX + leftDistY*leftDistY);
let dTop = Math.sqrt(topDistX*topDistX + topDistY*topDistY);
let dBottom = Math.sqrt(bottomDistX*bottomDistX + bottomDistY*bottomDistY);
//Math.min return the smallest value thus variable minimum will be which ever sides are closest together
let minimum = Math.min(dRight, dLeft, dBottom, dTop);
let val = 0;
//compare minimum to d*** and set val based on which ever side is closest
if (dTop == minimum) {
val = 1;
//the context stuff can be deleted. It's just here for visual. The if statements can be one line each.
context.lineWidth = 2;
context.strokeStyle = 'blue';
context.beginPath();
context.moveTo(objTop.x, objTop.y);
context.lineTo(obj2Bottom.x, obj2Bottom.y);
context.stroke();
}
else if (dRight == minimum) {
val = 2;
context.strokeStyle = 'orange';
context.beginPath();
context.moveTo(objRight.x, objRight.y);
context.lineTo(obj2Left.x, obj2Left.y);
context.stroke();
}
else if (dBottom == minimum) {
val = 3;
context.strokeStyle = 'green';
context.beginPath();
context.moveTo(objBottom.x, objBottom.y);
context.lineTo(obj2Top.x, obj2Top.y);
context.stroke();
}
else if (dLeft == minimum) {
val = 4;
context.strokeStyle = 'pink';
context.beginPath();
context.moveTo(objLeft.x, objLeft.y);
context.lineTo(obj2Right.x, obj2Right.y);
context.stroke();
}
//pass the objects and val to collisionActions
collisionActions(obj, obj2, val)
}
function collisionActions(obj, obj2, val) {
//player1 top to player2 bottom
if (obj.y <= obj2.y + obj2.h && obj2.y + obj2.h >= obj.y && val == 1) {
obj2.y = obj.y - obj2.h;
obj.y = obj2.y + obj2.h;
obj.vy = 0;
obj2.vy = 0;
obj2.jumping = false;
obj.jumping = true;
}
//player1 right to player2 left
if (obj.x + obj.w >= obj2.x && obj2.x <= obj.x + obj.w && val == 2) {
obj2.x = obj.x + obj.w;
obj.x = obj2.x - obj.w - 1;
obj.vx = 0;
obj2.vx = 0;
}
//player1 bottom to player2 top
if (obj.y + obj.h >= obj2.y && obj2.y <= obj.y + obj.h && val == 3) {
obj.y = obj2.y - obj.h;
obj2.y = obj.y + obj.h;
obj.vy = 0;
obj2.vy = 0;
obj.jumping = false;
obj2.jumping = true;
}
//player1 left to player2 right
if (obj.x <= obj2.x + obj2.w && obj2.x + obj2.w >= obj.x && val == 4) {
obj2.x = obj.x - obj2.w;
obj.x = obj2.x + obj2.w + 1;
obj.vx = 0;
obj2.vx = 0;
}
}
function loop() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
controlPlayer1(player1);
controlPlayer2(player2);
collisionDetection(player1, player2)
requestAnimationFrame(loop)
}
loop();
It may seem like a lot but it's really not and it works very well. I was having the same issues a while back and came up with this to use on a collision map because my character would randomly shoot of in the wrong direction or jumping next to a block wouldn't work. I have modified that code to be used here with just 2 moving blocks. Let me know if this works for you.
I am using a flood fill algorithm to fill in circles drawn on the canvas. The issue I am having is that the algorithm isn't filling right up to the edge of the circle.
Here is the algorithm based on this blog post:
function paintLocation(startX, startY, r, g, b) {
var colorLayer = context1.getImageData(0, 0, canvasWidth, canvasHeight);
pixelPos = (startY * canvasWidth + startX) * 4;
startR = colorLayer.data[pixelPos];
startG = colorLayer.data[pixelPos + 1];
startB = colorLayer.data[pixelPos + 2];
var pixelStack = [
[startX, startY]
];
var drawingBoundTop = 0;
while (pixelStack.length) {
var newPos, x, y, pixelPos, reachLeft, reachRight;
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
pixelPos = (y * canvasWidth + x) * 4;
while (y-- >= drawingBoundTop && matchStartColor(colorLayer, pixelPos, startR, startG, startB)) {
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
++y;
reachLeft = false;
reachRight = false;
while (y++ < canvasHeight - 1 && matchStartColor(colorLayer, pixelPos, startR, startG, startB)) {
colorPixel(colorLayer, pixelPos, r, g, b);
if (x > 0) {
if (matchStartColor(colorLayer, pixelPos - 4, startR, startG, startB)) {
if (!reachLeft) {
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < canvasWidth - 1) {
if (matchStartColor(colorLayer, pixelPos + 4, startR, startG, startB)) {
if (!reachRight) {
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
context1.putImageData(colorLayer, 0, 0);
}
Please see the JSFiddle or the below image to see what I mean. Clicking inside any circles will change the colour between yellow and black (the issue is far more visible with black).
I've read that the issue could be something to do with the anti-aliasing and I have tried turning it off with context1.imageSmoothingEnabled = true; but it didn't make a difference.
I have also tried changing my matchStartColour function as per this question but that doesn't help.
function matchStartColor(colorLayer, pixelPos, startR, startG, startB) {
var r = colorLayer.data[pixelPos];
var g = colorLayer.data[pixelPos + 1];
var b = colorLayer.data[pixelPos + 2];
return (r == startR && g == startG && b == startB);
}
I think it might have something to do with the fact that the circles have no fill colour and the background of the canvas isn't white but it is transparent black. I have tried changing the canvas background to white but that also didn't help.
Use flood fill to create a Mask
I just happened to do a floodFill the other day that addresses the problem of antialiased edges.
Rather than paint to the canvas directly, I paint to a byte array that is then used to create a mask. The mask allows for the alpha values to be set.
The fill can have a tolerance and a toleranceFade that control how it deals with colours that approch the tolerance value.
When pixel's difference between the start colour and tolerance are greater than (tolerance - toleranceFade) I set the alpha for that pixel to 255 - ((differance - (tolerance - toleranceFade)) / toleranceFade) * 255 which creates a nice smooth blend at the edges of lines. Though it does not work for all situations for high contrast situations it is an effective solution.
The example below shows the results of with and without the toleranceFade. The blue is without the toleranceFade, the red is with the tolerance set at 190 and the toleranceFade of 90.
You will have to play around with the setting to get the best results for your needs.
function showExample(){
var canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
ctx.fillStyle = "white"
ctx.fillRect(0,0,canvas.width,canvas.height)
ctx.lineWidth = 4;
ctx.strokeStyle = "black"
ctx.beginPath();
ctx.arc(100,100,90,0,Math.PI * 2);
ctx.arc(120,100,60,0,Math.PI * 2);
ctx.stroke();
ctx.fillStyle = "blue";
floodFill.fill(100,100,1,ctx)
ctx.fillStyle = "red";
floodFill.fill(40,100,190,ctx,null,null,90)
}
// FloodFill2D from https://github.com/blindman67/FloodFill2D
var floodFill = (function(){
"use strict";
const extent = {
top : 0,
left : 0,
bottom : 0,
right : 0,
}
var keepMask = false; // if true then a mask of the filled area is returned as a canvas image
var extentOnly = false; // if true then the extent of the fill is returned
var copyPixels = false; // if true then creating a copy of filled pixels
var cutPixels = false; // if true and copyPixels true then filled pixels are removed
var useBoundingColor = false; // Set the colour to fill up to. Will not fill over this colour
var useCompareColor = false; // Rather than get the pixel at posX,posY use the compareColours
var red, green, blue, alpha; // compare colours if
var canvas,ctx;
function floodFill (posX, posY, tolerance, context2D, diagonal, area, toleranceFade) {
var w, h, painted, x, y, ind, sr, sg, sb, sa,imgData, data, data32, RGBA32, stack, stackPos, lookLeft, lookRight, i, colImgDat, differance, checkColour;
toleranceFade = toleranceFade !== undefined && toleranceFade !== null ? toleranceFade : 0;
diagonal = diagonal !== undefined && diagonal !== null ? diagonal : false;
area = area !== undefined && area !== null ? area : {};
area.x = area.x !== undefined ? area.x : 0;
area.y = area.y !== undefined ? area.y : 0;
area.w = area.w !== undefined ? area.w : context2D.canvas.width - area.x;
area.h = area.h !== undefined ? area.h : context2D.canvas.height - area.y;
// vet area is on the canvas.
if(area.x < 0){
area.w = area.x + area.w;
area.x = 0;
}
if(area.y < 0){
area.h = area.y + area.h;
area.y = 0;
}
if(area.x >= context2D.canvas.width || area.y >= context2D.canvas.height){
return false;
}
if(area.x + area.w > context2D.canvas.width){
area.w = context2D.canvas.width - area.x;
}
if(area.y + area.h > context2D.canvas.height){
area.h = context2D.canvas.height - area.y;
}
if(area.w <= 0 || area.h <= 0){
return false;
}
w = area.w; // width and height
h = area.h;
x = posX - area.x;
y = posY - area.y;
if(extentOnly){
extent.left = x; // set up extent
extent.right = x;
extent.top = y;
extent.bottom = y;
}
if(x < 0 || y < 0 || x >= w || y >= h){
return false; // fill start outside area. Don't do anything
}
if(tolerance === 255 && toleranceFade === 0 && ! keepMask){ // fill all
if(extentOnly){
extent.left = area.x; // set up extent
extent.right = area.x + w;
extent.top = area.y;
extent.bottom = area.y + h;
}
context2D.fillRect(area.x,area.y,w,h);
return true;
}
if(toleranceFade > 0){ // add one if on to get correct number of steps
toleranceFade += 1;
}
imgData = context2D.getImageData(area.x,area.y,area.w,area.h);
data = imgData.data; // image data to fill;
data32 = new Uint32Array(data.buffer);
painted = new Uint8ClampedArray(w*h); // byte array to mark painted area;
function checkColourAll(ind){
if( ind < 0 || painted[ind] > 0){ // test bounds
return false;
}
var ind4 = ind << 2; // get index of pixel
if((differance = Math.max( // get the max channel difference;
Math.abs(sr - data[ind4++]),
Math.abs(sg - data[ind4++]),
Math.abs(sb - data[ind4++]),
Math.abs(sa - data[ind4++])
)) > tolerance){
return false;
}
return true
}
// check to bounding colour
function checkColourBound(ind){
if( ind < 0 || painted[ind] > 0){ // test bounds
return false;
}
var ind4 = ind << 2; // get index of pixel
differance = 0;
if(sr === data[ind4] && sg === data[ind4 + 1] && sb === data[ind4 + 2] && sa === data[ind4 + 3]){
return false
}
return true
}
// this function checks the colour of only selected channels
function checkColourLimited(ind){ // check only colour channels that are not null
var dr,dg,db,da;
if( ind < 0 || painted[ind] > 0){ // test bounds
return false;
}
var ind4 = ind << 2; // get index of pixel
dr = dg = db = da = 0;
if(sr !== null && (dr = Math.abs(sr - data[ind4])) > tolerance){
return false;
}
if(sg !== null && (dg = Math.abs(sg - data[ind4 + 1])) > tolerance){
return false;
}
if(sb !== null && (db = Math.abs(sb - data[ind4 + 2])) > tolerance){
return false;
}
if(sa !== null && (da = Math.abs(sa - data[ind4 + 3])) > tolerance){
return false;
}
diferance = Math.max(dr, dg, db, da);
return true
}
// set which function to check colour with
checkColour = checkColourAll;
if(useBoundingColor){
sr = red;
sg = green;
sb = blue;
if(alpha === null){
ind = (y * w + x) << 2; // get the starting pixel index
sa = data[ind + 3];
}else{
sa = alpha;
}
checkColour = checkColourBound;
useBoundingColor = false;
}else if(useCompareColor){
sr = red;
sg = green;
sb = blue;
sa = alpha;
if(red === null || blue === null || green === null || alpha === null){
checkColour = checkColourLimited;
}
useCompareColor = false;
}else{
ind = (y * w + x) << 2; // get the starting pixel index
sr = data[ind]; // get the start colour that we will use tolerance against.
sg = data[ind + 1];
sb = data[ind + 2];
sa = data[ind + 3];
}
stack = []; // paint stack to find new pixels to paint
lookLeft = false; // test directions
lookRight = false;
stackPos = 0;
stack[stackPos++] = x;
stack[stackPos++] = y;
while (stackPos > 0) { // do while pixels on the stack
y = stack[--stackPos]; // get the pixel y
x = stack[--stackPos]; // get the pixel x
ind = x + y * w;
while (checkColour(ind - w)) { // find the top most pixel within tollerance;
y -= 1;
ind -= w;
}
//checkTop left and right if allowing diagonal painting
if(diagonal && y > 0){
if(x > 0 && !checkColour(ind - 1) && checkColour(ind - w - 1)){
stack[stackPos++] = x - 1;
stack[stackPos++] = y - 1;
}
if(x < w - 1 && !checkColour(ind + 1) && checkColour(ind - w + 1)){
stack[stackPos++] = x + 1;
stack[stackPos++] = y - 1;
}
}
lookLeft = false; // set look directions
lookRight = false; // only look is a pixel left or right was blocked
while (checkColour(ind) && y < h) { // move down till no more room
if(toleranceFade > 0 && differance >= tolerance-toleranceFade){
painted[ind] = 255 - (((differance - (tolerance - toleranceFade)) / toleranceFade) * 255);
painted[ind] = painted[ind] === 0 ? 1 : painted[ind]; // min value must be 1
}else{
painted[ind] = 255;
}
if(extentOnly){
extent.left = x < extent.left ? x : extent.left; // Faster than using Math.min
extent.right = x > extent.right ? x : extent.right; // Faster than using Math.min
extent.top = y < extent.top ? y : extent.top; // Faster than using Math.max
extent.bottom = y > extent.bottom ? y : extent.bottom; // Faster than using Math.max
}
if (checkColour(ind - 1) && x > 0) { // check left is blocked
if (!lookLeft) {
stack[stackPos++] = x - 1;
stack[stackPos++] = y;
lookLeft = true;
}
} else if (lookLeft) {
lookLeft = false;
}
if (checkColour(ind + 1) && x < w -1) { // check right is blocked
if (!lookRight) {
stack[stackPos++] = x + 1;
stack[stackPos++] = y;
lookRight = true;
}
} else if (lookRight) {
lookRight = false;
}
y += 1; // move down one pixel
ind += w;
}
if(diagonal && y < h){ // check for diagonal areas and push them to be painted
if(checkColour(ind - 1) && !lookLeft && x > 0){
stack[stackPos++] = x - 1;
stack[stackPos++] = y;
}
if(checkColour(ind + 1) && !lookRight && x < w - 1){
stack[stackPos++] = x + 1;
stack[stackPos++] = y;
}
}
}
if(extentOnly){
extent.top += area.y;
extent.bottom += area.y;
extent.left += area.x;
extent.right += area.x;
return true;
}
canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
ctx = canvas.getContext("2d");
ctx.fillStyle = context2D.fillStyle;
ctx.fillRect(0, 0, w, h);
colImgDat = ctx.getImageData(0, 0, w, h);
if(copyPixels){
i = 0;
ind = 0;
if(cutPixels){
while(i < painted.length){
if(painted[i] > 0){
colImgDat.data[ind] = data[ind];
colImgDat.data[ind + 1] = data[ind + 1];
colImgDat.data[ind + 2] = data[ind + 2];
colImgDat.data[ind + 3] = data[ind + 3] * (painted[i] / 255);
data[ind + 3] = 255 - painted[i];
}else{
colImgDat.data[ind + 3] = 0;
}
i ++;
ind += 4;
}
context2D.putImageData(imgData, area.x, area.y);
}else{
while(i < painted.length){
if(painted[i] > 0){
colImgDat.data[ind] = data[ind];
colImgDat.data[ind + 1] = data[ind + 1];
colImgDat.data[ind + 2] = data[ind + 2];
colImgDat.data[ind + 3] = data[ind + 3] * (painted[i] / 255);
}else{
colImgDat.data[ind + 3] = 0;
}
i ++;
ind += 4;
}
}
ctx.putImageData(colImgDat,0,0);
return true;
}else{
i = 0;
ind = 3;
while(i < painted.length){
colImgDat.data[ind] = painted[i];
i ++;
ind += 4;
}
ctx.putImageData(colImgDat,0,0);
}
if(! keepMask){
context2D.drawImage(canvas,area.x,area.y,w,h);
}
return true;
}
return {
fill : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
ctx = undefined;
canvas = undefined;
},
getMask : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
keepMask = true;
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
ctx = undefined;
keepMask = false;
return canvas;
},
getExtent : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
extentOnly = true;
if(floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade)){
extentOnly = false;
return {
top : extent.top,
left : extent.left,
right : extent.right,
bottom : extent.bottom,
width : extent.right - extent.left,
height : extent.bottom - extent.top,
}
}
extentOnly = false;
return null;
},
cut : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
cutPixels = true;
copyPixels = true;
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
cutPixels = false;
copyPixels = false;
ctx = undefined;
return canvas;
},
copy : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
cutPixels = false;
copyPixels = true;
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
copyPixels = false;
ctx = undefined;
return canvas;
},
setCompareValues : function(R,G,B,A){
if(R === null && G === null && B === null && A === null){
return;
}
red = R;
green = G;
blue = B;
alpha = A;
useBoundingColor = false;
useCompareColor = true;
},
setBoundingColor : function(R,G,B,A){
red = R;
green = G;
blue = B;
alpha = A;
useCompareColor = false;
useBoundingColor = true;
}
}
}());
showExample();
Red floodFill.fill(40,100,190,ctx,null,null,90) tolerance 190, tolerance fade 90<br>Blue floodFill.fill(100,100,1,ctx) tolerance 1.<br>
For more info see readme at Github FloodFill2D
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.