Javascript And How To Make An Animated Walking Sprite Stop Walking?
Let's look at animative a sprite sheet, on an HTML5 sheet, using JavaScript.
A Little Setup
Commencement, let's create the canvass element.
<sail width= "300" elevation= "200" ></canvas>
Add together a edge (so we can come across our usable expanse).
sheet { edge : 1px solid black ; }
And load the sprite canvass (https://opengameart.org/content/green-cap-character-16x18). While we're at it, let'southward go access to the canvas and its 2d context.
let img = new Image (); img . src = ' https://opengameart.org/sites/default/files/Green-Cap-Character-16x18.png ' ; img . onload = function () { init (); }; allow canvas = document . querySelector ( ' canvas ' ); let ctx = canvas . getContext ( ' 2d ' ); part init () { // futurity animation code goes here }
The init
function is called after the image is loaded, via img.onload
. This is to ensure the image is loaded earlier nosotros endeavour working with information technology. All of animation code will go in the init
function. For the sake of this tutorial, this will work. If we were dealing with multiple images, nosotros'd probably desire to use Promises to wait for all of them to load before doing anything with them.
The Spritesheet
Now that we're prepare, let's take a await at the image.
Each row represents and animation wheel. The first (peak) row is the character walking in a downward direction, the second row is walking up, the 3rd row is walking left, and the fourth (bottom) row is walking right. Technically, the left column is a standing (no animation) while the middle and right columns are animation frames. I think nosotros tin utilize all three for a smoother walking animation, though. 😊
Context'south drawImage
Method
Before we get to animating our image, let'south wait at the drawImage
context method, as that'due south what we'll use for automatically slicing upward the sprite sail and applying it to our sail.
MDN docs - drawImage
Whoa, there are a lot of parameters in that method! Peculiarly the third form, which is the one nosotros'll be using. Don't worry, it's not as bad as it seems. In that location'south a logical group to information technology.
drawImage ( image , sx , sy , sWidth , sHeight , dx , dy , dWidth , dHeight )
The image
argument is the source prototype. The adjacent four (sx
, sy
, sWidth
, and sHeight
) relate to the source prototype - the sprite sheet. The last 4 (dx
, dy
, dWidth
, and dHeight
) relate to the destination - the canvass.
The "x" and "y" parameters (sx
, sy
, dx
, dy
) chronicle to the sprite sheet (source) and canvas (destination) starting positions, respectively. It's essentially a filigree, where the pinnacle left starts at (0, 0) and moves positively to the right and down. In other words, (l, xxx) is l pixels to the right and 30 pixels downward.
The "Width" and "Meridian" parameters (sWidth
, sHeight
, dWidth
, and dHeight
) refer to the width and tiptop of the sprite sheet and canvas, starting at their respective "x" and "y" positions. Allow's break information technology downward to i section, say the source image. If the source parameters (sx
, sy
, sWidth
, sHeight
) are (10, 15, xx, 30), the the starting position (in grid coordinates) would exist (ten, 15) and stretch to (30, 45). And then catastrophe coordinates are calculated as (sx
+ sWidth
, sy
+ sHeight
).
Drawing The Commencement Frame
Now that we've gone over the drawImage
method, let's actually see it in action.
Our sprite sheet's character frame size is conveniently labeled in the file name (16x18
), so that gives usa our width and acme attributes. The get-go frame will outset at (0, 0) and end at (16, eighteen). Allow's draw that to the sail. We'll outset with drawing this frame starting at (0, 0) on the canvas and continue the proportions.
role init () { ctx . drawImage ( img , 0 , 0 , 16 , eighteen , 0 , 0 , sixteen , xviii ); }
And we have our beginning frame! Information technology's a fiddling small-scale though. Permit'south scale it up a bit to make information technology easier to see.
Modify the above to this:
const calibration = 2 ; part init () { ctx . drawImage ( img , 0 , 0 , 16 , 18 , 0 , 0 , 16 * calibration , 18 * scale ); }
Y'all should see the paradigm drawn on the sheet has doubled in size both horizontally and vertically. By irresolute the dWidth
and dHeight
values, we can calibration the original image to be smaller or larger on the canvas. Exist careful when doing this though, as yous're dealing with pixels, it can offset blurring pretty apace. Try changing the scale
value and come across how the output is changed.
Next Frames
To depict a second frame, the merely thing we need to do is modify some values for the source set. Specifically, sx
and sy
. The width and elevation of each frame are the same, so we'll never have to modify those values. In fact, let'south pull those values out, create a couple scaled values, and draw our next two frames to the correct of our electric current frame.
const scale = 2 ; const width = 16 ; const height = 18 ; const scaledWidth = calibration * width ; const scaledHeight = scale * height ; function init () { ctx . drawImage ( img , 0 , 0 , width , height , 0 , 0 , scaledWidth , scaledHeight ); ctx . drawImage ( img , width , 0 , width , height , scaledWidth , 0 , scaledWidth , scaledHeight ); ctx . drawImage ( img , width * 2 , 0 , width , meridian , scaledWidth * ii , 0 , scaledWidth , scaledHeight ); }
And this is what it looks similar at present:
Now nosotros have the entire top row of the sprite canvass, only in three split frames. If yous look at the ctx.drawImage
calls, there are only 4 values that alter now - sx
, sy
, dx
, and dy
.
Let'south simplify it a flake. While we're at information technology, let's starting time using frame numbers from the sprite sail instead of dealing with pixels.
Replace all the ctx.drawImage
calls with this:
office drawFrame ( frameX , frameY , canvasX , canvasY ) { ctx . drawImage ( img , frameX * width , frameY * height , width , pinnacle , canvasX , canvasY , scaledWidth , scaledHeight ); } function init () { drawFrame ( 0 , 0 , 0 , 0 ); drawFrame ( i , 0 , scaledWidth , 0 ); drawFrame ( 0 , 0 , scaledWidth * 2 , 0 ); drawFrame ( 2 , 0 , scaledWidth * 3 , 0 ); }
Our drawFrame
office handles the sprite canvas math, so we only need to pass in frame numbers (starting at 0, like an array, then the "x" frames are 0, 1, and 2).
The canvas "x" and "y" values still take pixel values so we take better control over positioning the character. Moving the scaledWidth
multiplier inside the function (i.e. scaledWidth * canvasX
) would hateful everything moves/changes an unabridged scaled character width at a time. That wouldn't work with a walking animation if, say, the character moves iv or v pixels each frame. So nosotros leave that as information technology is.
At that place'southward also an extra line in that list of drawFrame
calls. This is to show what our blitheness cycle volition expect like, rather than but cartoon the tiptop three frames of the sprite sheet. Instead of the animation bicycle repeating "left pace, right step", it volition echo "stand, left, stand, right" - it's a slightly better animation bike. Either is fine though - a number of games in the 80s used two step animations.
This is where we're currently at:
Let'due south Animate This Character!
Now we're gear up to breathing our character! Allow's have a look at requestAnimationFrame
in the MDN docs.
This is what we'll employ to create our loop. We could too apply setInterval
, but requestAnimationFrame
has some nice optimizations in place already, like running at 60 frames per second (or as close equally it can) and stopping the blitheness loop when the browser/tab loses focus.
Essentially, the requestAnimationFrame
is a recursive function - to create our animation loop, we'll call requestAnimationFrame
again from the function nosotros're passing as the argument. Something like this:
window . requestAnimationFrame ( step ); function step () { // do something window . requestAnimationFrame ( step ); }
The lonely call before the walk
part starts the loop, then information technology's continuously chosen within.
Before we get to using information technology, at that place's 1 other context method we need to know and utilise - clearRect
(MDN docs). When cartoon to the canvas, if we keep calling drawFrame
on the same position, information technology'll proceed drawing on top of what'southward already at that place. For simplicity, we'll clear the entire canvas betwixt each draw, rather than just the area we draw to.
Then, our depict loop volition await something like articulate, draw the first frame, articulate, draw the second frame, and and then on.
In other words:
ctx . clearRect ( 0 , 0 , canvas . width , canvas . summit ); drawFrame ( 0 , 0 , 0 , 0 ); // repeat for each frame
Okay, let's animate this graphic symbol! Let'southward create an array for the wheel loop (0, 1, 0, 2) and something to keep track of where nosotros are in that wheel. And then nosotros'll create our step
function, which will human activity as the chief animation loop.
The step function clears the canvas, draws the frame, advances (or resets) our position in the bike loop, then calls itself via requestAnimationFrame
.
const cycleLoop = [ 0 , 1 , 0 , two ]; let currentLoopIndex = 0 ; function step () { ctx . clearRect ( 0 , 0 , canvas . width , canvas . elevation ); drawFrame ( cycleLoop [ currentLoopIndex ], 0 , 0 , 0 ); currentLoopIndex ++ ; if ( currentLoopIndex >= cycleLoop . length ) { currentLoopIndex = 0 ; } window . requestAnimationFrame ( step ); }
And to get the animation started, let's update the init
function.
function init () { window . requestAnimationFrame ( step ); }
That graphic symbol is going places fast! 😂
Slow Down There!
Looks like our grapheme is a little out of command. If the browser allows it, the character volition be fatigued 60 frames per second, or as close as possible. Let'southward put a limit on that so information technology's stepping every xv frames. We'll need to keep rails of which frame we're on. So, in the stride
office, we'll advance the counter every call, but simply depict after 15 frames pass. One time 15 frames pass, reset the counter, and draw the frame.
const cycleLoop = [ 0 , one , 0 , ii ]; let currentLoopIndex = 0 ; allow frameCount = 0 ; function step () { frameCount ++ ; if ( frameCount < 15 ) { window . requestAnimationFrame ( step ); return ; } frameCount = 0 ; ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ); drawFrame ( cycleLoop [ currentLoopIndex ], 0 , 0 , 0 ); currentLoopIndex ++ ; if ( currentLoopIndex >= cycleLoop . length ) { currentLoopIndex = 0 ; } window . requestAnimationFrame ( step ); }
Much amend!
The Other Directions
So far, we've only handled the down direction. How about we modify the animation a bit so the character does a complete four-footstep cycle in each management?
Remember, the "down" frames are in row 0 in our code (showtime row of the sprite sheet), up is row 1, left is row 2, and correct is row 3 (bottom row of the sprite sheet). The wheel remains 0, 1, 0, two for each row. Since nosotros're already handling the cycle changes, the only affair we need to change is the row number, which is the second parameter of the drawFrame
function.
We'll add a variable to continue rails of our current management. To proceed it simple, we'll go in the sprite sheet'south order (downwardly, up, left, correct) so it's sequential (0, i, 2, 3, echo).
When the cycle resets, we'll motion to the next direction. And one time we've gone through every management, we'll kickoff over. So, our updated step
part and associated variables expect similar this:
const cycleLoop = [ 0 , 1 , 0 , two ]; let currentLoopIndex = 0 ; let frameCount = 0 ; let currentDirection = 0 ; function step () { frameCount ++ ; if ( frameCount < fifteen ) { window . requestAnimationFrame ( step ); return ; } frameCount = 0 ; ctx . clearRect ( 0 , 0 , canvas . width , canvas . superlative ); drawFrame ( cycleLoop [ currentLoopIndex ], currentDirection , 0 , 0 ); currentLoopIndex ++ ; if ( currentLoopIndex >= cycleLoop . length ) { currentLoopIndex = 0 ; currentDirection ++ ; // Next row/direction in the sprite sail } // Reset to the "down" management once nosotros've run through them all if ( currentDirection >= four ) { currentDirection = 0 ; } window . requestAnimationFrame ( stride ); }
And in that location we take it! Our character is walking in all 4 directions, animated all from a unmarried image.
Source: https://dev.to/martyhimmel/animating-sprite-sheets-with-javascript-ag3
Posted by: baileycoluch.blogspot.com
0 Response to "Javascript And How To Make An Animated Walking Sprite Stop Walking?"
Post a Comment