2d platformer arrow follows me? - javascript

I am creating a web application canvas game with javascript.
At the moment I am trying to have one of the enemies shoot an arrow at my character. I want to eventually do parabolic projection, but for now I want just a simple line between two points.
Right now i have kinda accomplished this, but when my character moves back and forth the arrows being shot follow him. I know that this is because when my character moves, the canvasX and Y for the enemy gets adjusted, and thus it updates the arrow, what I don't know is how to fix it.
function SkeletonArcher(game, x, y) {
this.ctx = game.ctx;
this.name = "skeletonArcher";
this.animationAttackLeft = new Animation(this, attackLeftAnimationSpriteSheet, 288, 192, 4, 0.05, 16, true, 0.5);
var currentCharacter = game.getCurrentCharacter();
this.x = x;
this.y = y;
this.width = 2 * 16;
this.height = 4 * 16;
this.canvasX = x;
this.canvasY = y;
this.attacking = false;
}
SkeletonArcher.prototype.update = function() {
var gameEngine = this.game;
var currentCharacter = gameEngine.getCurrentCharacter();
if (this.animationAttackLeft.currentFrame() === 11) {
var startTime = this.game.timer.gameTime;
var newArrow = new Arrow(this.game, this.canvasX, this.canvasY, startTime);
this.game.addEntity(newArrow);
}
// here is where the enemy gets adjusted on the canvas
if (gameEngine.keyMap["KeyD"] && !currentCharacter.collidedRight) {
this.canvasX -= 3;
} else if (gameEngine.keyMap["KeyA"] && !currentCharacter.collidedLeft) {
this.canvasX += 3;
}
if (currentCharacter.canvasX >= this.canvasX - 800 && !this.attacking) {
this.animationState = "attackLeft";
this.attacking = true;
}
if (currentCharacter.canvasX <= this.canvasX - 800) {
this.attacking = false;
this.animationState = "idleLeft";
}
};
function Arrow(game, archerX, archerY, startTime) {
this.startTime = startTime;
this.game = game;
this.ctx = game.ctx;
this.name = "arrow";
var currentCharacter = game.getCurrentCharacter();
this.x = archerX;
this.y = archerY;
this.width = 2 * 16;
this.height = 4 * 16;
this.canvasX = archerX;
this.canvasY = archerY;
this.y1 = this.canvasY;
this.y2 = currentCharacter.canvasY;
this.x1 = this.canvasX;
this.x2 = currentCharacter.canvasX;
}
Arrow.prototype.update = function() {
var currentCharacter = this.game.getCurrentCharacter();
var gameEngine = this.game;
var timeSince = this.game.timer.gameTime - this.startTime;
this.canvasY = (1 - timeSince) * this.y1 + (timeSince * this.y2);
this.canvasX = (1 - timeSince) * this.x1 + (timeSince * this.x2);
};
Arrow.prototype.draw = function(first_argument) {
this.ctx.fillStyle = "#000000";
this.ctx.fillRect(this.canvasX, this.canvasY, 5, 3);
};

Related

How can I reverse the direction of this square after it reaches a certain value?

I'm trying to create an idle animation where the red rectangle moves back and forth slightly in a loop. For some reason once it reaches the specified threshhold instead of proceeding to move in the opposite direction, it just stops.
What did I do wrong?
<canvas id="myCanvas" width="1500" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// Spaceship structure
var shipWidth = 250;
var shipHeight = 100;
// Canvas parameters
var cWidth = canvas.width;
var cHeight = canvas.height;
// Positioning variables
var centerWidthPosition = (cWidth / 2) - (shipWidth / 2);
var centerHeightPosition = (cHeight / 2) - (shipHeight / 2);
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
function drawShip(){
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.fillStyle = "#FF0000";
ctx.fillRect(centerWidthPosition,centerHeightPosition,shipWidth,shipHeight);
centerWidthPosition--;
if (centerWidthPosition < 400){
++centerWidthPosition;
}
requestAnimationFrame(drawShip);
}
drawShip();
</script>
#TheAmberlamps explained why it's doing that. Here I offer you a solution to achieve what I believe you are trying to do.
Use a velocity variable that changes magnitude. X position always increases by velocity value. Velocity changes directions at screen edges.
// use a velocity variable
var xspeed = 1;
// always increase by velocity
centerWidthPosition += xspeed;
// screen edges are 0 and 400 in this example
if (centerWidthPosition > 400 || centerWidthPosition < 0){
xspeed *= -1; // change velocity direction
}
I added another condition in your if that causes the object to bounce back and forth. Remove the selection after || if you don't want it doing that.
Your function is caught in a loop; once centerWidthPosition reaches 399 your conditional makes it increment back up to 400, and then it decrements back to 399.
here is another one as a brain teaser - how would go by making this animation bounce in the loop - basically turn text into particles and then reverse back to text and reverse back to particles and back to text and so on and on and on infinitely:
var random = Math.random;
window.onresize = function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.onresize();
var ctx = canvas.getContext('2d');
ctx.font = 'bold 50px "somefont"';
ctx.textBaseline = 'center';
ctx.fillStyle = 'rgba(255,255,255,1)';
var _particles = [];
var particlesLength = 0;
var currentText = "SOMETEXT";
var createParticle = function createParticle(x, y) {_particles.push(new Particle(x, y));};
var checkAlpha = function checkAlpha(pixels, i) {return pixels[i * 4 + 3] > 0;};
var createParticles = function createParticles() {
var textSize = ctx.measureText(currentText);
ctx.fillText(currentText,Math.round((canvas.width / 2) - (textSize.width / 2)),Math.round(canvas.height / 2));
var imageData = ctx.getImageData(1, 1, canvas.width, canvas.height);
var pixels = imageData.data;
var dataLength = imageData.width * imageData.height;
for (var i = 0; i < dataLength; i++) {
var currentRow = Math.floor(i / imageData.width);
var currentColumn = i - Math.floor(i / imageData.height);
if (currentRow % 2 || currentColumn % 2) continue;
if (checkAlpha(pixels, i)) {
var cy = ~~(i / imageData.width);
var cx = ~~(i - (cy * imageData.width));
createParticle(cx, cy);
}}
particlesLength = _particles.length;
};
var Point = function Point(x, y) {
this.set(x, y);
};
Point.prototype = {
set: function (x, y) {
x = x || 0;
y = y || x || 0;
this._sX = x;
this._sY = y;
this.reset();
},
add: function (point) {
this.x += point.x;
this.y += point.y;
},
multiply: function (point) {
this.x *= point.x;
this.y *= point.y;
},
reset: function () {
this.x = this._sX;
this.y = this._sY;
return this;
},
};
var FRICT = new Point(0.98);//set to 0 if no flying needed
var Particle = function Particle(x, y) {
this.startPos = new Point(x, y);
this.v = new Point();
this.a = new Point();
this.reset();
};
Particle.prototype = {
reset: function () {
this.x = this.startPos.x;
this.y = this.startPos.y;
this.life = Math.round(random() * 300);
this.isActive = true;
this.v.reset();
this.a.reset();
},
tick: function () {
if (!this.isActive) return;
this.physics();
this.checkLife();
this.draw();
return this.isActive;
},
checkLife: function () {
this.life -= 1;
this.isActive = !(this.life < 1);
},
draw: function () {
ctx.fillRect(this.x, this.y, 1, 1);
},
physics: function () {
if (performance.now()<nextTime) return;
this.a.x = (random() - 0.5) * 0.8;
this.a.y = (random() - 0.5) * 0.8;
this.v.add(this.a);
this.v.multiply(FRICT);
this.x += this.v.x;
this.y += this.v.y;
this.x = Math.round(this.x * 10) / 10;
this.y = Math.round(this.y * 10) / 10;
}
};
var nextTime = performance.now()+3000;
createParticles();
function clearCanvas() {
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
(function clearLoop() {
clearCanvas();
requestAnimationFrame(clearLoop);
})();
(function animLoop(time) {
ctx.fillStyle = 'rgba(255,255,255,1)';
var isAlive = true;
for (var i = 0; i < particlesLength; i++) {
if (_particles[i].tick()) isAlive = true;
}
requestAnimationFrame(animLoop);
})();
function resetParticles() {
for (var i = 0; i < particlesLength; i++) {
_particles[i].reset();
}}

HTML5 Canvas | Bouncing Balls | Looping through an image array and placing different background images on each ball, centered

I am having a bit of a nightmare applying different background images onto balls that bounce around in the canvas under the effect of gravity and the bounds of my canvas, eventually settling down and stacking at the bottom.
I have created an image array, that I am trying to loop through and create a ball for each image, where the image becomes a centred background.
I can centre the image, but this makes the balls appear to leave the bounds of my canvas. So have reverted that change.
I am unable to get the background image to be different from the previous ball. Where the image shown on all balls is the last image in the array. However the number of balls created does reflect the number of images in the array.
Here is a link to a codepen:
https://codepen.io/jason-is-my-name/pen/BbNRXB
html, body{
width:100%;
height:100%;
margin: 0;
padding: 0;
background: #333333;
}
*{
margin: 0;
padding: 0;
}
.container {
width: 410px;
height: 540px;
}
#ball-stage{
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<canvas id="ball-stage" ></canvas>
</div>
<script>
/*
/*
* Created by frontside.com.au
* Amended by Jason
*/
$(document).ready(function () {
$.ajaxSetup({
cache: true
});
var url1 = "https://code.createjs.com/easeljs-0.6.0.min.js";
$.getScript(url1, function () {
new App();
})
});
function App() {
var self = this;
self.running = false;
self.initialized = false;
var stageClicked = false;
var stage, canvas;
var canvasWidth = 410;
var canvasHeight = 540;
var bounce = -0.75;
var balls = [];
var _gravityY = 1;
var _gravityX = 0;
var FPS = 30;
var infoText, detailsText;
var ballsInitalized = false;
var iOS = navigator.userAgent.match(/(iPod|iPhone|iPad)/);
self.initialize = function () {
toggleListeners(true);
self.initCanvas();
self.initGame();
};
var toggleListeners = function (enable) {
if (!enable) return;
};
self.refresh = function () {}
self.initCanvas = function () {
canvas = $("#ball-stage").get(0);
stage = new createjs.Stage(canvas);
window.addEventListener('resize', onStageResize, false);
onStageResize();
createjs.Touch.enable(stage);
createjs.Ticker.addListener(tick);
createjs.Ticker.setFPS(FPS);
self.initialized = true;
}
self.initGame = function () {
initBalls(canvasWidth, canvasHeight);
}
var onStageResize = function () {
stage.canvas.width = canvasWidth;
stage.canvas.height = canvasHeight;
}
var initBalls = function (stageX, stageY) {
var imagesArray = ["img-1.png","img-2.png","img-3.png","img-4.png","img-5.png","img-6.png","img-7.png","img-8.png"];
for (var i = 0; i < imagesArray.length; i++) {
console.log(i);
var imageArray = imagesArray[i];
console.log(imageArray);
setTimeout(function () {
var arrayImage = new Image();
console.log(arrayImage);
arrayImage.onload = function(){
addBall(arrayImage, stageX / 2, 0);
}
arrayImage.src = imageArray;
}, i * 1000);
}
}
var addBall = function (img, x, y) {
console.log(img);
var shape = new createjs.Shape();
shape.id = balls.length;
shape.radius = 51.25;
shape.mass = shape.radius;
shape.x = x;
shape.y = y;
shape.vx = rand(-3, 3);
shape.vy = rand(-3, 3);
var image = new Image();
image.src = img;
shape.graphics.beginBitmapFill(img,'repeat').drawCircle(0, 0, shape.radius);
stage.addChild(shape);
balls.push(shape);
}
var numBalls = function () {
return balls.length;
}
var tick = function () {
balls.forEach(move);
for (var ballA, i = 0, len = numBalls() - 1; i < len; i++) {
ballA = balls[i];
for (var ballB, j = i + 1; j < numBalls(); j++) {
ballB = balls[j];
checkCollision(ballA, ballB);
}
}
stage.update();
}
var rotate = function (x, y, sin, cos, reverse) {
return {
x: (reverse) ? (x * cos + y * sin) : (x * cos - y * sin),
y: (reverse) ? (y * cos - x * sin) : (y * cos + x * sin)
};
}
var checkCollision = function (ball0, ball1) {
var dx = ball1.x - ball0.x,
dy = ball1.y - ball0.y,
dist = Math.sqrt(dx * dx + dy * dy);
//collision handling code here
if (dist < ball0.radius + ball1.radius) {
//calculate angle, sine, and cosine
var angle = Math.atan2(dy, dx),
sin = Math.sin(angle),
cos = Math.cos(angle),
//rotate ball0's position
pos0 = {
x: 0,
y: 0
}, //point
//rotate ball1's position
pos1 = rotate(dx, dy, sin, cos, true),
//rotate ball0's velocity
vel0 = rotate(ball0.vx, ball0.vy, sin, cos, true),
//rotate ball1's velocity
vel1 = rotate(ball1.vx, ball1.vy, sin, cos, true),
//collision reaction
vxTotal = vel0.x - vel1.x;
vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) /
(ball0.mass + ball1.mass);
vel1.x = vxTotal + vel0.x;
//update position - to avoid objects becoming stuck together
var absV = Math.abs(vel0.x) + Math.abs(vel1.x),
overlap = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x);
pos0.x += vel0.x / absV * overlap;
pos1.x += vel1.x / absV * overlap;
//rotate positions back
var pos0F = rotate(pos0.x, pos0.y, sin, cos, false),
pos1F = rotate(pos1.x, pos1.y, sin, cos, false);
//adjust positions to actual screen positions
// ball1.x = ball0.x + pos1F.x;
setBallX(ball1, ball0.x + pos1F.x)
//ball1.y = ball0.y + pos1F.y;
setBallY(ball1, ball0.y + pos1F.y)
// ball0.x = ball0.x + pos0F.x;
setBallX(ball0, ball0.x + pos0F.x)
// ball0.y = ball0.y + pos0F.y;
setBallY(ball0, ball0.y + pos0F.y)
//rotate velocities back
var vel0F = rotate(vel0.x, vel0.y, sin, cos, false),
vel1F = rotate(vel1.x, vel1.y, sin, cos, false);
ball0.vx = vel0F.x;
ball0.vy = vel0F.y;
ball1.vx = vel1F.x;
ball1.vy = vel1F.y;
}
}
var checkWalls = function (ball) {
if (ball.x + ball.radius > canvas.width) {
// ball.x = canvas.width - ball.radius;
setBallX(ball, canvas.width - ball.radius)
ball.vx *= bounce;
} else
if (ball.x - ball.radius < 0) {
// ball.x = ball.radius;
setBallX(ball, ball.radius)
ball.vx *= bounce;
}
if (ball.y + ball.radius > canvas.height) {
// ball.y = canvas.height - ball.radius;
setBallY(ball, canvas.height - ball.radius)
ball.vy *= bounce;
} else
if (ball.y - ball.radius < 0) {
//ball.y = ball.radius;
setBallY(ball, ball.radius)
ball.vy *= bounce;
}
}
var move = function (ball) {
ball.vy += _gravityY;
ball.vx += _gravityX;
setBallX(ball, ball.x + ball.vx)
setBallY(ball, ball.y + ball.vy)
checkWalls(ball);
}
var setBallX = function (ball, x) {
if (isNaN(ball.pointerID)) {
ball.x = x
}
}
var setBallY = function (ball, y) {
if (isNaN(ball.pointerID)) {
ball.y = y
}
}
var rand = function (min, max) {
return Math.random() * (max - min) + min;
return (Math.random() * max) + min;
}
self.initialize();
return self;
}
window.log = function f() {
log.history = log.history || [];
log.history.push(arguments);
if (this.console) {
var args = arguments,
newarr;
args.callee = args.callee.caller;
newarr = [].slice.call(args);
if (typeof console.log === 'object') log.apply.call(console.log, console, newarr);
else console.log.apply(console, newarr);
}
};
(function (a) {
function b() {}
for (var c = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","), d; !!(d = c.pop());) {
a[d] = a[d] || b;
}
})
(function () {
try {
console.log();
return window.console;
} catch (a) {
return (window.console = {});
}
}());
</script>
I have been trapped at this point in the code for about a week now and could really do with some genius' help!
Aims:
Add balls equivalent to the length of the image array.
Each ball with have its respective image as a centred background.
The balls will not leave the bounds of the canvas.
Relevant code:
initBalls()
addBall()
Thanks, Jason.
https://codepen.io/prtjohanson/pen/vPKQBg
What needed to be changed:
for (let i = 0; i < imagesArray.length; i++) {
console.log(i);
const imageArray = imagesArray[i];
setTimeout(function() {
var arrayImage = new Image();
arrayImage.onload = function() {
addBall(arrayImage, stageX / 2, 0);
};
arrayImage.src = imageArray;
}, i * 1000);
}
By the time the setTimeout callback fired, your for loop had already finished and with a var declaration, the for loop iteration has no scope of its own, with a let, each iteration has its own scope like a function does.
If it must run on browsers that don't have let or const keywords, let me know, I can provide a solution for them as well
This will work in IE11 and other browsers that don't support ES6
for (var i = 0; i < imagesArray.length; i++) {
(function(imageArray) {
setTimeout(function() {
var arrayImage = new Image();
arrayImage.onload = function() {
console.log('Add'+i);
addBall(arrayImage, stageX / 2, 0);
};
arrayImage.src = imageArray;
}, i * 1000);
})(imagesArray[i]);
}
To get the images centered, without them going out of the bounds of canvas, use a 2D transform on the beginBitmapFill operation:
var transform = new createjs.Matrix2D();
transform.appendTransform(-shape.radius, -shape.radius, 1, 1, 0);
shape.graphics.beginBitmapFill(img, "repeat", transform).drawCircle(0, 0, shape.radius);
As for there being not as many balls as there are URLs in the array, it seems that sometimes the image source URL prompts "I am not a robot" captcha. If replaced with URL-s under your control, the issue should go away.

How to move character in two speeds without one affecting the other?

OK, so this is a little hard to explain. I'm making a canvas-based point-and-click 2d game. You can look around (move the environment) by dragging the mouse horizontally across the screen. And move the character by clicking where you want him to go. Kinda like This War of Mine. Here's a simplified version of what I got...
MOUSE ACTIONS:
var held, mouseX, mouseXInitial;
window.addEventListener('mousedown',function(e){
held = true;
mouseXInitial = mouseX;
});
window.addEventListener('mouseup',function(e){
held = false;
});
window.addEventListener('mousemove',function(e){
mouseX = e.clientX;
});
mouseEvents();
LOOKING AROUND (dragging across the screen to look around the environment):
var sharedParentObject = {
scrolledAmount: null,
scrolling: function(){
if (held){
this.scrolledAmount = mouseX - mouseXInitial;
this.x = this.xInitial + this.scrolledAmount;
}
},
inputShared: function(){
var that = this;
window.addEventListener('mousedown',function(e){
that.xInitial = that.x;
});
window.addEventListener('mousemove',function(e){
that.scrolling();
});
}
}
MOVING THE CHARACTER:
function Character(){
this.speed = 2;
this.target = null;
this.input = function(){
var that = this;
window.addEventListener('mouseup',function(e){
that.target = that.mouseXInitial;
that.target += that.scrolledAmount;
});
}
this.update = function(){
if (this.target){
//moving right
if (this.x + this.w/2 < this.target){
this.x += this.speed;
}
//moving left
if (this.x + this.w/2 > this.target){
this.x -= this.speed;
}
}
}
}
Character.prototype = Object.create(sharedParentObject);
This works but the problem is that once I start dragging across the screen to look around, while the character is already walking, it gets all weird and jittery. I understand why this is happening. The character's x is getting changed in both the character class and the parent class at the same time. Is there a way to have it so the character can keep walking towards the target, while still getting moved as I scroll the environment? Kinda doing both, without one affecting the other..
All you need to do is track the distance the mouse have moved between the onmousedown and onmouseup events. If the distance is very small, then the user has clicked on one spot, if the distance is larger then they are trying to pan the scene.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
display: block;
margin-top: 20px;
margin-left: auto;
margin-right: auto;
border: solid 1px white;
border-radius: 10px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
// ES5 Friendly class functions
// (Supports overloading & single inheritance)
var _class = (function() {
"use strict";
/*
All a class is in JS is a function that
acts as a constructor and an object (prototype)
that holds the properties shared across all
instances (static vars, constants & functions).
*/
function _class(constructor,prototype) {
prototype.base = null;
prototype.super = constructor;
constructor.prototype = prototype;
return constructor;
}
_class.extends = function(base,constructor,prototype) {
for (var property in base.prototype) {
if (!prototype[property]) {
prototype[property] = base.prototype[property];
}
}
function bundledConstructor() {
base.apply(this,arguments);
constructor.apply(this,arguments);
}
prototype.base = base;
prototype.super = bundledConstructor;
bundledConstructor.prototype = prototype;
return bundledConstructor;
}
return _class;
})();
void function() {
"use strict";
// Classes
// Encapsulate canvas element
var Viewport = _class(
function(canvasID,width,height,bgColour) {
this.canvas = document.getElementById(canvasID) || null;
this.width = width || 1.0;
this.height = height || 1.0;
this.bgColour = bgColour || "gray";
this.hWidth = this.width >> 1;
this.hHeight = this.height >> 1;
this.canvas.width = this.width;
this.canvas.height = this.height;
this.ctx = this.canvas.getContext("2d");
this.ctx.translate(this.hWidth,this.hHeight);
var bounds = this.canvas.getBoundingClientRect();
this.leftMargin = bounds.left;
this.topMargin = bounds.top;
},{
clear: function() {
var ctx = this.ctx;
ctx.fillStyle = this.bgColour;
ctx.fillRect(-this.hWidth,-this.hHeight,this.width,this.height);
}
}
);
// This is a class used to encapsulate
// the scene's panning/zooming.
var Camera = _class(
// Constructor Function
function(viewport,x,y) {
this.viewport = viewport || null;
this.x = x || 0.0;
this.y = y || 0.0;
this.scale = 1.0;
this.invScale = 1.0 / this.scale;
this.allowUserInput = false;
this.mouseDown = false;
this.mouseLastX = 0.0;
this.mouseLastY = 0.0;
viewport.canvas.addEventListener("wheel",this.onwheel.bind(this));
viewport.canvas.addEventListener("mousedown",this.onmousedown.bind(this));
viewport.canvas.addEventListener("mouseup",this.onmouseup.bind(this));
viewport.canvas.addEventListener("mousemove",this.onmousemove.bind(this));
},
// Prototype (constant values & functions go here)
{
scaleCoordinates: function(x,y) {
return [
(x - this.viewport.hWidth) * this.invScale + this.x,
(y - this.viewport.hHeight) * this.invScale + this.y
];
},
scaleDimensions: function(width,height) {
return [
width * this.invScale,
height * this.invScale
];
},
pan: function(deltaX,deltaY) {
this.x += deltaX;
this.y += deltaY;
},
zoom: function(deltaScale) {
this.scale += deltaScale;
this.scale = Math.max(0.0,this.scale);
this.invScale = 1.0 / this.scale;
},
onwheel: function(e) {
if (this.allowUserInput) {
var deltaY = -Math.sign(e.deltaY) * (e.deltaY / e.deltaY) * 0.25;
this.zoom(deltaY);
}
},
onmousedown: function(e) {
this.mouseDown = true;
[
this.mouseLastX,
this.mouseLastY
] = this.scaleCoordinates(
e.clientX - this.viewport.leftMargin,
e.clientY - this.viewport.topMargin
);
},
onmouseup: function(e) {
this.mouseDown = false;
},
onmousemove: function(e) {
if (this.allowUserInput && this.mouseDown) {
var [
mouseX,
mouseY
] = this.scaleCoordinates(
e.clientX - this.viewport.leftMargin,
e.clientY - this.viewport.topMargin
);
this.pan(
this.mouseLastX - mouseX,
this.mouseLastY - mouseY
);
}
}
}
);
// Contains basic behaviour all game objects will have
var GameObject = _class(
function(x,y,width,height,colour) {
this.x = x || 0.0;
this.y = y || 0.0;
this.width = width || 1.0;
this.height = height || 1.0;
this.colour = colour || "darkblue";
this.dx = 0.0;
this.dy = 0.0;
this._draw_x = 0.0;
this._draw_y = 0.0;
this._draw_width = 0.0;
this._draw_height = 0.0;
},{
updateDrawParameters: function(camera) {
this._draw_width = this.width * camera.scale;
this._draw_height = this.height * camera.scale;
this._draw_x = ((this.x - camera.x) * camera.scale) - this._draw_width * 0.5;
this._draw_y = ((this.y - camera.y) * camera.scale) - this._draw_height * 0.5;
},
tick: function() {
},
render: function(viewport) {
var ctx = viewport.ctx;
ctx.fillStyle = this.colour;
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.rect(this._draw_x,this._draw_y,this._draw_width,this._draw_height);
ctx.fill();
ctx.stroke();
}
}
);
// A more specialized type of game object that
// can move to a specific location
var MoveAgent = _class.extends(GameObject,
function(x,y,width,height,colour) {
this.currentState = this.STATE_IDLE;
this.targetX = 0.0;
this.targetY = 0.0;
},{
STATE_IDLE: 1000,
STATE_MOVING_TO_TARGET: 1001,
MOVE_SPEED: 1.0,
GOAL_TOLERANCE: 5.0, // How close is 'good enough' to the target
moveTo: function(x,y) {
this.targetX = x || 0.0;
this.targetY = y || 0.0;
this.currentState = this.STATE_MOVING_TO_TARGET;
},
tick: function() {
switch(this.currentState) {
case this.STATE_IDLE:
break;
case this.STATE_MOVING_TO_TARGET:
var x = this.targetX - this.x;
var y = this.targetY - this.y;
var l = x * x + y * y;
if (l < this.GOAL_TOLERANCE * this.GOAL_TOLERANCE) {
this.currentState = this.STATE_IDLE;
} else {
l = 1.0 / Math.sqrt(l);
this.dx = x * l * this.MOVE_SPEED;
this.dy = y * l * this.MOVE_SPEED;
this.x += this.dx;
this.y += this.dy;
}
break;
}
}
}
);
var ControlledMoveAgent = _class.extends(MoveAgent,
function(x,y,width,height,colour,camera) {
this.camera = camera || null;
this.mouseDown = false;
this.mouseX = 0.0;
this.mouseY = 0.0;
viewport.canvas.addEventListener("mousedown",this.onmousedown.bind(this));
viewport.canvas.addEventListener("mouseup",this.onmouseup.bind(this));
viewport.canvas.addEventListener("mousemove",this.onmousemove.bind(this));
},{
MOVE_TOLLERANCE: 5.0,
onmousedown: function(e) {
if (e.button === 0) {
this.mouseDown = true;
this.mouseX = e.clientX;
this.mouseY = e.clientY;
}
},
onmouseup: function(e) {
if (e.button === 0 && this.mouseDown) {
this.mouseDown = false;
var x = e.clientX - this.camera.viewport.leftMargin;
var y = e.clientY - this.camera.viewport.topMargin;
[x,y] = this.camera.scaleCoordinates(x,y);
this.moveTo(x,y);
}
},
onmousemove: function(e) {
if (this.mouseDown) {
var x = this.mouseX - e.clientX;
var y = this.mouseY - e.clientY;
var l = Math.sqrt(x * x + y * y);
if (l > this.MOVE_TOLLERANCE) {
this.mouseDown = false;
}
}
}
}
);
// Vars
var camera = null;
var viewport = null;
var scenery = [];
var character = null;
// Functions
function loop() {
// Tick
for (var i = 0; i < scenery.length; ++i) {
scenery[i].updateDrawParameters(camera);
}
character.tick();
character.updateDrawParameters(camera);
// Render
viewport.clear();
for (var i = 0; i < scenery.length; ++i) {
scenery[i].render(viewport);
}
character.render(viewport);
//
requestAnimationFrame(loop);
}
// Entry Point
onload = function() {
viewport = new Viewport("canvas",180,160,"gray");
camera = new Camera(viewport,0,0);
camera.allowUserInput = true;
camera.zoom(0.25);
for (var i = 0; i < 10; ++i) {
scenery[i] = new GameObject(
180 * Math.random() - 90,
160 * Math.random() - 80,
10 + Math.random() * 10,
10 + Math.random() * 10,
"#" + ((Math.random() * 255) | 0).toString(16)
+ ((Math.random() * 255) | 0).toString(16)
+ ((Math.random() * 255) | 0).toString(16)
);
}
character = new ControlledMoveAgent(0,0,10,10,"darkred",camera);
loop();
}
}()
</script>
</body>
</html>

How to change the zoom factor to only zoom OUT on Ken Burns effect?

I am helping a friend with a website, and he is using the Ken Burns Effect with Javascript and Canvas from this site https://www.willmcgugan.com/blog/tech/post/ken-burns-effect-with-javascript-and-canvas/
for a slide-show. It works perfectly, but he would like to change the zoom effect to where all of the images zoom OUT, instead of alternating between zooming in and out.
After about a week of "scrambling" the code unsuccessfully, he posted a question about it on the site. The reply he received was (quote) "That's definitely possible, with a few tweaks of the code. Sorry, no time to give you guidance at the moment, but it shouldn't be all that difficult" (end quote).
I can't seem to figure it out either, so I'm hoping that someone here may be of help. Below is the code as posted on the willmcgugan.com website. Any help on how to change the zoom effect would be greatly appreciated.
(function($){
$.fn.kenburns = function(options) {
var $canvas = $(this);
var ctx = this[0].getContext('2d');
var start_time = null;
var width = $canvas.width();
var height = $canvas.height();
var image_paths = options.images;
var display_time = options.display_time || 7000;
var fade_time = Math.min(display_time / 2, options.fade_time || 1000);
var solid_time = display_time - (fade_time * 2);
var fade_ratio = fade_time - display_time
var frames_per_second = options.frames_per_second || 30;
var frame_time = (1 / frames_per_second) * 1000;
var zoom_level = 1 / (options.zoom || 2);
var clear_color = options.background_color || '#000000';
var images = [];
$(image_paths).each(function(i, image_path){
images.push({path:image_path,
initialized:false,
loaded:false});
});
function get_time() {
var d = new Date();
return d.getTime() - start_time;
}
function interpolate_point(x1, y1, x2, y2, i) {
// Finds a point between two other points
return {x: x1 + (x2 - x1) * i,
y: y1 + (y2 - y1) * i}
}
function interpolate_rect(r1, r2, i) {
// Blend one rect in to another
var p1 = interpolate_point(r1[0], r1[1], r2[0], r2[1], i);
var p2 = interpolate_point(r1[2], r1[3], r2[2], r2[3], i);
return [p1.x, p1.y, p2.x, p2.y];
}
function scale_rect(r, scale) {
// Scale a rect around its center
var w = r[2] - r[0];
var h = r[3] - r[1];
var cx = (r[2] + r[0]) / 2;
var cy = (r[3] + r[1]) / 2;
var scalew = w * scale;
var scaleh = h * scale;
return [cx - scalew/2,
cy - scaleh/2,
cx + scalew/2,
cy + scaleh/2];
}
function fit(src_w, src_h, dst_w, dst_h) {
// Finds the best-fit rect so that the destination can be covered
var src_a = src_w / src_h;
var dst_a = dst_w / dst_h;
var w = src_h * dst_a;
var h = src_h;
if (w > src_w)
{
var w = src_w;
var h = src_w / dst_a;
}
var x = (src_w - w) / 2;
var y = (src_h - h) / 2;
return [x, y, x+w, y+h];
}
function get_image_info(image_index, load_callback) {
// Gets information structure for a given index
// Also loads the image asynchronously, if required
var image_info = images[image_index];
if (!image_info.initialized) {
var image = new Image();
image_info.image = image;
image_info.loaded = false;
image.onload = function(){
image_info.loaded = true;
var iw = image.width;
var ih = image.height;
var r1 = fit(iw, ih, width, height);;
var r2 = scale_rect(r1, zoom_level);
var align_x = Math.floor(Math.random() * 3) - 1;
var align_y = Math.floor(Math.random() * 3) - 1;
align_x /= 2;
align_y /= 2;
var x = r2[0];
r2[0] += x * align_x;
r2[2] += x * align_x;
var y = r2[1];
r2[1] += y * align_y;
r2[3] += y * align_y;
if (image_index % 2) {
image_info.r1 = r1;
image_info.r2 = r2;
}
else {
image_info.r1 = r2;
image_info.r2 = r1;
}
if(load_callback) {
load_callback();
}
}
image_info.initialized = true;
image.src = image_info.path;
}
return image_info;
}
function render_image(image_index, anim, fade) {
// Renders a frame of the effect
if (anim > 1) {
return;
}
var image_info = get_image_info(image_index);
if (image_info.loaded) {
var r = interpolate_rect(image_info.r1, image_info.r2, anim);
var transparency = Math.min(1, fade);
if (transparency > 0) {
ctx.save();
ctx.globalAlpha = Math.min(1, transparency);
ctx.drawImage(image_info.image, r[0], r[1], r[2] - r[0], r[3] - r[1], 0, 0, width, height);
ctx.restore();
}
}
}
function clear() {
// Clear the canvas
ctx.save();
ctx.globalAlpha = 1;
ctx.fillStyle = clear_color;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore();
}
function update() {
// Render the next frame
var update_time = get_time();
var top_frame = Math.floor(update_time / (display_time - fade_time));
var frame_start_time = top_frame * (display_time - fade_time);
var time_passed = update_time - frame_start_time;
function wrap_index(i) {
return (i + images.length) % images.length;
}
if (time_passed < fade_time)
{
var bottom_frame = top_frame - 1;
var bottom_frame_start_time = frame_start_time - display_time + fade_time;
var bottom_time_passed = update_time - bottom_frame_start_time;
if (update_time < fade_time) {
clear();
} else {
render_image(wrap_index(bottom_frame), bottom_time_passed / display_time, 1);
}
}
render_image(wrap_index(top_frame), time_passed / display_time, time_passed / fade_time);
if (options.post_render_callback) {
options.post_render_callback($canvas, ctx);
}
// Pre-load the next image in the sequence, so it has loaded
// by the time we get to it
var preload_image = wrap_index(top_frame + 1);
get_image_info(preload_image);
}
// Pre-load the first two images then start a timer
get_image_info(0, function(){
get_image_info(1, function(){
start_time = get_time();
setInterval(update, frame_time);
})
});
};
})( jQuery );
If you want the simplest solution, I forked and modified your Codepen here:
http://codepen.io/jjwilly16/pen/NAovkp?editors=1010
I just removed a conditional that controls whether the zoom is moving out or in.
if (image_index % 2) {
image_info.r1 = r1;
image_info.r2 = r2;
}
else {
image_info.r1 = r2;
image_info.r2 = r1;
}
Changed to:
image_info.r1 = r2;
image_info.r2 = r1;
Now it only zooms out :)

Slowing movement of image on canvas

The Problem
I am creating a game that involves dodging projectiles. The player is in control of an image of a ship and I dont want the ship to move exactly together as this looks very unrealistic.
The Question
Is there a way to control how fast the image moves, how can i slow the movemnt of the image down?
The Code
var game = create_game();
game.init();
//music
var snd = new Audio("Menu.mp3");
snd.loop = true;
snd.play();
document.getElementById('mute').addEventListener('click', function (evt) {
if ( snd.muted ) {
snd.muted = false
evt.target.innerHTML = 'mute'
}
else {
snd.muted = true
evt.target.innerHTML = 'unmute'
}
})
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#ff6600";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 74) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 177) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#ff6600";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
}
return {
init: init
};
}
https://jsfiddle.net/a6nmy804/4/ (Broken)
Throttle the player's movement using a "timeout" countdown
Create a global var playerFreezeCountdown=0.
In mousemove change player.x only if playerFreezeCountdown<=0.
If playerFreezeCountdown>0 you don't change player.x.
If playerFreezeCountdown<=0 you both change player.x and also set playerFreezeCountdown to a desired "tick timeout" value: playerFreezeCountdown=5. This timeout will cause prevent the player from moving their ship until 5 ticks have passed.
In tick, always decrement playerFreezeCountdown--. This will indirectly allow a change to player.x after when playerFreezeCountdown is decremented to zero or below zero.

Categories

Resources