Build a Street Fighter Demo with CSS Animations and JavaScript

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?

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.