The idea of the game

I've used SVG animations for a while and had a pretty good understanding of how to use it for making animated sprites or website layouts. The combination of platforms like GreenSock and native CSS animations worked great. So I decided to go further and wondered if I can create a simple game with these. Like a really beautifully crafted, very detailed game that has every little element animated. So once I got up at night, as I came up with an idea of the game, made some sketches and showed them to my brother - a professional web designer. We had the design, decided how every element should animate (trying to visualize and vocalize it) and I got to work.

How to play: The bouncing ball changes the color. You have to see what the current color is and match the color of the ball and the bars that it's going to land on. In order to change the bar color, click on it one time to make it red, twice to make it yellow, and a triple click will make it purple.

Here's the full version: http://codepen.io/gregh/full/yVLOyO

The creative process

I've been working on the game, running into problems and refactoring. One of the most major problems was how to make it look good on all devices and screen sizes. I worked on my Macbook Pro 💻 and it looked good, then it was too small on the 27" iMac screen 🖥 and of course it was too large to fit the iPhone📱I really like that there's an emoji for all of these devices. After a couple of attempts it was clear that the traditional techniques of using media queries to create responsive websites would not work.

This is not a tutorial, so I will not go thoroughly through what every line of code means. But I will show you some cool stuff and you can play around by changing the values on CodePen or in the browser dev tools. I also provide learning resources along the way. I tried to make as many comments as I could in the CodePen, so go ahead and check the code.

I really like how powerful GSAP is so no wonder I used it. Also CodePen has a built in Babel compiler, so I can write ES6 code there, which gives a lot of privileges like Class syntax or arrow functions. Read more here: https://github.com/DrkSephy/es6-cheatsheet

Animating With Greensock

I assume that you are familiar with GSAP, but if not, you can always learn it from the guy who hates tomatoes. https://ihatetomatoes.net/get-greensock-101/

Background animations

Everything you see on the background are basically SVGs. I put every wave, into a separate div, the layers of mountains are separate divs as well and there's also a div for the clouds. There's one thing to remember when you are working on a complex animations. I could make the background just one SVG, and animate it's child elements and paths. Greensock lets us do that. You just have to assign IDs to your svg elements (such as paths, groups etc) and select them by ID. The problem about that approach is that the animation does not run mobile devices. You want to place them in separate divs then animate the divs. I actually used the SVGs as background images.

  var wave1 = TweenMax.to('.wave1', 0.7, {backgroundPositionX: '-=54px', repeat: -1, ease: Power0.easeNone});

Then what we have here are just simple Tweens, that move the background position by 54 pixels (the background width), and we don't want an easing here, so that they move at a constant speed. We do the same for mountains and clouds. Setting each element different speeds we give the background a kind of a parallax effect, which looks cool.

Notice that there are small white particles floating around. I generate them, setting random position and size. Then I animate them in a circular motion:

  {rotation: 360, transformOrigin: "-"+radius+"px -"+radius+"px", ...}

They all move around in a circular motion slowly, which looks more like a chaotic motion.

Bars animations

Each bar has small moving elements inside of them. I created them using just HTML/CSS. SCSS saved a lot of time and lines of code (as always). I took the most of it by creating shapes mixins for the effects inside bars. If we look at the bubble effect, each circle is positioned absolute and is styled using this mixin. Creating triangles in CSS (especially with borders) takes rather too many lines of code, so using mixins for those shapes came in handy.

Let's look at the red bar, that has bubbles in it.

  @mixin bubble ($size, $top, $left) {
    height: $size;
    width: $size;
    top: $top;
    left: $left;
}

@mixin bubble_hollow ($size, $top, $left) {
    @include bubble ($size, $top, $left);
    background-color: transparent;
    border-style: solid;
}

so now if I want to create the inner bubbles I just call those mixins that set size and position them absolutely inside the bar:

  .bubble-4 {
    @include bubble(15px, 98px, 37px);
}
.bubble-5 {
    @include bubble_hollow(5px, 116px, 20px);
}

It's absolutely necessary when creating the little triangles with borders, for example, because it takes about 20 lines of CSS code to make them.

Bubbles animation

I use staggered animations for those effects. This animates all of elements inside the bar, but they don't start simultaneously. Instead, each of them has a small delay. Read more about staggered animations here: http://greensock.com/docs/#/HTML5/GSAP/TweenMax/staggerFrom/

Triangles animation

In the yellow bar, I made the spin. But notice how some of them rotate about the X axis and some about the Y axis. We use the cycle property here.

  var triangle = new TimelineMax({delay: 0.5});
triangle
        .staggerTo(el.find('.triangle'), 1.5, {
            cycle:{
                rotationY: [0, 360],
                rotationX: [360, 0],
            },
            repeat: -1,
            repeatDelay: 0.1
        }, 0.1);

Blocks animation

We use the same technique for the blocks animations. So half of them moves from left to right, the other half moves in the opposite direction.

Score animations

Click Rerun, to watch it animating again.

I wanted this to have this sort of jellyish effect. So I did with only a few lines of code:

    var resultTimeline = new TimelineMax();
  resultTimeline
      .fromTo('.stop-game .score-container', 0.7, { opacity: 0, scale: 0.3 }, { opacity: 1, scale: 1, ease: Elastic.easeOut.config(1.25, 0.5)})
      .fromTo('.stop-game .final-score', 2, { scale: 0.5 }, { scale: 1, ease: Elastic.easeOut.config(2, 0.5)}, 0)
      .fromTo('.stop-game .result', 1, { scale: 0.5 }, { scale: 1, ease: Elastic.easeOut.config(1.5, 0.5)}, 0.3)
      ;

To create the elastic (jellyish) effect, we just have to set the right easing. If you want to explore what easing functions you can use, check out the Greensock ease visualizer: http://greensock.com/ease-visualizer Check the Elastic and tweak the config params, to see how I got the result.

Flexbox

The first improvement was using Flexbox. Who's not familiar with that there's a great article https://css-tricks.com/snippets/css/a-guide-to-flexbox/ and a good series on Laracasts. But once you start using it, you can't live without it. So let's take a look at the Main menu screen and the Game screen.

Coloron

The Start Game Container has the following code:

  {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

“flex-direction: column” shows the direction the flex items are placed inside the container. Column set the direction from top to bottom. The default (row) would place all the items in a row from left to right. then the cool thing: justify-content. It distributes free space, and we can choose if we want to place the free space, before, after, between or around elements. Play with that and see how cool it is. So as we have it set to “space-between” it pushes Top and How to Play to the top and the bottom and places Logo Holder in the middle (all the free space is between these items). the Align-items: center, places centers the elements by the secondary axis (horizontal in this case) How to Play is a flex item has the following code:

  {
    display: flex;
    width: 100%;
}

So it is flex as well, we don't set flex-direction, so it takes the default (row). Which places the 3 sections horizontally. The sections One and Three have “flex” parameter set to 1, so they take all of the free space, which is a really cool flex box feature as well. Alternatively you could not touch the sections, but use a justify-content: space-around on the How to Play element. The reason I used flex: 1 is because I wanted the middle column to be exactly in the center.

Coloron

Now this is the game scene. Which has display: flex. It has a bars container, which contains all of the bars and ball holder. I didn't want to position the ball absolutely, I wanted to make it "sit" right on the sticks container using CSS, some solution that would work even if I change the bars height without any additional code. So the container has the flex-direction set to column, which does that we want, it places the ball-holder on top of the sticks container. justify-content: space-between pushes the ball-holder all the way to the top of the scene and the sticks container all the way to the bottom. Now we give the ball-holder the following css:

  {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

It becomes flex as well, we set the flex direction to column (which make the Y axis the main axis, thus placing items from top to bottom). Then we push the content to the end of the container with justify-content: flex-end, so the ball is pushed to the bottom of the ball holder. So what we have is two containers placed on top of each other, and the content of the top one is pushed to the very bottom. This is how we make the ball sit on the bars.

Imagine doing all of these screens without flexbox with all of the floats, width: 33.33333%’s and position: absolute bottom: 0’s. Flexbox makes it much cleaner and pleasant to work with.

Making the game scalable

The real thing was to make it scalable. See how good it looks on different screen sizes. As I said, I simply used the CSS transform for that. Which had it's own challenges. This is what I did. Let's say that the default size is 1200x800px. Then if your screen size is different than that you have to make the game container smaller and larger by some coefficient, which is screenHeight/800. Of course we have to take into account the situations where the device height is greater than width (tablets and phones in portrait mode) so we have this to scale our screen. This is the code to calculate the scaling ratio:

  var scale = (screenWidth > screenHeight) ? screenHeight/800 : screenWidth/1200;

This doesn't look good yet. See how the game looks small and is centered.

Coloron

So we have have to make the container larger by the same coefficient, so that when it's scaled it uses the 100% of the screen size. So if we scale it to 0.5 (half of the original size), then we have to make the initial size of it two time larger, so that the computed size (which is the initial size * 0.5) becomes the 100% of the screen size. Or maybe the screen is very large and we need to scale it by 1.2 then we have to make the container initial size to screenSize/1.2

  $('.container')
        .css('transform', 'scale(' + this.scale + ')')
        .css('height', height/this.scale)
        .css('width', width/this.scale)
        .css('transformOrigin', 'left top');

Afterword

I hope you enjoyed the game and the article. I hope you learned something new and I hope it will help you build something amazing.

For any questions contact me @greghvns

I will constantly update the article as I make updates to the game, add benchmarks or try out different techniques.


12,409 6 86