Monday, February 8, 2016

Collapsing Platforms in Phaser

I've been working on level design over the last few days for my Phaser platformer (view the complete source code on GitHub). I used my floating, ride-able cloud platforms (that I wrote about last week) in a few places, but I also found a few places where I wanted to use a collapsing platform. You know, one that falls after you stand on it for a little while.

The red-and-black platforms will fall after you jump on them.

To start, they are very similar to the riding platforms. The difference is in the custom collision callback function, shown below.

function fallPlatformSep(s, platform) {

    if (!s.locked) {
        s.lockTo(platform);
    }

    if (!platform.activated) {

        platform.activated = true;

        // save the original Y-value
        var origY = platform.position.y;

        platform.game.time.events.add(1000, function() {

            var t = platform.game.add.tween(platform.position);
            var dist = ((platform.game.world.height + 100) - platform.y);
            var time = dist * 2.25;
            t.to( { y: platform.position.y + dist }, time, Phaser.Easing.Quadratic.In, false, 0, 0, false);
            t.onComplete.add(function() {

                // after falling, wait 2 sec. before putting it back
                platform.game.time.events.add(2000, function() {
                 platform.activated = false;
                 platform.position.y = origY;
                 var t2 = platform.game.add.tween(platform);
                 t2.to({ alpha: 0.5 }, 100, Phaser.Easing.Linear.None, true, 0, 3, true);
                });

            });

            t.start();

        }, this);
        
    }
    
}

function update() {

    // ... other stuff ...

    this.physics.arcade.collide(frog, fallingPlatforms, fallPlatformSep, null, this);

    // ... other stuff ...

}

Since this function is called when the frog and the falling platform are colliding, the first step is to "activate" the platform, if it has not been activated already. This block should be entered the first time that the frog lands on the platform and won't be entered again no matter how long he stands on it.

The next step is to make a note of the original Y-value, so it can be put back a few seconds after it falls off the screen. The drop and restoration is handled by a few game timers and tweens. The first timer sets it up so that the platform will drop 1000 milliseconds after the first contact. Inside the timer function, I set up a Tween to move the platform down to a final position of 100 pixels below the world bounds. This makes sure that if the frog rides it to the bottom he will be complete off-world and trigger the kill function.

I'm using the Phaser.Easing.Quadratic.In easing function in the drop so that it gets off to a slow start. I actually found a very useful page with live demos of all the Phaser easing functions: just click here!

When the drop Tween completes, I set a second timer. Two seconds after the fall, the platform gets set back to its original Y-position and once again becomes inactive. Just to make it look nice, I use another Tween here to blink the alpha a few times.

But don't let your interest in my comics drop as fast as these platforms! Check out today's comic before you go to other parts of the Internet, or order lunch, or whatever.

Amphibian.com comic for 8 February 2016

Friday, February 5, 2016

Use Tiled's Custom Properties in Phaser

To make editing the maps for my Phaser platformer easier, I decided to make use of the custom properties Tiled lets you add to objects. These get directly translated to fields on the Sprite objects if you use the createFromObjects function to pull them from the map.

I used this technique on my moving cloud platforms. In the map editor, I select the object and then add two custom properties: direction and distance.

Adding two custom properties in Tiled

Once I saved my map, I could then use those properties when I set up my moving cloud platforms. In the create function, I make the platforms from the objects on the map. Look at the following code.

function create() {

    // ... other stuff ...

    platforms = this.add.group();
    platforms.enableBody = true;
    map.createFromObjects('others', 6573, 'cloud-blue', 0, true, false, platforms);
    platforms.forEach(function(p) {
        
        this.physics.enable(p, Phaser.Physics.ARCADE);
        p.body.allowGravity = false;
        p.body.immovable = true;
        
        var t = this.add.tween(p.position);

        // use the distance and direction custom properties
        // to set up the Tween
        var dist = (p.distance ? Number(p.distance) : 128);
        if (p.direction == "left") {
            t.to( { x: p.position.x - dist }, 3000, Phaser.Easing.Linear.None, true, 0, -1, true);
        } else if (p.direction == "right") {
            t.to( { x: p.position.x + dist }, 3000, Phaser.Easing.Linear.None, true, 0, -1, true);
        } else if (p.direction == "up") {
            t.to( { y: p.position.y - dist }, 3000, Phaser.Easing.Linear.None, true, 0, -1, true);
        } else if (p.direction == "down") {
            t.to( { y: p.position.y + dist }, 3000, Phaser.Easing.Linear.None, true, 0, -1, true);
        }
        
    }, this);

    // ... other stuff ...

}

As I iterate over them to perform their setup, I can read p.distance and p.direction in order to customize the movement Tween. I first check to see if distance is defined and use a default value if it is not. This way, the game doesn't fail if I forget to set the custom property. I then check the value of the direction field and set the Tween accordingly. If I forget to set direction on one of my objects, it just won't move because the to function will never be called on the Tween.

I like this method because it allows me to quickly and easily tweak the level in the map editor until I get the level design right. I don't have to go back to change any code when I want to move a platform's starting position, direction, or travel distance. I might add a third property set set the speed as well.

Remember, you can see the complete source code for the game on GitHub!

While you're here, why don't you take a few minutes and catch up on some comics that you may have missed - like today's! It deals with the first and best troubleshooting technique there is.

Amphibian.com comic for 5 February 2016

Wednesday, February 3, 2016

Ride Moving Platforms in a Phaser Game

Another staple of classic platformers that my Phaser game has been missing is the moving platform. You know, those floating platforms that go up and down or side to side and upon which you can ride? I had to add some. It wasn't that hard, but there were some tricks to it.

This is the frog's new cloud platform.

First, I had to add the sprites for the moving platforms. I did this by adding them to my map in Tiled as objects, the same way I added the enemies (see that blog post here).

platforms = game.add.group();
platforms.enableBody = true;
map.createFromObjects('others', 6573, 'cloud-blue', 0, true, false, platforms);
platforms.forEach(function(p) {
    
    game.physics.enable(p, Phaser.Physics.ARCADE);
    p.body.allowGravity = false;
    p.body.immovable = true;
    
    var t = this.add.tween(p.position);
    t.to({ y: p.position.y - 200 }, 3000, Phaser.Easing.Linear.None, true, 0, -1, true);
    
}, this);

Every object with id 6573 was assigned the "cloud-blue" sprite image key and put in the platforms group. Then I iterate over the group to set them all up with physics so they can collide with the frog. I turn off gravity on them so they don't fall off the screen, and I set body.immovable to true. This isn't to say that they can't move - it just means that they won't react to being pushed down when the frog lands on them. The movement of the platforms is controlled by Tweens. In the above code snippet, all of the platforms are given the same Tween. This one example Tween will move the platform up 200 pixels from its start position in 3 seconds and then loop forever. In my finished game I'll use different Tweens for different platforms.

After that, adding a collision check in the update function between the frog and the platforms group was all it took to make the platforms solid.

I now had moving platforms upon which the frog could land, but the behavior wasn't correct. As the platform moved up it would push the frog up with it, but on the way down the frog would keep falling and hitting the platform over and over again. It didn't look like he was riding it. The solution was to create a special collision callback function which "locks" the frog to the platform upon which he stands.

function platformSep(s, platform) {

    if (!s.locked) {
        s.locked = true;
        s.lockedTo = platform;
        s.body.velocity.y = 0;
    }

}

function update() {

    this.physics.arcade.collide(frog, platforms, platformSep, null, this);

    // ... other stuff ...

    if (frog.locked) {
        if (frog.body.right < frog.lockedTo.body.x || frog.body.x > frog.lockedTo.body.right) {
            frog.locked = false;
            frog.lockedTo = null;
        } else {
            frog.x += frog.lockedTo.deltaX;
            frog.y += frog.lockedTo.deltaY;
        }
    }

    // ... other stuff ...

}

Now, when the frog and a platform are in collision, the frog will get a locked flag set to true, and the lockedTo field will be set to the platform sprite on which he stands. The frog's y-velocity is also set to 0 here to stop any potential falling that might occur otherwise.

Once I had these additional fields, I could perform a check to see if the frog is currently locked to a platform. If he is, but his current position says that he's walked off of it, I cancel the lock and his behavior goes back to normal. Otherwise, the frog's position gets updated to match the movement deltas of the platform. This keeps him riding it, no matter which in direction it moves.

These moving platforms really add a lot of classic platformer feel to the game, and thanks to Phaser they weren't too difficult to add. Remember, you can see the complete source code to the game on GitHub and play the current version at this link!

There are some major upgrades going on in the comic today, so don't forget to read it!

Amphibian.com comic for 3 February 2016

Monday, February 1, 2016

Generating Color Gradients with JavaScript

The comic frames with the color
gradient backgrounds.
For the comic on Friday, I had to generate a color gradient between the full-brightness daylight color in the third frame and the nighttime darkness color in the fourth frame. Since the point of the comic was to adjust the brightness using a slider with 20 positions, I needed 40 colors (including the 2 starting colors) so that when the brightness was all the way down both frames would have the same background. They would meet in the middle.

It was actually a very nice comic. On the right you can see the frames with the color gradients, but the real comic is interactive - go play around with it if you haven't yet!

To generate the gradient of colors, I found a set of JavaScript functions which I will share here. I can't take credit for it, all I did was rearrange some of the code and re-order the output (the original seemed backwards to me) but it is very nice. I am keeping it in the source code for the Amphibian.com comic editor in case I ever need it again.

Here is the function. Calling it should be self-explanatory. Note that the colorCount parameter will be the total size of the output array INCLUDING the start and end colors.

function generateColorGradient(colorStart, colorEnd, colorCount) {

    /* Convert a hex string to an RGB triplet */
    var convertToRGB = function(hex) {

        /* Remove '#' in color hex string */
        var trim = function(s) {
            return (s.charAt(0) == '#') ? s.substring(1, 7) : s;
        };

        var color = [];
        color[0] = parseInt((trim(hex)).substring(0, 2), 16);
        color[1] = parseInt((trim(hex)).substring(2, 4), 16);
        color[2] = parseInt((trim(hex)).substring(4, 6), 16);
        return color;

    };

    /* Convert an RGB triplet to a hex string */
    var convertToHex = function(rgb) {

        var hex = function(c) {
            var s = "0123456789abcdef";
            var i = parseInt(c);
            if (i == 0 || isNaN(c))
                return "00";
            i = Math.round(Math.min(Math.max(0, i), 255));
            return s.charAt((i - i % 16) / 16) + s.charAt(i % 16);
        };

        return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);

    };

    var start = convertToRGB(colorStart);
    var end = convertToRGB(colorEnd);

    // The number of colors to compute
    var len = colorCount - 1;

    //Alpha blending amount
    var alpha = 0.0;

    var ret = [];
    ret.push(convertToHex(start));

    for (i = 0; i < len; i++) {
        var c = [];
        alpha += (1.0 / len);
        c[0] = end[0] * alpha + (1 - alpha) * start[0];
        c[1] = end[1] * alpha + (1 - alpha) * start[1];
        c[2] = end[2] * alpha + (1 - alpha) * start[2];
        ret.push(convertToHex(c));
    }

    ret[len] = convertToHex(end); // last color might be slightly off

    return ret;

}

One thing that I noticed is that for colorCount values greater than 13 the final color was not quite the requested final color. Rounding error I suppose. That's why I explicitly set the last element in the return array to the end color, if you were wondering.

Using jQuery, I wrote a simple demonstration of the gradient function:

    var things = generateColorGradient("#CC0033", "#FFFF66", 15);
    for (var i = 0; i < things.length; i++) {
        $("body").append("<div style='background-color: #" + things[i] + ";'>test</div>");
    }


...which produces the following output on a web page. Beautiful, isn't it?


Today's comic, on the other hand, contains absolutely nothing of any technical interest. You should still read it though. And if you haven't read all the other Amphibian.com comics, what are you waiting for? They don't take that long to read. Start today!

Amphibian.com comic for 1 February 2016

Friday, January 29, 2016

A Slider Control for the Web

Today's comic has a slider control embedded in the third frame, allowing you to adjust the brightness of the sun and extend daylight. It's suppose to look a bit like the brightness slider control on your phone. I got the idea from my daughter who keeps her brightness on the lowest setting in order to extend the battery life. She also panics when it gets down to 70%, as if a dead phone battery will somehow mean her own death as well. Teenagers...

You might assume that a big fan of HTML5 such as myself would use the range input field to implement the slider. You would be wrong. Unfortunately, I do try to keep the comic mostly compatible with IE9 (that might change be the end of the year) and it does not support the range type of input. Also, the default styling of the range slider looks pretty terrible. It would have taken me a while to get something that looked the way I wanted.

A standard HTML5 slider control. Input type "range" does this.

Well, how about the jQuery Mobile slider? It looks nice, and I already use jQuery. But, alas, its mobile styling really messed up the default styling of all kinds of things on my pages. It would have taken me quite some time to straighten it all out.

The jQuery Mobile Slider control. Looks nice, destroys Amphibian.com.

So twice I was thwarted by wanting a slider that looked nice and didn't cause me to do a lot of extra work. Before I gave up on this idea, I found the Slider for Bootstrap by Kyle J. Kemp. It came with CSS that didn't interfere with the rest of my CSS, and was easy to use.

Start by including the bootstrap-slider.js (or bootstrap-slider.min.js) file on your page. Also include the bootstrap-slider.css (or bootstrap-slider.min.css). Then write some HTML like this:

<input id="brightnessSlider" type="text"
       data-slider-tooltip="hide"
       data-slider-id="bSlider"
       data-slider-min="0"
       data-slider-max="19"
       data-slider-step="1"
       data-slider-value="19"/>

Note that the type of the input is "text" and not "range." Don't worry about that for now, look at the data-slider- attributes. These are used to pass configuration settings to the control. In my case, I wanted to hide the tooltip, set the minimum value to 0, the maximum value to 19 (for 20 total values), the stepping to 1, and start with a value of 19 (the max). The data-slider-id attribute is of interest, though. What it means is that when the JavaScript turns this input into a bunch of other elements that look like a slider, the parent div for all those will be named "bSlider". Once that's created, this text input will be hidden from view. But when the user interacts with the slider, the value will be written back to the text input's value - making it easy to figure out what to use when the form is submitted.

Take a look at the JavaScript now.

var bChange = function() {

    var n = Number($('#brightnessSlider').val());
    console.log("current value is " + n);

};
    
var bslide = $('#brightnessSlider').slider()
                    .on('change', bChange);

In this example, the bChange function doesn't really do much, but it shows how you can get the current value of the slider. The bChange function is passed in as the event handler for the change even when the slider is created.

If you want to change the width of the slider, the only way I found was to alter the CSS width attribute of the control's created div. I did this programmatically when the comic is displayed in one of the small-screen formats. Remember, the control's div will have the id matching the data-slider-id attribute in the input tag.

$("#bSlider").css("width", "150px");

The default width seemed to be 210px, but I reduced that to 150px for the phone screen sizes.

The styled slider in the comic.

After that, I added a few CSS rules to change the colors of parts of the slider, and I ended up with pretty much exactly what I envisioned it should look like. For now, this is the slider control that gets my recommendation - as long as you also use jQuery and Bootstrap. But who doesn't these days?

See the slider in action in today's comic!

Amphibian.com comic for 29 January 2016

Wednesday, January 27, 2016

Less Snow, More Snow Days?

The schools here were closed today. A snow day? No, it didn't snow. This has become normal it seems. The schools here in Pennsylvania close for bad weather much more than they ever did when I was in school.

Today the reason was a forecast of freezing rain. I'm not sure if there was any freezing rain. But they also sometimes close in advance due to a forecast of snow. The next day when the snowfall is much less than predicted (or doesn't materialize at all) it seems ridiculous that the kids had to miss school. Do they call and un-do the closure? No, the kids just stay home.

They also close school when it just gets too cold. I'm fairly certain that I had to stand outside waiting for the school bus in temperatures in the single digits - when measured in kelvin. I have to wonder if they give days off school when it gets too sunny in places like Florida.

The other thing that is missing from schools today is the 1-hour delay. When I was a kid, we had 1-hour delays, 2-hour delays, and complete cancellations. I can remember only a few early dismissals. I don't think 1-hour delay is an option anymore. There's never been one since my kids have been in school. What happened to them? Did snow plows get slower? Is it not possible to clear the roads in an hour anymore?

I don't know how these rampant closures prepare the children for adulthood. I don't get the day off when it snows. They get to stay home, but I have to drive my car in those terribly unsafe conditions to get to my job.

Why don't we just get school buses like this one?



I probably sound old now. When I was a kid we had to walk to school in the snow uphill both ways while being chased by bears! Well that's enough of my ranting about school closures, time to read today's comic. It never snows in the comic. Maybe it should. I don't know what the frogs would do in the snow.

Amphibian.com comic for 27 January 2016

Monday, January 25, 2016

Run Your Own Comic

Mechanical frogs, for repeatable
 installations of Amphibian.com
As you may have heard, the East Coast got hit by a major snow storm this weekend. It started snowing Friday night and snowed most of the day Saturday. Since I couldn't go anywhere I was able to get a lot done - making some new comics and enhancing the platformer game that I've been working on.

But before I did any of that, I finished checking in files and instructions for repeatable installations of the Amphibian.com webcomic application. I started doing this over a year ago, but never finished it. The goal was to have all of the files and complete keystroke-level instructions necessary to set up Amphibian.com on a new Ubuntu instance.

Why do I want this? Currently, the comic is running on an Ubuntu virtual machine hosted by Digital Ocean. Digital Ocean is great and I hope they're around for a long time, but what if they aren't? Or what if another service comes along for a better price? Or what if I just want to set up another instance somewhere for test purposes?

The complete installation includes:


I sat down in the morning with VirtualBox, the Ubuntu 14.04.3 server ISO image, and got to work. I created a fresh virtual machine with a clean install of Ubuntu and documented all the steps I had to perform to get the comic running. When I thought I had all the commands documented, I started from scratch with a new virtual machine and did it all again by just copying and pasting commands. Of course, I missed a few. I updated my document and tried again with a third virtual machine. That time, everything went well and I had Amphibian.com running in under 10 minutes.

The things that I was really missing were database setup scripts (to make the database, user, and tables), the site configuration files for Nginx, and a script to start and stop the application. I added those to the repository along with my complete setup instructions and now I feel a lot better. Not only does it allow me to have the confidence that I can recreate my application if I ever need to, but it allows other people to easily set up and run my application as well. Maybe you'd like to try it, and make your own comic?

Here is the link directly to the INSTALL.txt file in the repo on GitHub, so you can check it out.

What's next? Maybe complete automation of the setup using something like Puppet. That way I could take advantage of an auto-scaling service like AWS offers, so when my comic gets insanely popular I won't have to worry about long response times. It could happen.





As a side note, I found out while I was doing this that a new major version of PhantomJS was recently released. This 2.0.0 has some major differences and actually fixes some bugs that I've encountered while using it to generate the static images of my comics. Unfortunately, there is no binary distribution available for Linux yet - so my installation instructions for Amphibian.com actually include building PhantomJS 2.0.0 from source. That takes a long time, so I'm keeping my binary around in case I need it again before the official ones are available. If you would like it (built for Ubuntu 14.04.3 64-bit) just let me know and I'll give you a link.



If you're done setting up your own copy of Amphibian.com now, click the picture below and view my Amphibian.com comic for today!

Amphibian.com comic for 25 January 2016