🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

I do not understand the code version of time based sprite animation?

Started by
9 comments, last by 8Observer8 3 years, 4 months ago

I'm Universo and for the longest time had coding issues around the concept of time-based sprite animation basically, do not know how to implement it and any code or explanation simple or complex that I've found has been extremely confusing to me and was hoping to have a kind person walk me through the code step by step, explaining exactly how this is implemented correctly.

Here's what, I believe to know about the concept and or idea for time-based sprite animation it is to construct a delay or halt of some code statement using time of some kind in milliseconds with a collection of moving images and or sprites however any attempts from the tutorial's or my own brain have resulted in inaccurate various speed sprite animations and not the consistent stable speed, Id appreciate and be grateful for here's some code to show my progress and perhaps help you understand my position in this mess baring in mind, I prefer my code be as extremely simple and speedy as possible within the limitations, of course:

JavaScript with HTML5 Canvas

window.onload = function () {

    var canvas = document.getElementById("canvas"); // get the canvas object

    var ctx = canvas.getContext("2d"); // make sure we have a context for that canvas so we can draw stuff

    var FrameSize = 120; //120 pixels
    var FrameNumber = 0; //obvious
    var img = new Image(); // make a image object
    img.src = 'sprite_ani.png'; // url to the image
    var last = 0; // no idea 
   
    function Render() {

      ctx.clearRect(0,0 ,640,480); // clear screen
      now = performance.now(); //to get the current timestamp in milliseconds
      delta = now - last; // last is 0, how does this work?
      last = now; // last is now, why?

      if(delta > 20 //what does this value represent??) {
         FrameNumber += FrameNumber + 1; // increment FrameNumber by 1 could have been FrameNumber++
         delta = 20; // No idea why I need to keep this the same number??
      }
         
      if(FrameNumber>=4){   //just to make sure this is the end of the frame number sequence  
         FrameNumber = 0; // to reset the frame number
      }
         ctx.drawImage(img,FrameSize*FrameNumber,0,120,120,0,0,120,120); 
         
         /* 
         perticularly draw a sequence of images and switch each individual image 
         in that sequence based on the equation
         
         FrameSize 
         (number to multiply the pixels by)
         * // multiply operator symbol
         FrameNumber 
         (the pixels for the FrameSize to multiply with) 
         */
         
         window.requestAnimationFrame(Render); // do the Raf stuff part one
    }
    window.requestAnimationFrame(Render);  // do the raf stuff part two
   }
   

As you can see, it's extremely simple yet, I'm still struggling to not only understand it but extremely simply implement it, I'm not a fan of over-complicated coding hence why I ditched c++ ages ago.

Could someone in extremely simple English guide me through time-based sprite animation in the context of JavaScript preferably but can be any other language, Id also really appreciate various time-based sprite animation methods being explained too in extremely simple terms but not holding out, I thank you for reading this far.

Advertisement

(sorry to hear that u struggled)

it is indeed simple the code you're sharing here and i can see some simple mistakes in your code…;

(sorry to ask u this), but

  • what's yr Engineering background?
  • education?
  • why do u have an interest in programming?

please don't answer these questions if u don't want to, don't feel obliged, it's just to know how to phrase answers so not to confuse you any further; in other words, i believe the problem u've got here is a logical-understanding problem, everybody understands things differently, i feel that knowing a bit of your background could get u the right answers from folks round here;

until then ?

@ddlox
My engineering background could be considered the verb of sublime perhaps, I'm much better with idea's formulated within and around abstract realism/realistic abstractions in one form or another in a creative expression like music, art, etc, I need instant gratification at times to enjoy somethings, however, to answer you directly “sorry”, I've got hefty sparse computer knowledge to be fair, I could probably have a simplified conversation with a deep technical programmer/engineer at a slow pace to some extent unless he started talking about some very deep physical computer topic like how a rocket computer works and asked me confusing questions relating to it, ya, I've also programmed 3D prototype game engines in c++ with old and modern OpenGL this is without knowing answers to questions like the one I asked today and I was much younger but of course they were limited and lacking extremely haha if that helps, I learn in preferably very originate throw stuff together kinda way rather than a methodical logical computer-minded technique like a logician would or a careless just get it done swiftly mindset like a strategic mastermind type of person regardless of hedonistic qualities, however I'm able to dabble in those techniques if I have too, I am an emotion visionary and somewhat selfless idealist after all ?, which might not suggest my abilities but if you look into the shadows, you'll see a fidelity much more than light it self.

Id like to add that I've worked in a lot of languages like C++(WxWidgets, Win32, Qt, SDL, OpenGL. SFML), C (Win32, etc), Java(Slick2D/LWJGL/Swing/JavaFx), JavaScript(many libraries here), got 3D models on the screen or 2D images, etc, GLSL shaders, messed about with assembly language very mildly in various ways including NES, SNES, stuff played about with GBC code, etc, I'm pretty hardcore but maybe not as much as some others of course.

Education is definitely sublime in a lot of area's in one form or another, in terms of schooling, not much to write home about very brief and lacking, I learnt most of my stuff from the internet nowadays anything else was from people, books, school, myself, etc.


I enjoy the ability to go deep into stuff right down to the origin and also find myself fascinated by the creative potential of programmatic control, it's like being able to do whatever, you want nearly if you can program, then you can probably build a computer from scratch in one form or another or you can make a video camera, etc it sounds dumb at first but think about it, binary programming is about as hard as it gets in creativity within and around inside and outside the scope of fidelity unless we go to an energetic level then we are at the limitation of everything which itself is a binary network of mechanical and or radiant energy (in my opinion) and plus, I see individuals like Bisqwit, Javidx9, John Carmack, etc on various sites and experience a sense of missing out on the creative potential of binary programming.

Hope that answers your questions and no problem though it's nice to have the consideration as an option.

The word ‘delta’ usually means the difference between now and previously. You are basically getting the time it took to render the frame.

‘last’ will be zero, but only the first time the code is executed. It gets set to ‘now’ only after the delta is calculated. Here's how it will calculate over multiple frames:

delta = now - last
delta = 0 (now) - 0 (last) (0)
delta = 500 (now) - 0 (last) (500)
delta = 1000 - 500 (500)
delta = 1500 - 1000 (500)

This assumes every frame is taking 500 milliseconds, which it won't be, because it will fluctuate.

I'm not sure why there is a ‘delta > 20’ check. The idea is to increment FrameNumber over time, which chooses a different sprite, due to it being multiplied by the width of an individual sprite.

@Stardog 


I appreciate and grateful for your response, I learnt about that last and now technique because of you, 
I made some changes to my code however that made more sense, it kinda works but keeps skipping similar frames:

window.onload = function () {
	
	var canvas = document.getElementById("canvas");
	
	var ctx = canvas.getContext("2d");
	
	var FPS = 30;
	var Accumulator = 0;
	var FrameSize = 120;
	var FrameNumber = 0;
	var FrameCounter = 1;

	var img = new Image();
	img.src = "sprite_ani.png";
	
	var Accumulator = 0;
	var last = performance.now();
	
	function Render() {
		
		ctx.clearRect(0, 0, 640, 480);
		
		now = performance.now();
		
		var delta = now - last;
		
		last = now;
		
		Accumulator++;
		
		if (Accumulator >= delta) {
			FrameNumber += FrameNumber + 1;
			console.log(FrameNumber);
			Accumulator = 0;
		}
		if (FrameNumber >= 4) {
			FrameNumber = -0;
		}
		
		ctx.drawImage(img, FrameSize * FrameNumber, 0, 120, 120, 0, 0, 120, 120);
		
		window.requestAnimationFrame(Render);
	}
	window.requestAnimationFrame(Render);
};

Dominic Hughes said:
FrameNumber += FrameNumber + 1; // increment FrameNumber by 1 could have been FrameNumber++

I don't know much about C#, but as far as I know, “a += b” is shorthand for “a = a + b”.

For the above statement, you're thus computing

FrameNumber = FrameNumber + FrameNumber + 1

which is also

FrameNumber = 2 * FrameNumber + 1

which not the same as incrementing your frame number by one. (Assume your FrameNumber is 3, then this statement computes 2 * 3 + 1 = 7 as the next frame number.)

Sandbox: https://plnkr.co/edit/zjYT0KTfj50MejT9?preview

main.js


window.onload = () =>
{
    const ctx = document.getElementById("renderCanvas").getContext("2d");

    let sx = 0, sy = 0, sWidth = 512, sHeight = 256; // Source image
    let dx = 0, dy = 0, dWidth = 512, dHeight = 256; // Destination canvas
    // ctx.drawImage(spriteSheet, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

    const spriteSheet = new Image();
    spriteSheet.onload = () => { mainLoop(); };
    spriteSheet.src = "assets/sprites-cat-running.png";

    let currentTime, deltaTime;
    let lastTime = Date.now();
    let animationTime = 0;
    const animationSpeed = 100;
    const frames = [
        { x: 0, y: 0 }, { x: sWidth, y: 0 }, { x: sWidth * 2, y: 0 }, { x: sWidth * 3, y: 0 },
        { x: 0, y: sHeight }, { x: sWidth, y: sHeight }, { x: sWidth * 2, y: sHeight },
        { x: sWidth * 3, y: sHeight }
    ];

    let i = 0; // Frame index

    function mainLoop()
    {
        currentTime = Date.now();
        deltaTime = currentTime - lastTime;
        lastTime = currentTime;

        animationTime += deltaTime;
        if (animationTime >= animationSpeed)
        {
            animationTime = 0;
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
            ctx.drawImage(spriteSheet, frames[i].x, frames[i].y, sWidth, sHeight, dx, dy, dWidth, dHeight);
            i++;
            if (i >= frames.length) { i = 0; }
        }
        requestAnimationFrame(mainLoop);
    }
};

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Running Cat. Canvas API, JavaScript</title>
    <script src="js/main.js"></script>
</head>

<body>
    <canvas id="renderCanvas" width="512" height="256"></canvas>
</body>

</html>

Dominic Hughes said:
Hope that answers your questions and no problem though it's nice to have the consideration as an option.

ok i sorta understand your background, i'll keep my explanation very simple indeed;

thanks and apologies for late reply… i shutdown for christmas -?-, but i see that u have received some decent answers; so i will only touch on what has not been answered (in simple explanation);

Animation these days comes in various forms, but forget everything for a moment…let's picture this for a moment:

look outside through your window, look at a tree and focus your attention on 1 leaf only of that tree ;

now look at your watch (or wall clock) and every second try and see how that leaf behaves;

there are 2 things you should understand with this picture scene:

  • the leaf moves (or is animated by the wind) every time (but in our case every second) that you have looked at your watch:
    this is called “time-based” animation
  • now imagine if you could draw by hand on the surface of your window (using a Marker pen or so) every position that this leaf was animated to by the wind:
    this is called “frame-based” animation

These 2 concepts aim to achieve the same objective: the animation of the subject-matter (in our case the leaf);

The main difference between these 2 forms of animation is that:

  • in time-based animation, time drives the animation like this:
    your game engine continuously announces the time (like your watch) and everytime a pre-defined time of interest has been announced (like 1second) then you animate the subject-matter
    So if you want to animate your leaf every second, then everytime your engine tells u that a second has elapsed (since the last time it drew something) then you animate this leaf;
  • in frame-based animation, each video frame count drives the animation like this:
    (note: each time you had to draw a new leaf on your window without lifting your pen counts as a video frame rendering), so in analogy your game engine can be coded such that it can continuously announce/notify you each time it is about to draw without lifting its pen, that is, everytime a pre-defined number of frames has been announced (like 24 frames) then you animate the subject-matter
    So if you want to animate your leaf every 24 frames, then everytime your engine tells u it is about to draw on its 24th frame (since the last time it drew something) then you animate this leaf ;

note: when i say “since the last time….”, it means:

  • the game engine has to be able to reset time to be able to tell how much time is elapsing from the last time, this is simply like pressing a timer reset button (like on real stopwatches: Start | Stop | Reset )
  • likewise the game engine has to be able to tell how many video frames have gone on (or been displayed), so that when it gets to the frame of interest it can draw what we want on it

i hope so far this makes sense, if not, read again until it sinks in a bit ?

some game engines have one or the other methods implemented, others have both, it depends on what you want to achieve, how you want to achieve and also very often how the animation data is stored (but let's not go there)…

so let's pick a real example now: GRID5 from Codemasters:

when the company Codemasters claims that their game engine can render GRID5 the game at 60 FPS, it means:

  • in frame-based animation: their engine can animate our leaf up to 60 times (frames) a second; so within 1 second of time, that leaf could have been animated 1 to 60 times;
  • in time-based animation: their engine can animate our leaf approximately every 16.66666667milliseconds; how do we get to this number?

if 60 frames are rendered in 1 second which is 1000milliseconds then 1 frame can be rendered in (1000/60)msec

now you can imagine that with more than just 1 leaf to render, it does take a lot of code effort to render everything (not just a leaf) in under 16msec (but that's discussion for other web forums -lol-)

anyway now let's answer some of your questions (the Render( ) you have presented contains both time-based and frame-based code):

var last = 0; // no idea

Remember I mentioned about RESET: well, this is how you tell the engine to reset its time-based counter and later on, instead of resetting to 0 again, you just reset it to the current time:

last = now; // last is now, why?

why? because the current time is no longer 0 it has moved on and is depicted in now, so by storing now into last you are telling the engine to effectively reset its time-based counter to the current time in now ?

next:

if(delta > 20 //what does this value represent??) {

first of all you should not put a comment like this, it should be like this:

if(delta ≥ 20) { //what does this value represent??

i assume that was probably an editing error as you typed your question;

so what does 20 represent?

Because you are calculating time in millisecs as in this line:
now = performance.now(); //to get the current timestamp in milliseconds

20 is therefore 20msec

Remember I mentioned pre-defined time?

well, 20msec is the time-based pre-defined time at which you want to animate your "leaf"

and delta is used to measure time since the last time the engine drew something and if the time difference since the last draw is more than 20msec then your code increases the frame number;

if GRID5 does this every 16msec which means that every 1000th msec, it draws: 1000 / 16.6.. = 60 frames,

your engine does this every 20msec which means that every 1000th msec, your engine draws: 1000 / 20 = 50 frames

delta = now - last; // last is 0, how does this work?

yes last is 0 when then engine's stopwatch called performance in your code started, but the next time the Render( ) function is called again last will not be 0, this has been explained to you by @stardog earlier

next:

Dominic Hughes said:
FrameNumber += FrameNumber + 1; // increment FrameNumber by 1 could have been FrameNumber++

this has been explained to you by @alberth

next:

Dominic Hughes said:
delta = 20; // No idea why I need to keep this the same number??

you keep it the same number because you want to the same action the next time delta is the 20th msec again, why? because that's when frame number is increased in your code, in other words, everytime your stopwatch performance hits its 20th msec you increase frame number and then you press the reset button, which is the same as saying: delta = 20;

you can change 20 to whatever you want, but that would change the engine's behaviour as well

finally,

i can see that from previous explanations from other fellow posts, u have improved your Render( ) already, and @8observer8 has also given u a decent improved version of your code;

so hope u see the light a bit more now, and i hope my explanation was simple enough to understand;

All the best and have fun ?

@Alberth You are correct, I eventually solved this problem using the appropriate arithmetic formula.
++ instead of += .

@ddlox I will get back to you, just like you busy and all that, thank you for answering as well.

I translated my example above to Python and C++:

This topic is closed to new replies.

Advertisement