Wednesday, December 12, 2012

A Simple 3D Renderer in HTML5, Part 4

For the final installment, we will look at JSLint, accelerometer input on mobile devices, and audio.

(Previous entries: Part 1, Part 2, Part 3)

Code Error Checking, Standards, and Obfuscation

As we continued the project, we realized we needed to have at least some validation for our code so it wouldn't take so long to debug problems that turned out to be simple syntax errors or otherwise mis-typed values.  The best method we found for this was JSLint:

http://www.jslint.com/

Not only would this check our code, but it also introduced a lot of useful standards to keep anyone programming Javascript from getting themselves in trouble accidentally.  For our project's purposes, though, there are a few standard JSLint checks we decided to ignore:

  • Tolerate messy whitespace (white): This puts a lot of strictures on code formatting that don't comply with our own coding styles (most notably spaces around parenthesized expressions and line breaks before opening new curly braces).
  • Tolerate ++ and -- (plusplus): This is meant to keep developers out of trouble if they're expecting to use these operators and their side effects.  But for our purposes, we don't code with the expectation to use said side effects, and using these operators for loops is highly convenient.
  • Tolerate continue (continue): We're not quite sure why this is a standard JSLint check, but some loops are easier to write with the continue statement, so we turned this off.

JSLint has a number of quite useful standards that we revised our code to comply with:

All comparisons are done with the === and !== operators, because they force a type check as well as a value check.  If we just use the == and != operator, Javascript will try to be clever and may convert a string to its equivalent integer value or whatnot, which is likely not intended behavior.

As a nice carryover from standard Java, variables and normal functions are named with initial lowercase letters.  Only functions that are actually classes begin with uppercase letters.

All variables are defined at the beginning of a function, rather as a throwback to C.  In Javascript, variables are scoped within a function, but they are not scoped within a subset of curly braces, unlike most language derivatives of C.  So if you define a variable within a loop or conditional, it will be available for the rest of the function.  Forcing variables to be defined at the beginning of the function serves as a good reminder of this behavior.

With all of code JSLint-checked, the next trick was to be able to optimize and obfuscate the final version of the code for deployment to the web.  For this, the best recommendation was the Google Closure Compiler.

https://developers.google.com/closure/compiler/

This provides a very simple command line to put in your input file or files, and get optimized and obfuscated code out.  Plus, it will do analysis on the code and output errors and warnings.

Accelerometer Input

For this particular game, we needed to use the accelerometer to control the car when it was run on a mobile device.  There are a few different events available to get accelerometer input, but again, their availability varies depending on the device and the software version.  Android version 2.3.3 and below does not seem to support any of the accelerometer events we tried.

window.ondeviceorientation

This provides three rotation angles for the device, labeled alpha, beta, and gamma.  It's supported on all our target devices, and so we ended up using it for our control input.

window.ondevicemotion

This provides actual acceleration vector values, and depending on the device, separated gravity values.  It was not supported on all the Android devices we tested, though, as it's originally an iOS specification.

We also needed to track window.orientation, which gives values (from our tests) of -90, 0, 90, and 180.  This lets us know the screen's orientation, since the actual values returned from the accelerometer events are independent of the screen orientation.

Audio

Audio was the last major piece of the puzzle for this HTML5 experiment.  The HTML5 audio object is limited, especially on mobile devices.  On the surface, most of the usual functionality seems to be there: loading different types of files, playing them, seeking to points in them.  Looping isn't yet a first-class citizen; you have to set a delayed function call (often through setTimeout()) to restart the audio object.  A good discussion of what's available can be found here:

http://24ways.org/2010/the-state-of-html5-audio

iOS, however, throws all of this expected functionality out the window and severely limits what you can do with audio.  Apple explains their stance here:

http://developer.apple.com/library/safari/#documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html

Essentially, they don't want users accidentally overrunning their bandwidth allotments because the particular HTML5 application downloads a bunch of unexpected video and audio data.  So they don't let anything download unless it's called directly from a user click action.  So far, the only solution that got us close to normal behavior on iOS is here:

http://remysharp.com/2010/12/23/audio-sprites/

We group all of our sound effects into a single audio file that's loaded when the user clicks the "Play Game" button.  Then, when we actually want a sound effect to play, we seek the audio file to its time marker and start it playing, then set a delay timer to stop it playing after its time length has run out.  This does limit us to playing a single sound effect at a time, because iOS only allows one audio object in memory at a time.  Also, in order to get maximum performance on iOS devices, we have to encode the audio in Apple's native AIFF wrapper, using the IMA-ADPCM codec within it, since iOS devices decode that natively.  MP3 is also supported, but playback is much slower.

Ultimately, audio is a giant pain to deal with if you want your HTML5 application to run on iOS.  It slows down what is otherwise a very well-optimized implementation of HTML5.

The End

And that wraps up the series about our first foray into HTML5 development.  There are a lot of powerful things here, but also a lot of pitfalls, and a lot more platform-specific issues to work out than one might expect.

Still, there will be plenty of opportunities to use these technologies in future projects.

No comments:

Post a Comment