Image doesn't move on canvas - javascript

I'm new. I was trying to move an image but it just doesn't, I don't know where is the problem. I checked some topics but it didn't work., well here's my code:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var bgImage = new Image();
var player = new Image();
var x = 50;
var y = 50;
// We draw the background
bgImage.onload = function() {
context.drawImage(bgImage, 0, 0);
};
bgImage.src ='images/bg.png';
// We draw the player
player.onload = function(){
context.drawImage(player, x, y);
};
player.src = "images/player.png";
addEventListener("keypress", move,true);
function move(e){
if (e.keyCode == 39){
x += 2;
}
if (e.keyCode == 37){
x -= 4;
}
if (e.keyCode == 38){
y -= 4;
}
if (e.keyCode == 40){
y += 4;
}
}

You're missing the concept of the thing here, canvas is not dynamic by itself, it only provides you with the ability to draw static image frames. Therefore, the animation and interaction is possible, but it has to be entirely coded by yourself.
Any animation effect is given through the drawing of a continuous series of static frames over one another. Each frame will graphicaly represent a slight change in position/form/color of one or various objects within the scene.
It becomes clear you'd have to setup a loop chain where any change made to properties of an object in the scene would be reflected in the image frame which is currently on screen.
So it's not enough to change the value of your x and y variables, because they have been used only once in your code, to draw the first frame, then, they are used nowhere. There is nothing being drawn after the first frame. That one static image you created will remain forever on the screen, you can't expect animation out of a single static image.
Fix-wise, it's firstly necessary to setup a loading scheme for your image resources, since you need more than one. Because their loading is asynchronous AND out of order (they do not load respecting the order they were created).
After everything is loaded, you can choose to either fire a continuous loop, which will draw another frame regardless of wheter any change has actually been made to anything on the screen (this is the standard way for making animations, in more complex applications, it will allow you to keep control of animation time)
Or, since your code is simple enough, you could choose to draw another frame when, and only when, the position of your object has been changed. Something like this
addEventListener("keypress", move,true);
function move(e)
{
//... key handling stuff
redraw(); // Drawing the next, modified, frame
}
function redraw()
{
context.clearRect(0, 0, canvas.width, canvas.height)
context.drawImage(bgImage, 0, 0);
context.drawImage(player, x, y);
}

Related

How do I change a variable while using a function?

I'm coding on the p5.js Website Editor
So I'm trying to make a lot of rectangles that move when I press a specific key.
To make that I thought of making a function where I would put everything related to the rectangles moving, so that I don't have to rewrite a code to make them move every time. I want all of them to move the same way.
This is what I tried
function wall(x, y, sx, sy){
rect(x, y, sx, sy);
if(keyIsDown(65)){
return x+1;
}
}
wall(300, 300, 20, 30);
and just got a rectangle in the right coordinates but not moving when I press the "a" key
When you want to move things in p5.js, you need to use the draw() function. The draw() function runs the code inside of it repeatedly multiple times a second. You can use that to create an illusion of movement by updating the location of your object by a small amount every frame and then re-rendering the object.
I don't see you using the draw() function, so I'm guessing this is the first part of your problem.
The second part I see is that you are referring to "a lot of rectangles", but you are only making one.
Consider the code below for a quick solution (copy-paste it into the p5.js editor to see it in action).
let wallX = 200;
let wallY = 100;
let wallSpeed = 2;
let wallWidth = 20;
let wallHeight = 30;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
if (keyIsDown(LEFT_ARROW)) {
wallX = wallX - wallSpeed;
} else if (keyIsDown(RIGHT_ARROW)) {
wallX = wallX + wallSpeed;
}
if (keyIsDown(UP_ARROW)) {
wallY = wallY - wallSpeed;
} else if (keyIsDown(DOWN_ARROW)) {
wallY = wallY + wallSpeed;
}
wall(wallX, wallY, wallWidth, wallHeight);
}
function wall(x, y, sx, sy) {
for (let i = 0; i < 5; i++){
rect(x, y + sy * i, sx, sy);
}
}
In this code snippet, I am defining some global variables (generally not a good practice, but for the purposes of a p5.js sketch this is fine). The important ones are the wallX and the wallY variables. These variables are defined outside of the draw() function, so they will not be reset every time the loop runs.
Inside the loop, there are key handlers defined (I used the arrow keys for clarity). If the arrow keys are pressed, the position of the wall will change by the wallSpeed value per frame.
Also, note the background(220) call - this redraws the grey background at the beginning of the loop. If you remove that, the results of the previous renders will be visible, chances are that you don't want that.
Finally, the wall() function. You can see that the key press handling is not done inside of it - it needs the state information to come from the outside. All this function does is use a loop to draw five rectangles in a stack, at the position it is given. When the position changes, it redraws the wall in a different place on the canvas. The rectangles all refer to the root x and y values, so they will all move together as a unit.
Another way would be to create the class to represent a Wall and make instances of that class and call methods on those instances, but that could be something to revisit once you gain more experience with p5.js.
Hope this helps!

How to check if mouse position is inside HTML5 Canvas path?

I'm new to HTML5 Canvas and Javascript. I'm currently working on a HTML5 Canvas project (I wrote a separate question about it here and here).
The logic of the game is pretty simple: track the mouse position and if the mouse position exits the blue region, the game resets. The user starts on level 1 (function firstLevel). If their mouse position enters the red box region, they advance onto the next level (return secondLevel()). I did this previously by a hefty list of if statements that compared the mouse's x and y coordinates.
I've corrected the code to create a global constructor function I can reference in each of the level functions:
function Path(array, color) {
let path = new Path2D();
path.moveTo(array[0][0], array[0][1]);
for (i = 1; i < array.length; i++) {
path.lineTo(array[i][0], array[i][1]);
}
path.lineTo(array[0][0], array[0][1]);
return path;
}
And its referenced in the level functions as such:
// In the individual levels, put code that describes the shapes locally
var big = [[350,200], [900,200], [900,250], [700,250], [600,250], [600,650], [350,650]];
var blue = Path(big);
var small = [[900,200], [900,250], [850,250], [850, 200], [900, 200]];
var red = Path(small);
// 2. create cursor
c.beginPath();
c.rect(mouseX, mouseY, mouseWidth, mouseHeight);
c.fillStyle = "#928C6F";
c.fill();
// Local code that draws shapes:
c.beginPath()
c.fillStyle = '#C1EEFF';
c.fill(blue);
c.beginPath()
c.fillStyle = "#FF4000";
c.fill(red);
I want to know how can I check for the mouse position so it can run those conditional statements using the isPointInPath method? I now have a reference (refs. blue and red) for the Canvas shape, so I'm hoping there's a way I can check if the mouse's x and y position is a point that is inside the shape/path.
Link to my project: https://github.uconn.edu/pages/ssw19002/dmd-3475/final-project/maze-page-1.html
Source code: https://github.uconn.edu/ssw19002/dmd-3475/tree/master/final-project
You could use the MouseEvent.offsetX and MouseEvent.offsetY properties of a mouse event such as mousemove. (You would need to add an event listener for mouse events to the canvas element of course.)
Then use the x and y offset values obtained to call isPointInPath like
ctx.isPointInPath(path, x, y)
where path is a return value from function Path.
While MDN lists the offset properties as "experimental" they seem to have been around since IE9 at least.

Canvas Context.drawImage() is leaving behind duplicates of Sprite on UpdatePosition()

I've been experimenting with a basic game loop with HTML's Canvas element. Numerous tutorials online don't go into enough detail with the concepts of rendering and canvas.ctx (context).
What I'm trying to do is something very simple: Render an image on a canvas element and, on keydown, update its position and render it at the new location, making it move across the screen. Basically, what every video game does with its sprites.
I've been told through these tutorials that ctx.drawImage(image, x, y, ...) will work for this. However, what ends up happening in my version is essentially what happens when you win a game of solitaire on windows. It repeats the sprite's image as if it's creating a brand new sprite each time the game loops. The sprite itself doesn't move, a new sprite seems to be generated to the left/right/etc of the original one. I understand that I'm calling ctx.drawImage(...) every time I'm iterating through the game loop. However, this didn't happen when I used ctx.clearRect(...). It worked precisely how I expected it to. I'm not exactly sure why creating a rectangle with ctx works while creating an image doesn't.
My question is: Is there a way to simply update the position of the sprite without creating a brand new version of it every single loop?
Here's my relevant code:
let lastRender = 0; // For the general loop
let image = new Image();
image.src = "/img/image.png";
let state = {
pressedKeys: {
// left, right, up, down: false
},
position: {
x: canvas.width / 2,
y: canvas.width / 2
},
speed: 20
}
let pepsi = new Sprite({
img: image,
width: 100,
height: 100
)};
function Sprite (options) {
this.img = options.img;
this.width = options.width;
this.height = options.height;
this.render = function(){
ctx.drawImage(
this.img,
state.position.x,
state.position.y
)
}
}
function updatePosition(progress) {
//pressedKeys is just an object that relates WASD to the key codes
// and their respective directions, it's ignorable
if (state.pressedKeys.left) {
state.position.x -= state.speed;
}
if (state.pressedKeys.right) {
state.position.x += state.speed;
}
if (state.pressedKeys.up) {
state.position.y -= state.speed;
}
if (state.pressedKeys.down) {
state.position.y += state.speed;
}
}
function draw() {
pepsi.render();
}
function loop(timestamp) {
let progress = timestamp - lastRender;
update(progress) // <-- Updates position, doesn't touch draw()
draw(); // <-- Runs pepsi.render(); each loop
lastRender = timestamp;
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop); // for the general loop
If you have any qualms with the way this project is set up (for example, using the state.position for each Sprite), then I'd be glad to hear them in addition to the solution to my problem. Not in isolation. I got most of this code from contextless, non-specific online tutorials, but I understand most of it, save for the rendering.
Also, if you've seen this kind of question before and are on the fence about saying "Possible duplicate of {Borderline Tangentially-Related Post from Four Years Ago}", then here's some advice: Just answer the question again. It literally does nothing negative to you.
The solitaire smearing effect that you are getting, comes from the fact each frame is being drawn over the top of the last one. The canvas doesn't get cleared automatically between frames.
You mentioned that you have used clearRect, the use of clearRect is to clear all the pixels in the specified rectangle.
So if you put ctx.clearRect(0, 0, canvas.width, canvas.height) in the draw function before pepsi.render(), that should clear the canvas before drawing the next frame.

Canvas Rendering Optical Illusion in JS Game (Briefly duplicated graphics) - Fix?

I'm creating a side-scrolling space-shooter in javascript. So far everything seems to be working well. However, there is an odd bug in the canvas rendering that I can't quite figure out (and is difficult to describe, so bear with me!)
I have a player that can shoot projectiles by clicking the left mouse button. When the projectile first leaves the player, there appears to be two of them for a brief second, until they eventually merge in to the one projectile. I'm not creating two, so this seems like an optical illusion (this is most evident if you fire a few projectiles in quick succession).
The really odd thing is, when I try and capture a screenshot of this happening, all looks fine. Can anyone figure out what's going on?
Player code including projectiles (full code in fiddle);
var Player = (function () {
// ------------------------------------------------------------------------------------------------
// PLAYER VARIABLES
// ------------------------------------------------------------------------------------------------
var w = 50;
var h = 50;
var x = 0;
var y = 0;
var projectiles = [];
// ------------------------------------------------------------------------------------------------
// BIND EVENTS TO THE GLOBAL CANVAS
// ------------------------------------------------------------------------------------------------
Canvas.bindEvent('mousemove', function (e) {
y = (e.pageY - Canvas.element.getBoundingClientRect().top) - (h / 2);
});
Canvas.bindEvent('click', function () {
createProjectile(50, (y + (h / 2)) - 10);
});
// ------------------------------------------------------------------------------------------------
// FUNCTIONS
// ------------------------------------------------------------------------------------------------
var createProjectile = function (x, y) {
projectiles.push({
x: x,
y: y
})
};
var update = function () {
for (var p = projectiles.length - 1; p >= 0; p--) {
projectiles[p].x += 10;
if (projectiles[p].x > Canvas.element.width)projectiles.splice(p, 1);
}
};
var render = function () {
Canvas.context.fillStyle = 'white';
Canvas.context.fillRect(x, y, w, h);
for (var p = 0; p < projectiles.length; p++) {
Canvas.context.fillStyle = 'red';
Canvas.context.fillRect(projectiles[p].x, projectiles[p].y, 5, 5);
}
};
// ------------------------------------------------------------------------------------------------
// Exposed Variables and Functions
// ------------------------------------------------------------------------------------------------
return {
update: update,
render: render
}
})();
Js Fiddle Demo HERE: https://jsfiddle.net/oqz204bj/
EDIT
Based on #Pimskie's answer, It does indeed seem like an optical illusion - so my question now becomes, how could I reduce this effect? I plan on implementing a feature in the future that allows the player to switch weapons (where some of them would **actually* fire multiple projectiles) but I don't want this effect to remain for fear of confusion.
yes it is an optical illusion. The reason it looks like there multiple squares when first fired is because your eyes are focused on the big static ship square. Once your eye starts to follow the movement path, then it looks more like a fluid square moving instead of a square being redrawn 60 or 30 times per second. hold a piece of paper or your hand up to your screen covering the left half of it. Focus on the piece of paper and fire a few shots. You'll notice that the shots seem to appear multiple, the same as when just fired. It's a matter of your mind seeing 3 different frames as the same one.
requestAnimationFrame depends on the frame rate of your browser and computer. In most cases that's 60fps. 60 to 70fps is the limit of most monitors, and so it doesn't make sense to try and go above that. HOWEVER you can create the illusion of a more fluid movement by having a trailing tracer effect on your projectiles. That would involve having 2 or 3 extra squares created behind each projectile that have less and less opacity.
My best guess it's an optical illusion indeed.
Check this updated fiddle: https://jsfiddle.net/oqz204bj/1/
I removed one requestAnimationFrame and replaced a other with a very slow setInterval, just for demonstration. You can see only one bullet is created.

How do js animations work?

Im trying to understand how to make a javascript animation run smoothly and I've been reading some answers around here and I found something I don't understand.
Here is the link to the question Smooth javascript animation
In the answer with most votes it says "which is why generally it's a good idea to base position/frame on the amount of time that has elapsed since the start of the animation (using new Date().getTime()) rather than moving/changing a fixed amount each frame."
Can anyone show me a very very simple example that uses the method from this answer, and explain how you then control the speed of the animation?
Overview
Generally with animations, you have three components
Update
Draw
Timer
These are run within a loop, called the animation loop. A typical animation loop might look like the following (I will explain all of the functions in detail below):
function animate() {
update(); // Executes all game logic and updates your world
draw(); // Draws all of the animated elements onto your drawing context
timer(); // Controls the timing of when animate will be called again
};
animate(); // Start animating
The animation loop is the main flow controller of what goes on inside your animation. Basically the code inside the animation loop is called over and over again. Each execution of the animate function constitutes a frame. During a frame, your world is updated and redrawn on the screen. The frequency with which the animate function runs is called the frame rate, and is controlled by the timer.
You will also need a reference to the drawing context, which will be used to hold the elements you wish to animate, also known as sprites:
// Create a variable with a reference to our drawing context
// Note this is not the same as using a canvas element
var canvas = document.getElementById("canvas");
Update
Update is responsible for updating the status of each item you wish to animate, once per frame. To take a simplistic example, say you have an array containing three cars, each with an x and y position and a velocity. On each frame, you want to update the position of the car to reflect the distance it should travel based on its velocity.
Our cars array, and the code used to generate a car might look like this:
// A method to create new cars
var Car = new Car(x, y, vx, vy) {
this.className = "car"; // The CSS class name we want to give the car
this.x = x || 0; // The x position of the car
this.y = y || 0; // The y position of the car
this.vx = vx || 0; // the x component of the car's velocity
this.vy = vy || 0 // the y component of the car's velocity
};
// A function that can be called to update the position of the car on each frame
Car.prototype.drive = function () {
this.x += this.vx;
this.y += this.vy;
// Return an html string that represents our car sprite, with correct x and y
// positions
return "<div class='"
+ this.className
+ "' style='left:"
+ this.x
+ "px; top:"
+ this.y
+ "px;'></div>";
};
// Create a variable to hold our cars
var cars = [
new Car(10, 10, 5, 3),
new Car(50, 22, 1, 0),
new Car(9, 33, 20, 10)
];
When we call update, we will want to call the drive method of each car in order to move the car sprites around the screen. This drive method will return an html string that represents the sprite, including it's current position. We will want to append this string to a variable that can be used to set the inner HTML of the canvas div:
// An empty string that will be used to contain the innerHTML for our canvas
var htmlStr = "";
// Update the position of each car
function update() {
// Clear the canvas content
htmlStr = "";
for (var i = 0, len = cars.length; i < len; i++) {
// Add the car sprite to the html string to be rendered
htmlStr += cars[i].drive();
}
};
Draw
When the update function is done outputting our sprites, we will need to actually draw the elements on the canvas. In order to do this, we use the innerHTML method of the canvas variable, passing it in htmlStr, which contains markup used to represent all of the sprites. This will ask the browser to parse the text and spit DOM elements out on the screen:
function draw() {
// Parse the text containing our sprites and render them on the DOM tree
canvas.innerHTML = htmlStr;
};
You could argue that there are better ways to do this, and there probably are. However, in the interest of keeping it simple, I kept it simple. This is also not the most performant method, as the DOM API is very slow. If you look at the system resource usage of this application, most of it will be hogged by innerHTML calls. If you want a faster context for drawing, you should use HTML 5 canvas.
Timer
Finally, you need some way of calling animate over and over again. You could do this a couple of ways. First, there is good old setTimeout:
setTimeout(animate, 0);
This instructs the browsers to call animate after a delay of 0ms. In practice, animate will never execute after a delay of 0ms. The minimum resolution of setTimeout in most browsers is around 15ms, but even that is not guaranteed, due to the way the UI thread works in javascript.
A better solution is to use requestAnimationFrame, which basically tells the browser you are doing animation. The browser will do a bunch of nice optimizations for you. However, this is not fully supported across browsers. You should use this solution for a cross-browser requestAnimationFrame polyfill:
http://paulirish.com/2011/requestanimationframe-for-smart-animating/
Then you can use:
requestAnimFrame(animate);
In the end your finished program will look something like:
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
// Create a variable with a reference to our drawing context
// Note this is not the same as using a canvas element
var canvas = document.getElementById("canvas");
// A method to create new cars
var Car = new Car(x, y, vx, vy) {
this.className = "car"; // The CSS class name we want to give the car
this.x = x || 0; // The x position of the car
this.y = y || 0; // The y position of the car
this.vx = vx || 0; // the x component of the car's velocity
this.vy = vy || 0 // the y component of the car's velocity
};
// A function that can be called to update the position of the car on each frame
Car.prototype.drive = function () {
this.x += this.vx;
this.y += this.vy;
// Return an html string that represents our car sprite, with correct x and y positions
return "<div class='"
+ this.className
+ "' style='left:"
+ this.x
+ "px; top:"
+ this.y
+ "px;'></div>";
};
// Create a variable to hold our cars
var cars = [
new Car(10, 10, 5, 3),
new Car(50, 22, 1, 0),
new Car(9, 33, 20, 10)
];
// An empty string that will be used to contain the innerHTML for our canvas
var htmlStr = "";
// Update the position of each car
function update() {
// Clear the canvas content
htmlStr = "";
for (var i = 0, len = cars.length; i < len; i++) {
// Add the car sprite to the html string to be rendered
htmlStr += cars[i].drive();
}
};
function draw() {
// Parse the text containing our sprites and render them on the DOM tree
canvas.innerHTML = htmlStr;
};
function animate() {
update(); // Executes all game logic and updates your world
draw(); // Draws all of the animated elements onto your drawing context
requestAnimFrame(animate); // Controls the timing of when animate will be called again
};
animate(); // Start animating
Conclusion
There is a lot of room for optimization, tweaking, abstraction, and a whole lot of other goodness that I didn't go into here. There are a ton of ways to implement update and draw, and all have there pros and cons.
However, every animation you will ever write will use some sort of animation loop and they all have this basic architecture. Hopefully, this illustrates the basics of an animation in javascript.
The basic idea is the following:
You want to move a DIV from 0,0 to 100,0 in 1 second.
You'd like it to have 50 frames per second (using a setTimeout(fun, 20))
However, since the callback is not guaranteed to run in exactly 20ms, your callback needs to figure out when it was actually run. For example, let's say your animation started at time X but your first animation callback wasn't called until X+5ms. You need to calculate the position that the div should be at 25ms of the animation, your animation callback can't assume 50 even steps.
Here's a very simple code sample. I don't usually code with globals but this is the easiest way to show the technique.
http://jsfiddle.net/MWWm6/2/
var duration = 1000; // in ms
var startTime; // in ms
var startX = 0;
var endX = 500;
// 0 means try to get as many frames as possible
// > 1: Remember that this is not guaranteed to run as often as requested
var refreshInterval = 0;
var div = document.getElementById('anim');
function updatePosition() {
var now = (new Date()).getTime();
var msSinceStart = now - startTime;
var percentageOfProgress = msSinceStart / duration;
var newX = (endX - startX) * percentageOfProgress;
div.style.left = Math.min(newX, endX) + "px";
if (window.console) {
console.log('Animation Frame - percentageOfProgress: ' + percentageOfProgress + ' newX = ' + newX);
}
if (newX < endX) {
scheduleRepaint();
}
}
function scheduleRepaint() {
setTimeout(updatePosition, refreshInterval);
}
div.onclick = function() {
startTime = (new Date()).getTime();
scheduleRepaint();
}
This post describes the best way to do animations: http://paulirish.com/2011/requestanimationframe-for-smart-animating/
That will run at 60fps, or as fast as possible.
Once you know that, then you can decide how long you want your animation to take and how far the object is moving, then work out how far it has to move each frame.
Of course, for smooth animations, you should use CSS Transitions where ever possible – take a look at http://css3.bradshawenterprises.com.
The quick answer is that setTimeout doesn't guarantee that the callback will be executed n number of milliseconds after you call it. It just guarantees that it will be executed no sooner than n milliseconds from that time. John Resig covers some of this in this piece.
For this reason, you need to check what time your animation callback has actually executed, not what time you've scheduled it with setTimeout.
I've written a blogpost which demonstrates the concept of "time based" animation using canvas and a sprite sheet.
The example animates a sprite and takes into account the elapsed time to make sure it remains consistent in different framerates. You could easily apply this into moving an element around a page by changing the code slightly. The concepts related to timing and animation itself remain pretty much the same.
http://codeutopia.net/blog/2009/08/21/using-canvas-to-do-bitmap-sprite-animation-in-javascript/
(I always feel so spammy leaving answers that basically just link to my blog, even if the link is actually entirely relevant :D )
Look at it this way: You've got some object you want to move. Let's say you're giving it 5 seconds to move from A to B, and you want it to be done at 30fps. That means you've got to update the object's position 150 times, and have at most 1/30 = 0.0333 seconds per update.
If you're using the "time since last adjustment", and your code takes 0.05 seconds to execute, then you're not going to make the 30fps. However, if you're basing the animation off when it started, then it doesn't matter how long your code takes - when your update code fires, it'll calculate and set the object's position should be at that stage of the animal, regardless of how many frames have actually been displayed.

Categories

Resources