Javascript Tetris Pt 6: Lights, Action, Music!
Full source code can be downloaded from project home at kjeldahlnilsson.net.
The game looks very prototype-ish right now. We're not shooting for blockbuster level presentation here, but we should at least provide a bare minimum of animation and audio feedback. Let's do something about that by adding animation, making the playing field a little more interesting, plus some support for sound effects and music.
First off; some sort animation payoff when the player clears one or more rows. We want a sort of stylized explosion to occur. Let's create that separately first. We'll achieve this by applying a JQuery UI effect to a div while hiding it.
test.js:
testExplodeAnimation: function() { Graphics.clearGameContainer(); var square = Graphics.createRectangleDiv("black", 400, 400, 100, 100); $(square).hide("explode", {}, 1000); }
This works, and looks cool. Now we'll finish the Field.doRowClears() method from yesterday, including some animation when we remove the filled rows in the field.
field.js:
doRowClears: function() { var rowsToClear = []; this.State.gridState.eachRowWithIndex(function(row, rowNumber) { var entireRowFilled = true; // Is row filled? for (var tile in row) { if (row[tile] === 0) { entireRowFilled = false; break; } } // Set row to be cleared if (entireRowFilled) { rowsToClear.push(rowNumber); } }); if (rowsToClear.length > 0) { this.explodeAndClearRows(rowsToClear); } }, explodeAndClearRows: function(rowsToExplode) { // Set up big animation rectangle to cover the disappearing rows var topRow = 1000; var bottomRow = 0; for (var row in rowsToExplode) { if (rowsToExplode.hasOwnProperty(row)) { if (topRow > rowsToExplode[row]) { topRow = rowsToExplode[row]; } if (bottomRow < rowsToExplode[row]) { bottomRow = rowsToExplode[row]; } } } var rowsTotalTopY = this.State.posY + (topRow * Piece.State.tileHeight); var rowsTotalBottomY = this.State.posY + (bottomRow * Piece.State.tileHeight) + Piece.State.tileHeight; var rowsTotalHeight = rowsTotalBottomY - rowsTotalTopY; var explodingRect = Graphics.createRectangleDiv("#C0ADFF", this.State.posX, rowsTotalTopY, Piece.State.tileWidth * this.WIDTH, rowsTotalHeight, 10); // Clear the actual tiles in grid before animating the large rectangle for (row in rowsToExplode) { if (rowsToExplode.hasOwnProperty(row)) { this.clearRow(rowsToExplode[row]); } } // Use JQuery UI effect to "explode" the big rectangle $(explodingRect).hide("explode", {}, 1500); // Clean up Graphics.removeNodeFromGameContainer(explodingRect); // Shuffle remaining higher tiles downwards in field for (row in rowsToExplode) { if (rowsToExplode.hasOwnProperty(row)) { this.shiftTilesDownToRowX(rowsToExplode[row]); } } }, clearRow: function(rowNo) { for (x = 0; x < this.State.gridState.length; x++) { this.tileOff(x, rowNo); } }, shiftTilesDownToRowX: function(clearedRowY) { for (var y = (clearedRowY - 1); y >= 0; y--) { // Start at bottom to cascade tiles for (var x = 0; x < this.State.gridState.length; x++) { // Shuffle tile state down if (this.isTileOn(x, y)) { this.tileOff(x, y); this.tileOn(x, y + 1); } else { this.tileOff(x, y + 1); } } } }
We also want to improve the appearance of the playing field somewhat; solid magenta is handy for raw testing but not exactly visually appealing in a finished game. Let's add a background graphic by updating Graphics.createFieldBackground():
graphics.js:
createFieldBackground: function(bgcolor, x, y, width, height) { var rect = document.createElement('div'); rect.style.position = "absolute"; rect.style.top = y + "px"; rect.style.left = x + "px"; rect.style.zIndex = "-1"; rect.style.height = height + "px"; rect.style.width = width + "px"; rect.style.backgroundColor = bgcolor; rect.style.backgroundImage = "url('assets/images/gameplayScreen.png')"; rect.style.backgroundRepeat = "no-repeat"; rect.style.backgroundPosition = "0px 0px"; this.getGameContainer().appendChild(rect); return rect; },

Much better. The background picture is simply an image pulled from a random "mountains" search on Flickr, then cropped and tweaked a bit in Paint.Net.
We'll wrap up todays installment by adding support for sound and music. I found an elegant little library called Soundmanager. Soundmanager creates and wraps a hidden Flash component in the page, enabling us to seamlessly load and play mp3 files (or other media):
test.js:
testPlaySound: function() { Sound.playLandingSound(); }, testPlaySoundLooped: function() { Sound.playLoopedLandingSound(); }, testPlayMusic: function() { Sound.playAmbientMusic(); },
We then create our Sound object, wrapping the functionality we need from SoundManager to load and play audio.
sound.js:
var Sound = { SoundBank: { rotation: "rotation", landing: "landing", clearedOneRow: "clearedOneRow", clearedTwoRows: "clearedTwoRows", clearedThreeRows: "clearedThreeRows", clearedFourRows: "clearedFourRows", ambientMusic: "ambientMusic" }, loadSounds: function() { soundManager.createSound(this.SoundBank.rotation, 'assets/sound/rotation.mp3'); soundManager.createSound(this.SoundBank.landing, 'assets/sound/landing.mp3'); soundManager.createSound(this.SoundBank.clearedOneRow, 'assets/sound/clearedOneRow.mp3'); soundManager.createSound(this.SoundBank.clearedTwoRows, 'assets/sound/clearedTwoRows.mp3'); soundManager.createSound(this.SoundBank.clearedThreeRows, 'assets/sound/clearedThreeRows.mp3'); soundManager.createSound(this.SoundBank.clearedFourRows, 'assets/sound/clearedFourRows.mp3'); soundManager.createSound(this.SoundBank.ambientMusic, 'assets/sound/ambientMusic.mp3'); }, playAmbientMusic: function() { playLooped(this.SoundBank.ambientMusic); }, playRotationSound: function() { soundManager.play(this.SoundBank.rotation); }, playLandingSound: function() { soundManager.play(this.SoundBank.landing); }, playLoopedLandingSound: function() { playLooped(this.SoundBank.landing); }, playClearedSound: function(level) { if (!level || level < 4 || level > 1) { return; } switch (level) { case 1: soundManager.play(this.SoundBank.clearedOneRow); break; case 2: soundManager.play(this.SoundBank.clearedTwoRows); break; case 3: soundManager.play(this.SoundBank.clearedThreeRows); break; case 4: soundManager.play(this.SoundBank.clearedFourRows); break; } }, }; // Looping sound support playLooped: function playLooped(soundID) { window.setTimeout(function() { soundManager.play(soundID, { onfinish: function() { playLooped(soundID); } }); }, 1); }; soundManager.onload = function() { Sound.loadSounds(); }
Sound effects and music can now be launched from the rest of the game logic.
We have most of what we need now; in the next installment we glue it all together to create a playable game!


Write a comment