Treehouse

Build a Street Fighter Demo with CSS Animations and JavaScript

By on  

I recently learned a cool technique from Simurai about how to animate PNG sprites with the CSS3 animations' steps() property. The main idea in this technique is to "recreate" some kind of animated GIF but with the tiles of a PNG sprite.

As with everyone I know, I played to Street Fighter in my childhood and when I saw this ... guess what popped in my head?

Check out this Pen!

If the pen doesn't render above, click here to see it in action.

Let's Create the First CSS Move

We'll start with the punch (see in the sprite bellow it's the third one). First we need to open Photoshop to create the sprite. Make all images the same size (these ones are 70px width and 80px height). There is a good app called Texture Packer which can help in creation of game sprites. Try to find the dimensions of the biggest of your frames and use these dimensions for your grid. At the end you'll get something like this:

Then we need to set up a DIV for Ken which will receive our punch move (and all our other future moves):

/* html */
<div class="ken"></div>
/* css */
.ken { 
    width:70px; height:80px; /* exactly the size of an image in our sprite */
    background-image:url('../images/sprite.png'); 
}

Let's assume vendor prefixes are implicitly working. Now we can declare the punch animation like this:

/* css */
.punch { 
    animation: punch steps(4) 0.15s infinite; 
}
@keyframes punch {
    from { background-position:0px -160px; }
    to { background-position:-280px -160px; }
}

What we just did is apply an animation (punch) to a class name (.punch) which basically animates background-position from 0px to -280px (on x axis). This animation will be broken into 4 parts (steps(4) which corresponds to the punch's 4 images), and it will take 0.15 second to perform; then it will start over infinitely.

Finally we need a way to add/remove the .punch class name on DIV.ken when another key is pressed.

/* javascript */
$(document).on('keydown', function(e) {
    if (e.keyCode === 68) { // 68 is the letter D on the keyboard
        $('.ken').addClass('punch');
        setTimeout(function() { $ken.removeClass('punch'); }, 150);
    }
});

We used jQuery to addClass('punch') if the letter "D" is pressed and then remove it after a setTimeout (a delay) of 150ms (remember our css animation takes exactly 0.15s wich is the same as 150ms). That's pretty much all you need to know to create a lot more moves.

Take it to the Next Level with SASS

If you pay attention to what we are doing, you'll notice we have some values that never change (width/height of an image in the sprite), and, after you've created some other moves, you'll notice you have a lot of code duplication which will be difficult to read and maintain in the future. SASS can help us DRY all this mess!

First we need basic @mixins like animation() and keyframes():

@mixin animation($params) { 
    -webkit-animation:$params;
    -moz-animation:$params;
    -ms-animation:$params;
    animation:$params;
}
@mixin keyframes($name) { 
    @-webkit-keyframes $name { @content }
    @-moz-keyframes    $name { @content }
    @-ms-keyframes     $name { @content }
    @keyframes         $name { @content }
}

We need to store image width / height values and SASS variables exist for this reason:

$spriteWidth:70px;
$spriteHeight:80px;

And finally we can mix those together to create a complicated new mixin which will declare moves and handle correct calculation of background positions for us:

@mixin anim($animName, $steps, $animNbr, $animParams){
    .#{$animName} { 
        @content;
        @include animation($animName steps($steps) $animParams); 
    }
    @include keyframes($animName) {
        from { background-position:0px (-$spriteHeight * ($animNbr - 1)); }
        to { background-position:-($spriteWidth * $steps) (-$spriteHeight * ($animNbr - 1)); }
    }
}

Now you can create a new move with a single line of code:

$spriteWidth:70px;
$spriteHeight:80px;

/* punch */
@include anim($animName:punch, $steps:3, $animNbr:3, $animParams:.15s infinite);
/* kick */
@include anim($animName:kick, $steps:5, $animNbr:7, $animParams:.5s infinite);
/* hadoken */
@include anim($animName:hadoken, $steps:4, $animNbr:1, $animParams:.5s infinite);
...

$animNbr is very important: the calculation is based on this number. In fact it's just the moves count in the sprite. Our first example was the punch, right? And in our sprite it's the move number 3. The kick is number 7, etc.

Add Collision Detection for the Fireball

We need a very fast loop for collision detection. It will test the fireball position (offset) every 50 milliseconds, compare it to something else position (here we test the end of the screen). If the fireball's left position is bigger than the window width, then it means the fireball overtaken the screen so we immediately apply an .explode class.

Here's how I did it; it's not perfect but it works very well:

var $fireball = $('<div/>', { class:'fireball' });
$fireball.appendTo($ken);

var isFireballColision = function(){ 
    return $fireballPos.left + 75 > $(window).width();
};

var explodeIfColision = setInterval(function(){
    $fireballPos = $fireball.offset();
    if (isFireballColision()) {
        $fireball.addClass('explode'); 
        clearInterval(explodeIfColision);
        setTimeout(function() { $fireball.remove(); }, 500); 
    }
}, 50);

What's Next?

We could easily add some sound effects, background music, sprite another character, mix this up with web RTC to allow multiple computers to control characters (I don't know something with NodeJS and Socket.io or maybe the cool new Meteor framework); that's what I love with web development: it's almost limitless.

Julien Knebel

About Julien Knebel

I'm a freelance front-end developer living and coding in Paris, France. I love combining design techniques with web development technologies.

ydkjs-6.png

Recent Features

Incredible Demos

Discussion

  1. stefan

    very cool dude !

  2. Tony

    Alright Adam, this is good stuff here, thanks for sharing

  3. Tony

    Um I meant David lmao

  4. towry

    Great stuff !!!!

  5. towry

    comment post without refreshing??

  6. Mitch

    This is sweet. I saw this thing last week and thought it was incredibly awesome. I’m probably going to link to this post in DZone’s link roundup.

  7. Très beau travail Julien, merci pour le partage :)

  8. WOW!

    that is awesome work!

  9. This is such a detaild demo and I love the free vectors that you showed. I will try to put it together since I`m a new in game developing , and see how it works. Best wished dude.

  10. Hah! I don’t think I might need to use this any time soon, but it’s nonetheless a wicked way to use CSS & company :-)

    One thing though, the CodePen demo does not play nicely with arrows–the page scrolls indeed (yes, a laptop doesn’t have as much screen space as a huge monitor!)

  11. Daryl

    Wowwwwwwww great job David !!!!!!!!!!

  12. Bien vu dude ! ;)

  13. Barun Saha

    That’s superb! Hope to try it soon.

  14. Wow! Cool! Great job!

  15. Mind blowing, i visited on your website cause i am crazy to learn.. But i bit upset cause JavaScript sections is not in English language, and hard to understand and learn :(

    @Julien Please add some kind of translator or convert it in Eng. language, so that the snippet by you will be helpful for everyone :)

    • Argh sorry bro i didn’t except this demo would attract so much people. I’ll let you know asap cheers!

  16. I am not a game fan but doing this using CSS3 is amazing. Good job!

  17. Its really cool. Hats of to you and keep it up. There is so much to learn.

    Arun

  18. Tony Brown

    The reverse kick sprite does not complete it’s animation???

  19. Great Job David … Just keep it up , bunch of techniques learned by you … thank you .

  20. MrK

    this is a nice demostration of why html5 should never be an standar for games.

  21. wOw great JOB man ,super cool stuff. sharing is good thnx man

  22. Rather a nice article. I am going to try it myself a couple of times. :-)

  23. Elvis

    Oh Shit!!!!! floor is equal to Bison, levitating, that pig!

  24. Cauê
    • OMG man it rocks! On the MDN Demo home page :D You made my day. Happy to see people taking it a step further. Well done.

  25. Catz

    Explain please where did you get these sprites? Manual extraction?

  26. Will be my default Easter Egg! Nice!

  27. I have just started using similar techniques for animations myself, however I’ve run into a problem that your demo suffers from as well…browser zoom. When zooming all my responsive images work as they should do, but because of the nature of the sprites the animations break. I’m going to work on a solution but just wondered whether you had realised this and worked out a solution yourself?

    • Nop had the same issu, but I don’t feel like it’s a real problem after all? I guess… :)

  28. Agustin

    Just found this today and thought it might be a nice addition to the post. Multiple animated gif backgrounds from arcade games: http://abduzeedo.com/wicked-fighting-game-background-gifs

    • Man it’s totally rocking awesome!! :D I wish I could have time to continue my demo with one of these gif backgrounds.

  29. Amazing code! If you don’t mind I may try a similar project and reference this demo.

  30. Sure! I’d love to see that when it’s ready :)

  31. Ikraam

    Dude you’ve opened doors to many innovations here. Well done! thank you!!!

  32. Ikraam

    How would the code look like if you wanted the user to execute certain commands before executing a move? e.g down,forward,down,froward + hit = shoryuken. Thanks in advance it is highly appreciated as always! :)

  33. Doesn’t work in IE9. :-) But,… neither does my website,… so I’m okay with that. LOL

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!